From 140883a69c5c77b62ec7317e73a3c5b60c37186d Mon Sep 17 00:00:00 2001 From: CJ_Clippy Date: Wed, 11 Dec 2024 23:23:46 -0800 Subject: [PATCH] dec 2024 progress --- Tiltfile | 43 +- charts/README.md | 5 + .../supertokens.yaml | 3 + charts/fp/templates/factory.yaml | 9 +- charts/fp/templates/keycloak.yaml | 44 + charts/fp/templates/next.yaml | 20 +- charts/fp/values.yaml | 4 + charts/keycloak/keycloak/.helmignore | 25 + charts/keycloak/keycloak/Chart.lock | 9 + charts/keycloak/keycloak/Chart.yaml | 35 + charts/keycloak/keycloak/README.md | 823 ++++++++++ charts/keycloak/keycloak/templates/NOTES.txt | 104 ++ .../keycloak/keycloak/templates/_helpers.tpl | 348 +++++ .../keycloak/templates/admin-ingress.yaml | 61 + .../templates/configmap-env-vars.yaml | 106 ++ .../keycloak/templates/configmap.yaml | 20 + .../keycloak/templates/extra-list.yaml | 9 + .../keycloak/templates/headless-service.yaml | 40 + charts/keycloak/keycloak/templates/hpa.yaml | 66 + .../keycloak/keycloak/templates/ingress.yaml | 61 + .../templates/init-scripts-configmap.yaml | 19 + .../keycloak-config-cli-configmap.yaml | 23 + .../templates/keycloak-config-cli-job.yaml | 138 ++ .../keycloak/templates/metrics-service.yaml | 41 + .../keycloak/templates/networkpolicy.yaml | 102 ++ charts/keycloak/keycloak/templates/pdb.yaml | 28 + .../keycloak/templates/prometheusrule.yaml | 20 + charts/keycloak/keycloak/templates/role.yaml | 28 + .../keycloak/templates/rolebinding.yaml | 25 + .../templates/secret-external-db.yaml | 19 + .../keycloak/keycloak/templates/secrets.yaml | 20 + .../keycloak/keycloak/templates/service.yaml | 65 + .../keycloak/templates/serviceaccount.yaml | 22 + .../keycloak/templates/servicemonitor.yaml | 58 + .../keycloak/templates/statefulset.yaml | 371 +++++ .../keycloak/templates/tls-pass-secret.yaml | 43 + .../keycloak/templates/tls-secret.yaml | 71 + charts/keycloak/keycloak/values.yaml | 1383 +++++++++++++++++ charts/keycloak/values-overrides.yaml | 50 + dockerfiles/next.dockerfile | 2 +- packages/fetchers/src/findOrCreateVtuber.ts | 2 +- packages/types/src/index.ts | 124 +- packages/utils/src/file.ts | 3 +- packages/utils/src/image.spec.ts | 4 +- packages/utils/src/patron.ts | 180 +++ scripts/data-migrations/.gitignore | 1 + scripts/data-migrations/2024-10-18-drupal.php | 79 - ...024-10-25-from-strapi-to-postgrest-mk2.sql | 1018 +++++++++++- .../2024-11-21-relate-vtubers-to-vods.sql | 19 + ...2024-11-22-relate-mux_asset_id-to-vods.sql | 11 + .../2024-11-22-relate-thumbnails-to-vods.sql | 11 + ...4-11-22-rename-vtuber_num-to-vtuber_id.sql | 11 + scripts/data-migrations/README.md | 2 +- scripts/k8s-secrets.sh | 14 +- scripts/keycloak-seed.sh | 4 + services/bot/src/utils/loader.ts | 2 +- services/factory/README.md | 6 + services/factory/crontab | 4 +- services/factory/pnpm-lock.yaml | 28 - services/factory/src/config.ts | 8 + .../factory/src/tasks/generate_thumbnail.ts | 20 +- .../src/tasks/synchronize_patrons_list.ts | 318 ++-- services/factory/src/utils/importDirectory.ts | 2 +- services/htmx/README.md | 15 + services/htmx/app/index.ts | 0 services/htmx/app/vods/index.ts | 26 + services/htmx/app/vods/view.njk | 0 services/htmx/config.ts | 20 + services/htmx/index.ts | 21 + services/htmx/package.json | 23 + services/htmx/pnpm-lock.yaml | 876 +++++++++++ services/htmx/src/routes.ts | 26 + .../00096_add-id_num-to-s3_files.sql | 4 + .../00097_add-id_num-to-mux-assets.sql | 4 + .../00098_add-id_num-to-streams.sql | 6 + .../migrations/00099_add-vtuber_num.sql | 6 + .../migrations/00100_add-id_num-to-tags.sql | 2 + .../00101_add-missing-cols-to-tags.sql | 11 + .../00102_add-toy_id_num-to-tags.sql | 3 + .../migrations/00103_add-toy_num.sql | 21 + .../migrations/00104_add-toy_num-fix.sql | 7 + .../00105_add-id_num-to-vods_links.sql | 18 + .../migrations/00106_add-id_num-to-vods.sql | 2 + .../migrations/00107_remove-uuid.sql | 125 ++ .../migrations/00108_add-strapi-tables.sql | 47 + .../00109_use-consistent-description_1.sql | 5 + .../migrations/00110_unseed-vtubers.sql | 5 + .../00111_create-strapi-tables-tvr.sql | 33 + .../migrations/00112_create-tvr_tag_links.sql | 38 + .../migrations/00113_create-tvr_vod_links.sql | 47 + .../00114_create-tags_toy_links.sql | 47 + .../00115_create-tags_vods_links.sql | 55 + .../migrations/00116_create-timestamps.sql | 210 +++ .../00117_create-timestamps_tag_links.sql | 41 + .../00118_create-timestamps_vod_links.sql | 50 + .../00119_create-toys_link_tag_links.sql | 41 + .../00120_create-vods_mux_asset_links.sql | 41 + .../00121_create-vods_stream_links.sql | 50 + .../00122_create-vods_thumbnail_links.sql | 87 ++ .../00123_create-vods_uploader_links.sql | 37 + .../00124_create-vods_video_src_b_2_links.sql | 41 + .../00125_create-vods_vtuber_links.sql | 50 + .../00126_create-vtubers_toy_links.sql | 105 ++ ...rant-permissions-for-vods_vtuber_links.sql | 2 + .../00128_add-vtuber-fk-to-vods.sql | 6 + .../00129_add-vtuber_id-to-vods.sql | 9 + .../00130_add-s3_files-fk-to-vods.sql | 6 + .../00131_grant-perms-on-s3_files.sql | 7 + .../00132_rename-vtuber_num-to-vtuber_id.sql | 7 + .../00133_add-mux_assets-fk-to-vods.sql | 6 + .../00134_add-thumbnail-fk-to-vods.sql | 6 + .../next/app/api/auth/[...nextauth]/route.ts | 17 + .../next/app/api/auth/[[...path]]/route.ts | 39 - services/next/app/api/page.tsx | 82 +- .../patreon/currently-entitled-tiers/route.ts | 53 + .../next/app/api/patreon/session/route.ts | 18 + services/next/app/api/session/route.ts | 12 + services/next/app/api/user/route.ts | 3 + services/next/app/archive/page.tsx | 1 + services/next/app/auth/[[...path]]/page.tsx | 27 - services/next/app/callback/route.ts | 11 - .../app/components/access-denied-screen.tsx | 24 + services/next/app/components/auth-buttons.tsx | 41 + .../next/app/components/auth-provider.tsx | 11 + services/next/app/components/auth.tsx | 133 -- services/next/app/components/auth.tsx.old | 116 ++ services/next/app/components/error-card.tsx | 9 + services/next/app/components/navbar.tsx | 67 +- services/next/app/components/patron-perks.tsx | 51 + services/next/app/components/patrons-list.tsx | 23 +- .../next/app/components/permissions-table.tsx | 8 + .../next/app/components/protected-route.tsx | 53 + .../next/app/components/query-provider.tsx | 27 + services/next/app/components/spinner.tsx | 6 + .../next/app/components/streams-table.tsx | 23 +- .../app/components/supertokensProvider.tsx | 18 - services/next/app/components/tag.tsx | 1 - services/next/app/components/tagger.tsx | 1 - .../next/app/components/timestamps-list.tsx | 1 - services/next/app/components/upload-form.tsx | 1 - .../next/app/components/user-controls.tsx | 1 - .../next/app/components/user-metadata.tsx | 37 + services/next/app/components/vibrate-test.tsx | 29 + services/next/app/components/video-player.tsx | 1 - services/next/app/components/vod-card.tsx | 14 +- services/next/app/components/vods-list.tsx | 10 +- services/next/app/components/vtuber-card.tsx | 10 +- services/next/app/config/appInfo.ts | 8 - services/next/app/config/backend.ts | 75 - services/next/app/config/clientConfigs.ts | 16 + services/next/app/config/configs.ts | 59 +- services/next/app/config/frontend.tsx | 48 - .../app/connect/patreon/redirect/page.tsx | 1 - services/next/app/faq/page.tsx | 37 - services/next/app/latest-vods/page.tsx | 8 +- services/next/app/layout.tsx | 62 +- services/next/app/lib/auth.ts | 135 ++ services/next/app/lib/constants.ts | 9 +- services/next/app/lib/dates.ts | 2 +- services/next/app/lib/keycloak.ts | 195 +++ services/next/app/lib/patreon.ts | 251 ++- services/next/app/lib/streams.ts | 114 +- services/next/app/lib/tag-vod-relations.ts | 3 +- services/next/app/lib/tags.ts | 3 +- services/next/app/lib/timestamps.ts | 1 - services/next/app/lib/useForwardRef.ts | 2 +- services/next/app/lib/vods.ts | 222 ++- services/next/app/lib/vtubers.ts | 26 +- services/next/app/logto.ts | 11 - services/next/app/page.tsx | 38 +- .../profile/hooks/updateIsUsernamePublic.ts | 14 + .../next/app/profile/hooks/useMetadata.ts | 52 + services/next/app/profile/page.tsx | 219 ++- services/next/app/tags/[slug]/page.tsx | 8 +- services/next/app/vt/[slug]/page.tsx | 106 +- .../next/app/vt/[slug]/vods/[page]/page.tsx | 7 +- services/next/app/vt/[slug]/vods/page.tsx | 14 +- services/next/app/vt/page.tsx | 7 +- services/next/logto.ts | 13 - services/next/middleware.ts | 176 +++ services/next/package.json | 12 +- services/next/pnpm-lock.yaml | 952 ++++++------ services/next/test-jwe-decrypt.js | 18 + services/next/tsconfig.json | 2 +- .../tweet/content-types/tweet/lifecycles.js | 8 +- 185 files changed, 10435 insertions(+), 2007 deletions(-) rename charts/fp/{templates => templates-staging}/supertokens.yaml (96%) create mode 100644 charts/fp/templates/keycloak.yaml create mode 100644 charts/keycloak/keycloak/.helmignore create mode 100644 charts/keycloak/keycloak/Chart.lock create mode 100644 charts/keycloak/keycloak/Chart.yaml create mode 100644 charts/keycloak/keycloak/README.md create mode 100644 charts/keycloak/keycloak/templates/NOTES.txt create mode 100644 charts/keycloak/keycloak/templates/_helpers.tpl create mode 100644 charts/keycloak/keycloak/templates/admin-ingress.yaml create mode 100644 charts/keycloak/keycloak/templates/configmap-env-vars.yaml create mode 100644 charts/keycloak/keycloak/templates/configmap.yaml create mode 100644 charts/keycloak/keycloak/templates/extra-list.yaml create mode 100644 charts/keycloak/keycloak/templates/headless-service.yaml create mode 100644 charts/keycloak/keycloak/templates/hpa.yaml create mode 100644 charts/keycloak/keycloak/templates/ingress.yaml create mode 100644 charts/keycloak/keycloak/templates/init-scripts-configmap.yaml create mode 100644 charts/keycloak/keycloak/templates/keycloak-config-cli-configmap.yaml create mode 100644 charts/keycloak/keycloak/templates/keycloak-config-cli-job.yaml create mode 100644 charts/keycloak/keycloak/templates/metrics-service.yaml create mode 100644 charts/keycloak/keycloak/templates/networkpolicy.yaml create mode 100644 charts/keycloak/keycloak/templates/pdb.yaml create mode 100644 charts/keycloak/keycloak/templates/prometheusrule.yaml create mode 100644 charts/keycloak/keycloak/templates/role.yaml create mode 100644 charts/keycloak/keycloak/templates/rolebinding.yaml create mode 100644 charts/keycloak/keycloak/templates/secret-external-db.yaml create mode 100644 charts/keycloak/keycloak/templates/secrets.yaml create mode 100644 charts/keycloak/keycloak/templates/service.yaml create mode 100644 charts/keycloak/keycloak/templates/serviceaccount.yaml create mode 100644 charts/keycloak/keycloak/templates/servicemonitor.yaml create mode 100644 charts/keycloak/keycloak/templates/statefulset.yaml create mode 100644 charts/keycloak/keycloak/templates/tls-pass-secret.yaml create mode 100644 charts/keycloak/keycloak/templates/tls-secret.yaml create mode 100644 charts/keycloak/keycloak/values.yaml create mode 100644 charts/keycloak/values-overrides.yaml create mode 100644 packages/utils/src/patron.ts create mode 100644 scripts/data-migrations/.gitignore delete mode 100644 scripts/data-migrations/2024-10-18-drupal.php create mode 100644 scripts/data-migrations/2024-11-21-relate-vtubers-to-vods.sql create mode 100644 scripts/data-migrations/2024-11-22-relate-mux_asset_id-to-vods.sql create mode 100644 scripts/data-migrations/2024-11-22-relate-thumbnails-to-vods.sql create mode 100644 scripts/data-migrations/2024-11-22-rename-vtuber_num-to-vtuber_id.sql create mode 100755 scripts/keycloak-seed.sh create mode 100644 services/htmx/README.md create mode 100644 services/htmx/app/index.ts create mode 100644 services/htmx/app/vods/index.ts create mode 100644 services/htmx/app/vods/view.njk create mode 100644 services/htmx/config.ts create mode 100644 services/htmx/index.ts create mode 100644 services/htmx/package.json create mode 100644 services/htmx/pnpm-lock.yaml create mode 100644 services/htmx/src/routes.ts create mode 100644 services/migrations/migrations/00096_add-id_num-to-s3_files.sql create mode 100644 services/migrations/migrations/00097_add-id_num-to-mux-assets.sql create mode 100644 services/migrations/migrations/00098_add-id_num-to-streams.sql create mode 100644 services/migrations/migrations/00099_add-vtuber_num.sql create mode 100644 services/migrations/migrations/00100_add-id_num-to-tags.sql create mode 100644 services/migrations/migrations/00101_add-missing-cols-to-tags.sql create mode 100644 services/migrations/migrations/00102_add-toy_id_num-to-tags.sql create mode 100644 services/migrations/migrations/00103_add-toy_num.sql create mode 100644 services/migrations/migrations/00104_add-toy_num-fix.sql create mode 100644 services/migrations/migrations/00105_add-id_num-to-vods_links.sql create mode 100644 services/migrations/migrations/00106_add-id_num-to-vods.sql create mode 100644 services/migrations/migrations/00107_remove-uuid.sql create mode 100644 services/migrations/migrations/00108_add-strapi-tables.sql create mode 100644 services/migrations/migrations/00109_use-consistent-description_1.sql create mode 100644 services/migrations/migrations/00110_unseed-vtubers.sql create mode 100644 services/migrations/migrations/00111_create-strapi-tables-tvr.sql create mode 100644 services/migrations/migrations/00112_create-tvr_tag_links.sql create mode 100644 services/migrations/migrations/00113_create-tvr_vod_links.sql create mode 100644 services/migrations/migrations/00114_create-tags_toy_links.sql create mode 100644 services/migrations/migrations/00115_create-tags_vods_links.sql create mode 100644 services/migrations/migrations/00116_create-timestamps.sql create mode 100644 services/migrations/migrations/00117_create-timestamps_tag_links.sql create mode 100644 services/migrations/migrations/00118_create-timestamps_vod_links.sql create mode 100644 services/migrations/migrations/00119_create-toys_link_tag_links.sql create mode 100644 services/migrations/migrations/00120_create-vods_mux_asset_links.sql create mode 100644 services/migrations/migrations/00121_create-vods_stream_links.sql create mode 100644 services/migrations/migrations/00122_create-vods_thumbnail_links.sql create mode 100644 services/migrations/migrations/00123_create-vods_uploader_links.sql create mode 100644 services/migrations/migrations/00124_create-vods_video_src_b_2_links.sql create mode 100644 services/migrations/migrations/00125_create-vods_vtuber_links.sql create mode 100644 services/migrations/migrations/00126_create-vtubers_toy_links.sql create mode 100644 services/migrations/migrations/00127_grant-permissions-for-vods_vtuber_links.sql create mode 100644 services/migrations/migrations/00128_add-vtuber-fk-to-vods.sql create mode 100644 services/migrations/migrations/00129_add-vtuber_id-to-vods.sql create mode 100644 services/migrations/migrations/00130_add-s3_files-fk-to-vods.sql create mode 100644 services/migrations/migrations/00131_grant-perms-on-s3_files.sql create mode 100644 services/migrations/migrations/00132_rename-vtuber_num-to-vtuber_id.sql create mode 100644 services/migrations/migrations/00133_add-mux_assets-fk-to-vods.sql create mode 100644 services/migrations/migrations/00134_add-thumbnail-fk-to-vods.sql create mode 100644 services/next/app/api/auth/[...nextauth]/route.ts delete mode 100644 services/next/app/api/auth/[[...path]]/route.ts create mode 100644 services/next/app/api/patreon/currently-entitled-tiers/route.ts create mode 100644 services/next/app/api/patreon/session/route.ts create mode 100644 services/next/app/api/session/route.ts create mode 100644 services/next/app/api/user/route.ts delete mode 100644 services/next/app/auth/[[...path]]/page.tsx delete mode 100644 services/next/app/callback/route.ts create mode 100644 services/next/app/components/access-denied-screen.tsx create mode 100644 services/next/app/components/auth-buttons.tsx create mode 100644 services/next/app/components/auth-provider.tsx delete mode 100644 services/next/app/components/auth.tsx create mode 100644 services/next/app/components/auth.tsx.old create mode 100644 services/next/app/components/error-card.tsx create mode 100644 services/next/app/components/patron-perks.tsx create mode 100644 services/next/app/components/permissions-table.tsx create mode 100644 services/next/app/components/protected-route.tsx create mode 100644 services/next/app/components/query-provider.tsx create mode 100644 services/next/app/components/spinner.tsx delete mode 100644 services/next/app/components/supertokensProvider.tsx create mode 100644 services/next/app/components/user-metadata.tsx create mode 100644 services/next/app/components/vibrate-test.tsx delete mode 100644 services/next/app/config/appInfo.ts delete mode 100644 services/next/app/config/backend.ts create mode 100644 services/next/app/config/clientConfigs.ts delete mode 100644 services/next/app/config/frontend.tsx create mode 100644 services/next/app/lib/auth.ts create mode 100644 services/next/app/lib/keycloak.ts delete mode 100644 services/next/app/logto.ts create mode 100644 services/next/app/profile/hooks/updateIsUsernamePublic.ts create mode 100644 services/next/app/profile/hooks/useMetadata.ts delete mode 100644 services/next/logto.ts create mode 100644 services/next/middleware.ts create mode 100644 services/next/test-jwe-decrypt.js diff --git a/Tiltfile b/Tiltfile index eb06f3e..8cd535a 100644 --- a/Tiltfile +++ b/Tiltfile @@ -116,6 +116,13 @@ k8s_yaml(helm( './charts/traefik/values-overrides.yaml' ] )) +k8s_yaml(helm( + './charts/keycloak/keycloak', + namespace='futureporn', + values=[ + './charts/keycloak/values-overrides.yaml' + ] +)) k8s_yaml(helm( './charts/fp', values=['./charts/fp/values.yaml'], @@ -264,12 +271,13 @@ docker_build( load('ext://uibutton', 'cmd_button') -cmd_button('supertokens:seed', - argv=['./scripts/supertokens-seed.sh'], - resource='supertokens', +cmd_button('keycloak:seed', + argv=['./scripts/keycloak-seed.sh'], + resource='keycloak', icon_name='start', - text='create supertokens database', + text='create keycloak database', ) + cmd_button('postgres:restore', argv=['./scripts/postgres-restore.sh'], resource='postgresql-primary', @@ -332,6 +340,13 @@ docker_build( dockerfile='dockerfiles/migrations.dockerfile', target='migrations', pull=False, + only=[ + './.npmrc', + './package.json', + './pnpm-lock.yaml', + './pnpm-workspace.yaml', + './services/migrations' + ], ) ## Uncomment the following for fp/next in dev mode @@ -432,11 +447,11 @@ k8s_resource( k8s_resource( workload='next', links=[ - link('https://next.fp.sbtp.xyz'), - link('https://next.fp.sbtp.xyz/api/auth/dashboard'), + link('https://next.fp.sbtp.xyz') ], resource_deps=['postgrest', 'postgresql-primary'], labels=['frontend'], + port_forwards=['3000'], ) @@ -502,15 +517,25 @@ k8s_resource( # labels=['database'], # ) +# k8s_resource( +# workload='supertokens', +# links=[ +# link('https://supertokens.fp.sbtp.xyz'), +# ], +# labels=['backend'], +# ) + k8s_resource( - workload='supertokens', + workload='keycloak', links=[ - link('https://supertokens.fp.sbtp.xyz'), + link('https://keycloak.fp.sbtp.xyz'), ], + port_forwards=['8080'], labels=['backend'], ) + # k8s_resource( # workload='mailbox', # resource_deps=['postgresql-primary', 'postgrest'], @@ -596,7 +621,7 @@ k8s_resource( ) k8s_resource( workload='pgadmin4', - # port_forwards=['5050:80'], + port_forwards=['5050:80'], labels=['database'], ) k8s_resource( diff --git a/charts/README.md b/charts/README.md index 4910adc..1cba87a 100644 --- a/charts/README.md +++ b/charts/README.md @@ -69,3 +69,8 @@ We override default values in the parent folder. ### phpmyadmin helm pull bitnami/phpmyadmin --version 17.0.7 --untar --destination ./charts/phpmyadmin + +### keycloak + + helm pull bitnami/keycloak --version 24.2.2 --untar --destination ./charts/keycloak + diff --git a/charts/fp/templates/supertokens.yaml b/charts/fp/templates-staging/supertokens.yaml similarity index 96% rename from charts/fp/templates/supertokens.yaml rename to charts/fp/templates-staging/supertokens.yaml index ceca4b5..1d44a12 100644 --- a/charts/fp/templates/supertokens.yaml +++ b/charts/fp/templates-staging/supertokens.yaml @@ -126,6 +126,8 @@ spec: value: "true" - name: SUPERTOKENS_PORT value: "{{ .Values.supertokens.port }}" + - name: SUPERTOKENS_URL + value: {{ printf "https://%s" .Values.supertokens.hostname | quote }} - name: POSTGRESQL_CONNECTION_URI valueFrom: secretKeyRef: @@ -136,3 +138,4 @@ spec: secretKeyRef: name: supertokens key: apiKeys + diff --git a/charts/fp/templates/factory.yaml b/charts/fp/templates/factory.yaml index a83c21c..5b8d6f2 100644 --- a/charts/fp/templates/factory.yaml +++ b/charts/fp/templates/factory.yaml @@ -44,8 +44,10 @@ spec: secretKeyRef: name: patreon key: creatorRefreshToken + - name: SUPERTOKENS_URL + value: {{ printf "https://%s" .Values.supertokens.hostname | quote }} - name: POSTGREST_URL - value: "{{ .Values.postgrest.url }}" + value: {{ printf "https://%s" .Values.postgrest.hostname | quote }} - name: SCOUT_URL value: "{{ .Values.scout.url }}" - name: CACHE_DIR @@ -68,6 +70,11 @@ spec: secretKeyRef: name: capture key: s3SecretAccessKey + - name: SUPERTOKENS_API_KEY + valueFrom: + secretKeyRef: + name: supertokens + key: apiKey resources: limits: cpu: 250m diff --git a/charts/fp/templates/keycloak.yaml b/charts/fp/templates/keycloak.yaml new file mode 100644 index 0000000..0aba4c5 --- /dev/null +++ b/charts/fp/templates/keycloak.yaml @@ -0,0 +1,44 @@ +## most of keycloak's config is done thru it's Helm Chart values-overrides.yaml in ../../keycloak +## however, there are some things that said Chart doesn't handle for us, such as Certificates and traefik HTTPRoutes. +## we handle those out-of-spec things here + +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: keycloak-httproute + namespace: futureporn +spec: + parentRefs: + - name: traefik-gateway + hostnames: + - keycloak.fp.sbtp.xyz + rules: + - matches: + - path: + type: PathPrefix + value: / + filters: + - type: ResponseHeaderModifier + responseHeaderModifier: + add: + - name: x-cj-was-here + value: "true" + backendRefs: + - name: keycloak + port: 8080 + +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: keycloak + namespace: futureporn +spec: + secretName: keycloak-tls + issuerRef: + name: {{ .Values.certManager.issuer | quote }} + kind: ClusterIssuer + dnsNames: + - {{ .Values.keycloak.hostname | quote }} + diff --git a/charts/fp/templates/next.yaml b/charts/fp/templates/next.yaml index 78ccb2a..2ce9144 100644 --- a/charts/fp/templates/next.yaml +++ b/charts/fp/templates/next.yaml @@ -18,11 +18,20 @@ spec: - name: NEXT_PUBLIC_UPPY_COMPANION_URL value: "{{ .Values.uppy.url }}" - name: NEXT_PUBLIC_POSTGREST_URL - value: "{{ .Values.postgrest.url }}" + value: {{ printf "https://%s" .Values.postgrest.hostname | quote }} - name: NEXT_PUBLIC_WEBSITE_DOMAIN value: {{ printf "https://%s" .Values.next.hostname | quote }} - name: NEXT_PUBLIC_API_DOMAIN value: {{ .Values.next.hostname | quote }} + - name: NEXTAUTH_URL + value: {{ printf "https://%s" .Values.next.hostname | quote }} + - name: NEXTAUTH_URL_INTERNAL + value: http://next.futureporn.svc.cluster.local + - name: NEXTAUTH_SECRET + valueFrom: + secretKeyRef: + name: next + key: nextAuthSecret - name: PATREON_CLIENT_ID valueFrom: secretKeyRef: @@ -40,6 +49,15 @@ spec: key: apiKeys - name: SUPERTOKENS_URL value: {{ printf "https://%s" .Values.supertokens.hostname | quote }} + - name: KEYCLOAK_CLIENT_ID + value: futureporn + - name: KEYCLOAK_CLIENT_SECRET + valueFrom: + secretKeyRef: + name: keycloak + key: clientSecret + - name: KEYCLOAK_ISSUER + value: {{ .Values.keycloak.issuer | quote }} ports: - name: web containerPort: 3000 diff --git a/charts/fp/values.yaml b/charts/fp/values.yaml index d2e2d02..64cfb79 100644 --- a/charts/fp/values.yaml +++ b/charts/fp/values.yaml @@ -96,6 +96,10 @@ supertokens: port: 3348 hostname: supertokens.fp.sbtp.xyz replicas: 1 +keycloak: + hostname: keycloak.fp.sbtp.xyz + replicas: 1 + issuer: https://keycloak.fp.sbtp.xyz/realms/futureporn logto: admin: port: 3002 diff --git a/charts/keycloak/keycloak/.helmignore b/charts/keycloak/keycloak/.helmignore new file mode 100644 index 0000000..207983f --- /dev/null +++ b/charts/keycloak/keycloak/.helmignore @@ -0,0 +1,25 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +# img folder +img/ +# Changelog +CHANGELOG.md diff --git a/charts/keycloak/keycloak/Chart.lock b/charts/keycloak/keycloak/Chart.lock new file mode 100644 index 0000000..76f6912 --- /dev/null +++ b/charts/keycloak/keycloak/Chart.lock @@ -0,0 +1,9 @@ +dependencies: +- name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 16.2.2 +- name: common + repository: oci://registry-1.docker.io/bitnamicharts + version: 2.27.0 +digest: sha256:80a30494e1385f132dc70f43bf342dfbfc2250d4bea81ddea4de831617245d75 +generated: "2024-11-22T07:51:44.565506689Z" diff --git a/charts/keycloak/keycloak/Chart.yaml b/charts/keycloak/keycloak/Chart.yaml new file mode 100644 index 0000000..a1b7090 --- /dev/null +++ b/charts/keycloak/keycloak/Chart.yaml @@ -0,0 +1,35 @@ +annotations: + category: DeveloperTools + images: | + - name: keycloak + image: docker.io/bitnami/keycloak:26.0.6-debian-12-r0 + - name: keycloak-config-cli + image: docker.io/bitnami/keycloak-config-cli:6.1.6-debian-12-r6 + licenses: Apache-2.0 +apiVersion: v2 +appVersion: 26.0.6 +dependencies: +- condition: postgresql.enabled + name: postgresql + repository: oci://registry-1.docker.io/bitnamicharts + version: 16.x.x +- name: common + repository: oci://registry-1.docker.io/bitnamicharts + tags: + - bitnami-common + version: 2.x.x +description: Keycloak is a high performance Java-based identity and access management + solution. It lets developers add an authentication layer to their applications with + minimum effort. +home: https://bitnami.com +icon: https://bitnami.com/assets/stacks/keycloak/img/keycloak-stack-220x234.png +keywords: +- keycloak +- access-management +maintainers: +- name: Broadcom, Inc. All Rights Reserved. + url: https://github.com/bitnami/charts +name: keycloak +sources: +- https://github.com/bitnami/charts/tree/main/bitnami/keycloak +version: 24.2.2 diff --git a/charts/keycloak/keycloak/README.md b/charts/keycloak/keycloak/README.md new file mode 100644 index 0000000..c89b34e --- /dev/null +++ b/charts/keycloak/keycloak/README.md @@ -0,0 +1,823 @@ + + +# Bitnami package for Keycloak + +Keycloak is a high performance Java-based identity and access management solution. It lets developers add an authentication layer to their applications with minimum effort. + +[Overview of Keycloak](https://www.keycloak.org/) + +Trademarks: This software listing is packaged by Bitnami. The respective trademarks mentioned in the offering are owned by the respective companies, and use of them does not imply any affiliation or endorsement. + +## TL;DR + +```console +helm install my-release oci://registry-1.docker.io/bitnamicharts/keycloak +``` + +Looking to use Keycloak in production? Try [VMware Tanzu Application Catalog](https://bitnami.com/enterprise), the commercial edition of the Bitnami catalog. + +## Introduction + +Bitnami charts for Helm are carefully engineered, actively maintained and are the quickest and easiest way to deploy containers on a Kubernetes cluster that are ready to handle production workloads. + +This chart bootstraps a [Keycloak](https://github.com/bitnami/containers/tree/main/bitnami/keycloak) deployment on a [Kubernetes](https://kubernetes.io) cluster using the [Helm](https://helm.sh) package manager. + +Bitnami charts can be used with [Kubeapps](https://kubeapps.dev/) for deployment and management of Helm Charts in clusters. + +## Prerequisites + +- Kubernetes 1.23+ +- Helm 3.8.0+ + +## Installing the Chart + +To install the chart with the release name `my-release`: + +```console +helm install my-release oci://REGISTRY_NAME/REPOSITORY_NAME/keycloak +``` + +> Note: You need to substitute the placeholders `REGISTRY_NAME` and `REPOSITORY_NAME` with a reference to your Helm chart registry and repository. For example, in the case of Bitnami, you need to use `REGISTRY_NAME=registry-1.docker.io` and `REPOSITORY_NAME=bitnamicharts`. + +These commands deploy a Keycloak application on the Kubernetes cluster in the default configuration. + +> **Tip**: List all releases using `helm list` + +## Configuration and installation details + +### Resource requests and limits + +Bitnami charts allow setting resource requests and limits for all containers inside the chart deployment. These are inside the `resources` value (check parameter table). Setting requests is essential for production workloads and these should be adapted to your specific use case. + +To make this process easier, the chart contains the `resourcesPreset` values, which automatically sets the `resources` section according to different presets. Check these presets in [the bitnami/common chart](https://github.com/bitnami/charts/blob/main/bitnami/common/templates/_resources.tpl#L15). However, in production workloads using `resourcePreset` is discouraged as it may not fully adapt to your specific needs. Find more information on container resource management in the [official Kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). + +### [Rolling vs Immutable tags](https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-understand-rolling-tags-containers-index.html) + +It is strongly recommended to use immutable tags in a production environment. This ensures your deployment does not change automatically if the same tag is updated with a different image. + +Bitnami will release a new chart updating its containers if a new version of the main container, significant changes, or critical vulnerabilities exist. + +### Use an external database + +Sometimes, you may want to have Keycloak connect to an external PostgreSQL database rather than a database within your cluster - for example, when using a managed database service, or when running a single database server for all your applications. To do this, set the `postgresql.enabled` parameter to `false` and specify the credentials for the external database using the `externalDatabase.*` parameters. Here is an example: + +```text +postgresql.enabled=false +externalDatabase.host=myexternalhost +externalDatabase.user=myuser +externalDatabase.password=mypassword +externalDatabase.database=mydatabase +externalDatabase.port=5432 +``` + +> NOTE: Only PostgreSQL database server is supported as external database + +It is not supported but possible to run Keycloak with an external MSSQL database with the following settings: + +```yaml +externalDatabase: + host: "mssql.example.com" + port: 1433 + user: keycloak + database: keycloak + existingSecret: passwords +extraEnvVars: + - name: KC_DB # override values from the conf file + value: 'mssql' + - name: KC_DB_URL + value: 'jdbc:sqlserver://mssql.example.com:1433;databaseName=keycloak;' +``` + +### Importing and exporting a realm + +#### Importing a realm + +You can import a realm by setting the `KEYCLOAK_EXTRA_ARGS` to contain the `--import-realm` argument. + +This will import all `*.json` under `/opt/bitnami/keycloak/data/import` files as a realm into keycloak as per the +official documentation [here](https://www.keycloak.org/server/importExport#_importing_a_realm_from_a_directory). You +can supply the files by mounting a volume e.g. with docker compose as follows: + +```yaml +keycloak: + image: bitnami/keycloak:latest + volumes: + - /local/path/to/realms/folder:/opt/bitnami/keycloak/data/import +``` + +#### Exporting a realm + +You can export a realm through the GUI but it will not export users even the option is set, this is a known keycloak +[bug](https://github.com/keycloak/keycloak/issues/23970). + +By using the `kc.sh` script you can export a realm with users. Be sure to mount the export folder to a local folder: + +```yaml +keycloak: + image: bitnami/keycloak:latest + volumes: + - /local/path/to/export/folder:/export +``` + +Then open a terminal in the running keycloak container and run: + +```bash +kc.sh export --dir /export/ --users realm_file +```` + +This will export the all the realms with users to the `/export` folder. + +### Configure Ingress + +This chart provides support for Ingress resources. If you have an ingress controller installed on your cluster, such as [nginx-ingress-controller](https://github.com/bitnami/charts/tree/main/bitnami/nginx-ingress-controller) or [contour](https://github.com/bitnami/charts/tree/main/bitnami/contour) you can utilize the ingress controller to serve your application.To enable Ingress integration, set `ingress.enabled` to `true`. + +The most common scenario is to have one host name mapped to the deployment. In this case, the `ingress.hostname` property can be used to set the host name. The `ingress.tls` parameter can be used to add the TLS configuration for this host. + +However, it is also possible to have more than one host. To facilitate this, the `ingress.extraHosts` parameter (if available) can be set with the host names specified as an array. The `ingress.extraTLS` parameter (if available) can also be used to add the TLS configuration for extra hosts. + +> NOTE: For each host specified in the `ingress.extraHosts` parameter, it is necessary to set a name, path, and any annotations that the Ingress controller should know about. Not all annotations are supported by all Ingress controllers, but [this annotation reference document](https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md) lists the annotations supported by many popular Ingress controllers. + +Adding the TLS parameter (where available) will cause the chart to generate HTTPS URLs, and the application will be available on port 443. The actual TLS secrets do not have to be generated by this chart. However, if TLS is enabled, the Ingress record will not work until the TLS secret exists. + +[Learn more about Ingress controllers](https://kubernetes.io/docs/concepts/services-networking/ingress-controllers/). + +### Configure admin Ingress + +In addition to the Ingress resource described above, this chart also provides the ability to define an Ingress for the admin area of Keycloak, for example the `master` realm. + +For this scenario, you can use the Keycloak Config CLI integration with the following values, where `keycloak-admin.example.com` is to be replaced by the actual hostname: + +```yaml +adminIngress: + enabled: true + hostname: keycloak-admin.example.com +keycloakConfigCli: + enabled: true + configuration: + master.json: | + { + "realm" : "master", + "attributes": { + "frontendUrl": "https://keycloak-admin.example.com" + } + } +``` + +### Configure TLS Secrets for use with Ingress + +This chart facilitates the creation of TLS secrets for use with the Ingress controller (although this is not mandatory). There are several common use cases: + +- Generate certificate secrets based on chart parameters. +- Enable externally generated certificates. +- Manage application certificates via an external service (like [cert-manager](https://github.com/jetstack/cert-manager/)). +- Create self-signed certificates within the chart (if supported). + +In the first two cases, a certificate and a key are needed. Files are expected in `.pem` format. + +Here is an example of a certificate file: + +> NOTE: There may be more than one certificate if there is a certificate chain. + +```text +-----BEGIN CERTIFICATE----- +MIID6TCCAtGgAwIBAgIJAIaCwivkeB5EMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV +... +jScrvkiBO65F46KioCL9h5tDvomdU1aqpI/CBzhvZn1c0ZTf87tGQR8NK7v7 +-----END CERTIFICATE----- +``` + +Here is an example of a certificate key: + +```text +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAvLYcyu8f3skuRyUgeeNpeDvYBCDcgq+LsWap6zbX5f8oLqp4 +... +wrj2wDbCDCFmfqnSJ+dKI3vFLlEz44sAV8jX/kd4Y6ZTQhlLbYc= +-----END RSA PRIVATE KEY----- +``` + +- If using Helm to manage the certificates based on the parameters, copy these values into the `certificate` and `key` values for a given `*.ingress.secrets` entry. +- If managing TLS secrets separately, it is necessary to create a TLS secret with name `INGRESS_HOSTNAME-tls` (where INGRESS_HOSTNAME is a placeholder to be replaced with the hostname you set using the `*.ingress.hostname` parameter). +- If your cluster has a [cert-manager](https://github.com/jetstack/cert-manager) add-on to automate the management and issuance of TLS certificates, add to `*.ingress.annotations` the [corresponding ones](https://cert-manager.io/docs/usage/ingress/#supported-annotations) for cert-manager. +- If using self-signed certificates created by Helm, set both `*.ingress.tls` and `*.ingress.selfSigned` to `true`. + +### Use with ingress offloading SSL + +If your ingress controller has the SSL Termination, you should set `proxy` to `edge`. + +### Manage secrets and passwords + +This chart provides several ways to manage passwords: + +- Values passed to the chart: In this scenario, a new secret including all the passwords will be created during the chart installation. When upgrading, it is necessary to provide the secrets to the chart as shown below. Replace the KEYCLOAK_ADMIN_PASSWORD, POSTGRESQL_PASSWORD and POSTGRESQL_PVC placeholders with the correct passwords and PVC name. + +```console +helm upgrade keycloak bitnami/keycloak \ + --set auth.adminPassword=KEYCLOAK_ADMIN_PASSWORD \ + --set postgresql.postgresqlPassword=POSTGRESQL_PASSWORD \ + --set postgresql.persistence.existingClaim=POSTGRESQL_PVC +``` + +- An existing secret with all the passwords via the `existingSecret` parameter. + +### Add extra environment variables + +In case you want to add extra environment variables (useful for advanced operations like custom init scripts), you can use the `extraEnvVars` property. + +```yaml +extraEnvVars: + - name: KEYCLOAK_LOG_LEVEL + value: DEBUG +``` + +Alternatively, you can use a ConfigMap or a Secret with the environment variables. To do so, use the `extraEnvVarsCM` or the `extraEnvVarsSecret` values. + +### Use Sidecars and Init Containers + +If additional containers are needed in the same pod (such as additional metrics or logging exporters), they can be defined using the `sidecars` config parameter. + +```yaml +sidecars: +- name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +If these sidecars export extra ports, extra port definitions can be added using the `service.extraPorts` parameter (where available), as shown in the example below: + +```yaml +service: + extraPorts: + - name: extraPort + port: 11311 + targetPort: 11311 +``` + +> NOTE: This Helm chart already includes sidecar containers for the Prometheus exporters (where applicable). These can be activated by adding the `--enable-metrics=true` parameter at deployment time. The `sidecars` parameter should therefore only be used for any extra sidecar containers. + +If additional init containers are needed in the same pod, they can be defined using the `initContainers` parameter. Here is an example: + +```yaml +initContainers: + - name: your-image-name + image: your-image + imagePullPolicy: Always + ports: + - name: portname + containerPort: 1234 +``` + +Learn more about [sidecar containers](https://kubernetes.io/docs/concepts/workloads/pods/) and [init containers](https://kubernetes.io/docs/concepts/workloads/pods/init-containers/). + +### Initialize a fresh instance + +The [Bitnami Keycloak](https://github.com/bitnami/containers/tree/main/bitnami/keycloak) image allows you to use your custom scripts to initialize a fresh instance. In order to execute the scripts, you can specify custom scripts using the `initdbScripts` parameter as dict. + +In addition to this option, you can also set an external ConfigMap with all the initialization scripts. This is done by setting the `initdbScriptsConfigMap` parameter. Note that this will override the previous option. + +The allowed extensions is `.sh`. + +### Deploy extra resources + +There are cases where you may want to deploy extra objects, such a ConfigMap containing your app's configuration or some extra deployment with a micro service used by your app. For covering this case, the chart allows adding the full specification of other objects using the `extraDeploy` parameter. + +### Set Pod affinity + +This chart allows you to set your custom affinity using the `affinity` parameter. Find more information about Pod's affinity in the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity). + +As an alternative, you can use of the preset configurations for pod affinity, pod anti-affinity, and node affinity available at the [bitnami/common](https://github.com/bitnami/charts/tree/main/bitnami/common#affinities) chart. To do so, set the `podAffinityPreset`, `podAntiAffinityPreset`, or `nodeAffinityPreset` parameters. + +## Parameters + +### Global parameters + +| Name | Description | Value | +| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | +| `global.imageRegistry` | Global Docker image registry | `""` | +| `global.imagePullSecrets` | Global Docker registry secret names as an array | `[]` | +| `global.defaultStorageClass` | Global default StorageClass for Persistent Volume(s) | `""` | +| `global.storageClass` | DEPRECATED: use global.defaultStorageClass instead | `""` | +| `global.compatibility.openshift.adaptSecurityContext` | Adapt the securityContext sections of the deployment to make them compatible with Openshift restricted-v2 SCC: remove runAsUser, runAsGroup and fsGroup and let the platform use their allowed default IDs. Possible values: auto (apply if the detected running cluster is Openshift), force (perform the adaptation always), disabled (do not perform adaptation) | `auto` | + +### Common parameters + +| Name | Description | Value | +| ------------------------ | --------------------------------------------------------------------------------------- | --------------- | +| `kubeVersion` | Force target Kubernetes version (using Helm capabilities if not set) | `""` | +| `nameOverride` | String to partially override common.names.fullname | `""` | +| `fullnameOverride` | String to fully override common.names.fullname | `""` | +| `namespaceOverride` | String to fully override common.names.namespace | `""` | +| `commonLabels` | Labels to add to all deployed objects | `{}` | +| `enableServiceLinks` | If set to false, disable Kubernetes service links in the pod spec | `true` | +| `commonAnnotations` | Annotations to add to all deployed objects | `{}` | +| `dnsPolicy` | DNS Policy for pod | `""` | +| `dnsConfig` | DNS Configuration pod | `{}` | +| `clusterDomain` | Default Kubernetes cluster domain | `cluster.local` | +| `extraDeploy` | Array of extra objects to deploy with the release | `[]` | +| `diagnosticMode.enabled` | Enable diagnostic mode (all probes will be disabled and the command will be overridden) | `false` | +| `diagnosticMode.command` | Command to override all containers in the the statefulset | `["sleep"]` | +| `diagnosticMode.args` | Args to override all containers in the the statefulset | `["infinity"]` | + +### Keycloak parameters + +| Name | Description | Value | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | +| `image.registry` | Keycloak image registry | `REGISTRY_NAME` | +| `image.repository` | Keycloak image repository | `REPOSITORY_NAME/keycloak` | +| `image.digest` | Keycloak image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `image.pullPolicy` | Keycloak image pull policy | `IfNotPresent` | +| `image.pullSecrets` | Specify docker-registry secret names as an array | `[]` | +| `image.debug` | Specify if debug logs should be enabled | `false` | +| `auth.adminUser` | Keycloak administrator user | `user` | +| `auth.adminPassword` | Keycloak administrator password for the new user | `""` | +| `auth.existingSecret` | Existing secret containing Keycloak admin password | `""` | +| `auth.passwordSecretKey` | Key where the Keycloak admin password is being stored inside the existing secret. | `""` | +| `auth.annotations` | Additional custom annotations for Keycloak auth secret object | `{}` | +| `customCaExistingSecret` | Name of the secret containing the Keycloak custom CA certificates. The secret will be mounted as a directory and configured using KC_TRUSTSTORE_PATHS. | `""` | +| `tls.enabled` | Enable TLS encryption. Required for HTTPs traffic. | `false` | +| `tls.autoGenerated` | Generate automatically self-signed TLS certificates. Currently only supports PEM certificates | `false` | +| `tls.existingSecret` | Existing secret containing the TLS certificates per Keycloak replica | `""` | +| `tls.usePem` | Use PEM certificates as input instead of PKS12/JKS stores | `false` | +| `tls.truststoreFilename` | Truststore filename inside the existing secret | `keycloak.truststore.jks` | +| `tls.keystoreFilename` | Keystore filename inside the existing secret | `keycloak.keystore.jks` | +| `tls.keystorePassword` | Password to access the keystore when it's password-protected | `""` | +| `tls.truststorePassword` | Password to access the truststore when it's password-protected | `""` | +| `tls.passwordsSecret` | Secret containing the Keystore and Truststore passwords. | `""` | +| `spi.existingSecret` | Existing secret containing the Keycloak truststore for SPI connection over HTTPS/TLS | `""` | +| `spi.truststorePassword` | Password to access the truststore when it's password-protected | `""` | +| `spi.truststoreFilename` | Truststore filename inside the existing secret | `keycloak-spi.truststore.jks` | +| `spi.passwordsSecret` | Secret containing the SPI Truststore passwords. | `""` | +| `spi.hostnameVerificationPolicy` | Verify the hostname of the server's certificate. Allowed values: "ANY", "WILDCARD", "STRICT". | `""` | +| `adminRealm` | Name of the admin realm | `master` | +| `production` | Run Keycloak in production mode. TLS configuration is required except when using proxy=edge. | `false` | +| `proxyHeaders` | Set Keycloak proxy headers | `""` | +| `proxy` | reverse Proxy mode edge, reencrypt, passthrough or none | `""` | +| `httpRelativePath` | Set the path relative to '/' for serving resources. Useful if you are migrating from older version which were using '/auth/' | `/` | +| `configuration` | Keycloak Configuration. Auto-generated based on other parameters when not specified | `""` | +| `existingConfigmap` | Name of existing ConfigMap with Keycloak configuration | `""` | +| `extraStartupArgs` | Extra default startup args | `""` | +| `enableDefaultInitContainers` | Deploy default init containers | `true` | +| `initdbScripts` | Dictionary of initdb scripts | `{}` | +| `initdbScriptsConfigMap` | ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`) | `""` | +| `command` | Override default container command (useful when using custom images) | `[]` | +| `args` | Override default container args (useful when using custom images) | `[]` | +| `extraEnvVars` | Extra environment variables to be set on Keycloak container | `[]` | +| `extraEnvVarsCM` | Name of existing ConfigMap containing extra env vars | `""` | +| `extraEnvVarsSecret` | Name of existing Secret containing extra env vars | `""` | + +### Keycloak statefulset parameters + +| Name | Description | Value | +| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | +| `replicaCount` | Number of Keycloak replicas to deploy | `1` | +| `revisionHistoryLimitCount` | Number of controller revisions to keep | `10` | +| `containerPorts.http` | Keycloak HTTP container port | `8080` | +| `containerPorts.https` | Keycloak HTTPS container port | `8443` | +| `containerPorts.metrics` | Keycloak metrics container port | `9000` | +| `extraContainerPorts` | Optionally specify extra list of additional port-mappings for Keycloak container | `[]` | +| `statefulsetAnnotations` | Optionally add extra annotations on the statefulset resource | `{}` | +| `podSecurityContext.enabled` | Enabled Keycloak pods' Security Context | `true` | +| `podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy | `Always` | +| `podSecurityContext.sysctls` | Set kernel settings using the sysctl interface | `[]` | +| `podSecurityContext.supplementalGroups` | Set filesystem extra groups | `[]` | +| `podSecurityContext.fsGroup` | Set Keycloak pod's Security Context fsGroup | `1001` | +| `containerSecurityContext.enabled` | Enabled containers' Security Context | `true` | +| `containerSecurityContext.seLinuxOptions` | Set SELinux options in container | `{}` | +| `containerSecurityContext.runAsUser` | Set containers' Security Context runAsUser | `1001` | +| `containerSecurityContext.runAsGroup` | Set containers' Security Context runAsGroup | `1001` | +| `containerSecurityContext.runAsNonRoot` | Set container's Security Context runAsNonRoot | `true` | +| `containerSecurityContext.privileged` | Set container's Security Context privileged | `false` | +| `containerSecurityContext.readOnlyRootFilesystem` | Set container's Security Context readOnlyRootFilesystem | `true` | +| `containerSecurityContext.allowPrivilegeEscalation` | Set container's Security Context allowPrivilegeEscalation | `false` | +| `containerSecurityContext.capabilities.drop` | List of capabilities to be dropped | `["ALL"]` | +| `containerSecurityContext.seccompProfile.type` | Set container's Security Context seccomp profile | `RuntimeDefault` | +| `resourcesPreset` | Set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). This is ignored if resources is set (resources is recommended for production). | `small` | +| `resources` | Set container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` | +| `livenessProbe.enabled` | Enable livenessProbe on Keycloak containers | `true` | +| `livenessProbe.initialDelaySeconds` | Initial delay seconds for livenessProbe | `300` | +| `livenessProbe.periodSeconds` | Period seconds for livenessProbe | `1` | +| `livenessProbe.timeoutSeconds` | Timeout seconds for livenessProbe | `5` | +| `livenessProbe.failureThreshold` | Failure threshold for livenessProbe | `3` | +| `livenessProbe.successThreshold` | Success threshold for livenessProbe | `1` | +| `readinessProbe.enabled` | Enable readinessProbe on Keycloak containers | `true` | +| `readinessProbe.initialDelaySeconds` | Initial delay seconds for readinessProbe | `30` | +| `readinessProbe.periodSeconds` | Period seconds for readinessProbe | `10` | +| `readinessProbe.timeoutSeconds` | Timeout seconds for readinessProbe | `1` | +| `readinessProbe.failureThreshold` | Failure threshold for readinessProbe | `3` | +| `readinessProbe.successThreshold` | Success threshold for readinessProbe | `1` | +| `startupProbe.enabled` | Enable startupProbe on Keycloak containers | `false` | +| `startupProbe.initialDelaySeconds` | Initial delay seconds for startupProbe | `30` | +| `startupProbe.periodSeconds` | Period seconds for startupProbe | `5` | +| `startupProbe.timeoutSeconds` | Timeout seconds for startupProbe | `1` | +| `startupProbe.failureThreshold` | Failure threshold for startupProbe | `60` | +| `startupProbe.successThreshold` | Success threshold for startupProbe | `1` | +| `customLivenessProbe` | Custom Liveness probes for Keycloak | `{}` | +| `customReadinessProbe` | Custom Rediness probes Keycloak | `{}` | +| `customStartupProbe` | Custom Startup probes for Keycloak | `{}` | +| `lifecycleHooks` | LifecycleHooks to set additional configuration at startup | `{}` | +| `automountServiceAccountToken` | Mount Service Account token in pod | `true` | +| `hostAliases` | Deployment pod host aliases | `[]` | +| `podLabels` | Extra labels for Keycloak pods | `{}` | +| `podAnnotations` | Annotations for Keycloak pods | `{}` | +| `podAffinityPreset` | Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `podAntiAffinityPreset` | Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `soft` | +| `nodeAffinityPreset.type` | Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` | `""` | +| `nodeAffinityPreset.key` | Node label key to match. Ignored if `affinity` is set. | `""` | +| `nodeAffinityPreset.values` | Node label values to match. Ignored if `affinity` is set. | `[]` | +| `affinity` | Affinity for pod assignment | `{}` | +| `nodeSelector` | Node labels for pod assignment | `{}` | +| `tolerations` | Tolerations for pod assignment | `[]` | +| `topologySpreadConstraints` | Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template | `[]` | +| `podManagementPolicy` | Pod management policy for the Keycloak statefulset | `Parallel` | +| `priorityClassName` | Keycloak pods' Priority Class Name | `""` | +| `schedulerName` | Use an alternate scheduler, e.g. "stork". | `""` | +| `terminationGracePeriodSeconds` | Seconds Keycloak pod needs to terminate gracefully | `""` | +| `updateStrategy.type` | Keycloak statefulset strategy type | `RollingUpdate` | +| `updateStrategy.rollingUpdate` | Keycloak statefulset rolling update configuration parameters | `{}` | +| `minReadySeconds` | How many seconds a pod needs to be ready before killing the next, during update | `0` | +| `extraVolumes` | Optionally specify extra list of additional volumes for Keycloak pods | `[]` | +| `extraVolumeMounts` | Optionally specify extra list of additional volumeMounts for Keycloak container(s) | `[]` | +| `initContainers` | Add additional init containers to the Keycloak pods | `[]` | +| `sidecars` | Add additional sidecar containers to the Keycloak pods | `[]` | + +### Exposure parameters + +| Name | Description | Value | +| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | +| `service.type` | Kubernetes service type | `ClusterIP` | +| `service.http.enabled` | Enable http port on service | `true` | +| `service.ports.http` | Keycloak service HTTP port | `80` | +| `service.ports.https` | Keycloak service HTTPS port | `443` | +| `service.nodePorts` | Specify the nodePort values for the LoadBalancer and NodePort service types. | `{}` | +| `service.sessionAffinity` | Control where client requests go, to the same pod or round-robin | `None` | +| `service.sessionAffinityConfig` | Additional settings for the sessionAffinity | `{}` | +| `service.clusterIP` | Keycloak service clusterIP IP | `""` | +| `service.loadBalancerIP` | loadBalancerIP for the SuiteCRM Service (optional, cloud specific) | `""` | +| `service.loadBalancerSourceRanges` | Address that are allowed when service is LoadBalancer | `[]` | +| `service.externalTrafficPolicy` | Enable client source IP preservation | `Cluster` | +| `service.annotations` | Additional custom annotations for Keycloak service | `{}` | +| `service.extraPorts` | Extra port to expose on Keycloak service | `[]` | +| `service.extraHeadlessPorts` | Extra ports to expose on Keycloak headless service | `[]` | +| `service.headless.annotations` | Annotations for the headless service. | `{}` | +| `service.headless.extraPorts` | Extra ports to expose on Keycloak headless service | `[]` | +| `ingress.enabled` | Enable ingress record generation for Keycloak | `false` | +| `ingress.ingressClassName` | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) | `""` | +| `ingress.pathType` | Ingress path type | `ImplementationSpecific` | +| `ingress.apiVersion` | Force Ingress API version (automatically detected if not set) | `""` | +| `ingress.controller` | The ingress controller type. Currently supports `default` and `gce` | `default` | +| `ingress.hostname` | Default host for the ingress record (evaluated as template) | `keycloak.local` | +| `ingress.hostnameStrict` | Disables dynamically resolving the hostname from request headers. | `false` | +| `ingress.path` | Default path for the ingress record (evaluated as template) | `""` | +| `ingress.servicePort` | Backend service port to use | `http` | +| `ingress.annotations` | Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. | `{}` | +| `ingress.labels` | Additional labels for the Ingress resource. | `{}` | +| `ingress.tls` | Enable TLS configuration for the host defined at `ingress.hostname` parameter | `false` | +| `ingress.selfSigned` | Create a TLS secret for this ingress record using self-signed certificates generated by Helm | `false` | +| `ingress.extraHosts` | An array with additional hostname(s) to be covered with the ingress record | `[]` | +| `ingress.extraPaths` | Any additional arbitrary paths that may need to be added to the ingress under the main host. | `[]` | +| `ingress.extraTls` | The tls configuration for additional hostnames to be covered with this ingress record. | `[]` | +| `ingress.secrets` | If you're providing your own certificates, please use this to add the certificates as secrets | `[]` | +| `ingress.extraRules` | Additional rules to be covered with this ingress record | `[]` | +| `adminIngress.enabled` | Enable admin ingress record generation for Keycloak | `false` | +| `adminIngress.ingressClassName` | IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) | `""` | +| `adminIngress.pathType` | Ingress path type | `ImplementationSpecific` | +| `adminIngress.apiVersion` | Force Ingress API version (automatically detected if not set) | `""` | +| `adminIngress.controller` | The ingress controller type. Currently supports `default` and `gce` | `default` | +| `adminIngress.hostname` | Default host for the admin ingress record (evaluated as template) | `keycloak.local` | +| `adminIngress.path` | Default path for the admin ingress record (evaluated as template) | `""` | +| `adminIngress.servicePort` | Backend service port to use | `http` | +| `adminIngress.annotations` | Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. | `{}` | +| `adminIngress.labels` | Additional labels for the Ingress resource. | `{}` | +| `adminIngress.tls` | Enable TLS configuration for the host defined at `adminIngress.hostname` parameter | `false` | +| `adminIngress.selfSigned` | Create a TLS secret for this ingress record using self-signed certificates generated by Helm | `false` | +| `adminIngress.extraHosts` | An array with additional hostname(s) to be covered with the admin ingress record | `[]` | +| `adminIngress.extraPaths` | Any additional arbitrary paths that may need to be added to the admin ingress under the main host. | `[]` | +| `adminIngress.extraTls` | The tls configuration for additional hostnames to be covered with this ingress record. | `[]` | +| `adminIngress.secrets` | If you're providing your own certificates, please use this to add the certificates as secrets | `[]` | +| `adminIngress.extraRules` | Additional rules to be covered with this ingress record | `[]` | +| `networkPolicy.enabled` | Specifies whether a NetworkPolicy should be created | `true` | +| `networkPolicy.allowExternal` | Don't require server label for connections | `true` | +| `networkPolicy.allowExternalEgress` | Allow the pod to access any range of port and all destinations. | `true` | +| `networkPolicy.kubeAPIServerPorts` | List of possible endpoints to kube-apiserver (limit to your cluster settings to increase security) | `[]` | +| `networkPolicy.extraIngress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `networkPolicy.extraEgress` | Add extra ingress rules to the NetworkPolicy | `[]` | +| `networkPolicy.ingressNSMatchLabels` | Labels to match to allow traffic from other namespaces | `{}` | +| `networkPolicy.ingressNSPodMatchLabels` | Pod labels to match to allow traffic from other namespaces | `{}` | + +### RBAC parameter + +| Name | Description | Value | +| --------------------------------------------- | --------------------------------------------------------- | ------- | +| `serviceAccount.create` | Enable the creation of a ServiceAccount for Keycloak pods | `true` | +| `serviceAccount.name` | Name of the created ServiceAccount | `""` | +| `serviceAccount.automountServiceAccountToken` | Auto-mount the service account token in the pod | `false` | +| `serviceAccount.annotations` | Additional custom annotations for the ServiceAccount | `{}` | +| `serviceAccount.extraLabels` | Additional labels for the ServiceAccount | `{}` | +| `rbac.create` | Whether to create and use RBAC resources or not | `false` | +| `rbac.rules` | Custom RBAC rules | `[]` | + +### Other parameters + +| Name | Description | Value | +| ----------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------- | +| `pdb.create` | Enable/disable a Pod Disruption Budget creation | `true` | +| `pdb.minAvailable` | Minimum number/percentage of pods that should remain scheduled | `""` | +| `pdb.maxUnavailable` | Maximum number/percentage of pods that may be made unavailable | `""` | +| `autoscaling.enabled` | Enable autoscaling for Keycloak | `false` | +| `autoscaling.minReplicas` | Minimum number of Keycloak replicas | `1` | +| `autoscaling.maxReplicas` | Maximum number of Keycloak replicas | `11` | +| `autoscaling.targetCPU` | Target CPU utilization percentage | `""` | +| `autoscaling.targetMemory` | Target Memory utilization percentage | `""` | +| `autoscaling.behavior.scaleUp.stabilizationWindowSeconds` | The number of seconds for which past recommendations should be considered while scaling up | `120` | +| `autoscaling.behavior.scaleUp.selectPolicy` | The priority of policies that the autoscaler will apply when scaling up | `Max` | +| `autoscaling.behavior.scaleUp.policies` | HPA scaling policies when scaling up | `[]` | +| `autoscaling.behavior.scaleDown.stabilizationWindowSeconds` | The number of seconds for which past recommendations should be considered while scaling down | `300` | +| `autoscaling.behavior.scaleDown.selectPolicy` | The priority of policies that the autoscaler will apply when scaling down | `Max` | +| `autoscaling.behavior.scaleDown.policies` | HPA scaling policies when scaling down | `[]` | + +### Metrics parameters + +| Name | Description | Value | +| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | +| `metrics.enabled` | Enable exposing Keycloak statistics | `false` | +| `metrics.service.ports.http` | Metrics service HTTP port | `8080` | +| `metrics.service.ports.https` | Metrics service HTTPS port | `8443` | +| `metrics.service.ports.metrics` | Metrics service Metrics port | `9000` | +| `metrics.service.annotations` | Annotations for enabling prometheus to access the metrics endpoints | `{}` | +| `metrics.service.extraPorts` | Add additional ports to the keycloak metrics service (i.e. admin port 9000) | `[]` | +| `metrics.serviceMonitor.enabled` | Create ServiceMonitor Resource for scraping metrics using PrometheusOperator | `false` | +| `metrics.serviceMonitor.port` | Metrics service HTTP port | `metrics` | +| `metrics.serviceMonitor.scheme` | Metrics service scheme | `http` | +| `metrics.serviceMonitor.tlsConfig` | Metrics service TLS configuration | `{}` | +| `metrics.serviceMonitor.endpoints` | The endpoint configuration of the ServiceMonitor. Path is mandatory. Port, scheme, tlsConfig, interval, timeout and labellings can be overwritten. | `[]` | +| `metrics.serviceMonitor.path` | Metrics service HTTP path. Deprecated: Use @param metrics.serviceMonitor.endpoints instead | `""` | +| `metrics.serviceMonitor.namespace` | Namespace which Prometheus is running in | `""` | +| `metrics.serviceMonitor.interval` | Interval at which metrics should be scraped | `30s` | +| `metrics.serviceMonitor.scrapeTimeout` | Specify the timeout after which the scrape is ended | `""` | +| `metrics.serviceMonitor.labels` | Additional labels that can be used so ServiceMonitor will be discovered by Prometheus | `{}` | +| `metrics.serviceMonitor.selector` | Prometheus instance selector labels | `{}` | +| `metrics.serviceMonitor.relabelings` | RelabelConfigs to apply to samples before scraping | `[]` | +| `metrics.serviceMonitor.metricRelabelings` | MetricRelabelConfigs to apply to samples before ingestion | `[]` | +| `metrics.serviceMonitor.honorLabels` | honorLabels chooses the metric's labels on collisions with target labels | `false` | +| `metrics.serviceMonitor.jobLabel` | The name of the label on the target service to use as the job name in prometheus. | `""` | +| `metrics.prometheusRule.enabled` | Create PrometheusRule Resource for scraping metrics using PrometheusOperator | `false` | +| `metrics.prometheusRule.namespace` | Namespace which Prometheus is running in | `""` | +| `metrics.prometheusRule.labels` | Additional labels that can be used so PrometheusRule will be discovered by Prometheus | `{}` | +| `metrics.prometheusRule.groups` | Groups, containing the alert rules. | `[]` | + +### keycloak-config-cli parameters + +| Name | Description | Value | +| --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | +| `keycloakConfigCli.enabled` | Whether to enable keycloak-config-cli job | `false` | +| `keycloakConfigCli.image.registry` | keycloak-config-cli container image registry | `REGISTRY_NAME` | +| `keycloakConfigCli.image.repository` | keycloak-config-cli container image repository | `REPOSITORY_NAME/keycloak-config-cli` | +| `keycloakConfigCli.image.digest` | keycloak-config-cli container image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag | `""` | +| `keycloakConfigCli.image.pullPolicy` | keycloak-config-cli container image pull policy | `IfNotPresent` | +| `keycloakConfigCli.image.pullSecrets` | keycloak-config-cli container image pull secrets | `[]` | +| `keycloakConfigCli.annotations` | Annotations for keycloak-config-cli job | `{}` | +| `keycloakConfigCli.command` | Command for running the container (set to default if not set). Use array form | `[]` | +| `keycloakConfigCli.args` | Args for running the container (set to default if not set). Use array form | `[]` | +| `keycloakConfigCli.automountServiceAccountToken` | Mount Service Account token in pod | `true` | +| `keycloakConfigCli.hostAliases` | Job pod host aliases | `[]` | +| `keycloakConfigCli.resourcesPreset` | Set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). This is ignored if keycloakConfigCli.resources is set (keycloakConfigCli.resources is recommended for production). | `small` | +| `keycloakConfigCli.resources` | Set container requests and limits for different resources like CPU or memory (essential for production workloads) | `{}` | +| `keycloakConfigCli.containerSecurityContext.enabled` | Enabled keycloak-config-cli Security Context | `true` | +| `keycloakConfigCli.containerSecurityContext.seLinuxOptions` | Set SELinux options in container | `{}` | +| `keycloakConfigCli.containerSecurityContext.runAsUser` | Set keycloak-config-cli Security Context runAsUser | `1001` | +| `keycloakConfigCli.containerSecurityContext.runAsGroup` | Set keycloak-config-cli Security Context runAsGroup | `1001` | +| `keycloakConfigCli.containerSecurityContext.runAsNonRoot` | Set keycloak-config-cli Security Context runAsNonRoot | `true` | +| `keycloakConfigCli.containerSecurityContext.privileged` | Set keycloak-config-cli Security Context privileged | `false` | +| `keycloakConfigCli.containerSecurityContext.readOnlyRootFilesystem` | Set keycloak-config-cli Security Context readOnlyRootFilesystem | `true` | +| `keycloakConfigCli.containerSecurityContext.allowPrivilegeEscalation` | Set keycloak-config-cli Security Context allowPrivilegeEscalation | `false` | +| `keycloakConfigCli.containerSecurityContext.capabilities.drop` | List of capabilities to be dropped | `["ALL"]` | +| `keycloakConfigCli.containerSecurityContext.seccompProfile.type` | Set keycloak-config-cli Security Context seccomp profile | `RuntimeDefault` | +| `keycloakConfigCli.podSecurityContext.enabled` | Enabled keycloak-config-cli pods' Security Context | `true` | +| `keycloakConfigCli.podSecurityContext.fsGroupChangePolicy` | Set filesystem group change policy | `Always` | +| `keycloakConfigCli.podSecurityContext.sysctls` | Set kernel settings using the sysctl interface | `[]` | +| `keycloakConfigCli.podSecurityContext.supplementalGroups` | Set filesystem extra groups | `[]` | +| `keycloakConfigCli.podSecurityContext.fsGroup` | Set keycloak-config-cli pod's Security Context fsGroup | `1001` | +| `keycloakConfigCli.backoffLimit` | Number of retries before considering a Job as failed | `1` | +| `keycloakConfigCli.podLabels` | Pod extra labels | `{}` | +| `keycloakConfigCli.podAnnotations` | Annotations for job pod | `{}` | +| `keycloakConfigCli.extraEnvVars` | Additional environment variables to set | `[]` | +| `keycloakConfigCli.nodeSelector` | Node labels for pod assignment | `{}` | +| `keycloakConfigCli.podTolerations` | Tolerations for job pod assignment | `[]` | +| `keycloakConfigCli.extraEnvVarsCM` | ConfigMap with extra environment variables | `""` | +| `keycloakConfigCli.extraEnvVarsSecret` | Secret with extra environment variables | `""` | +| `keycloakConfigCli.extraVolumes` | Extra volumes to add to the job | `[]` | +| `keycloakConfigCli.extraVolumeMounts` | Extra volume mounts to add to the container | `[]` | +| `keycloakConfigCli.initContainers` | Add additional init containers to the Keycloak config cli pod | `[]` | +| `keycloakConfigCli.sidecars` | Add additional sidecar containers to the Keycloak config cli pod | `[]` | +| `keycloakConfigCli.configuration` | keycloak-config-cli realms configuration | `{}` | +| `keycloakConfigCli.existingConfigmap` | ConfigMap with keycloak-config-cli configuration | `""` | +| `keycloakConfigCli.cleanupAfterFinished.enabled` | Enables Cleanup for Finished Jobs | `false` | +| `keycloakConfigCli.cleanupAfterFinished.seconds` | Sets the value of ttlSecondsAfterFinished | `600` | + +### Database parameters + +| Name | Description | Value | +| -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------ | +| `postgresql.enabled` | Switch to enable or disable the PostgreSQL helm chart | `true` | +| `postgresql.auth.postgresPassword` | Password for the "postgres" admin user. Ignored if `auth.existingSecret` with key `postgres-password` is provided | `""` | +| `postgresql.auth.username` | Name for a custom user to create | `bn_keycloak` | +| `postgresql.auth.password` | Password for the custom user to create | `""` | +| `postgresql.auth.database` | Name for a custom database to create | `bitnami_keycloak` | +| `postgresql.auth.existingSecret` | Name of existing secret to use for PostgreSQL credentials | `""` | +| `postgresql.auth.secretKeys.userPasswordKey` | Name of key in existing secret to use for PostgreSQL credentials. Only used when `auth.existingSecret` is set. | `password` | +| `postgresql.architecture` | PostgreSQL architecture (`standalone` or `replication`) | `standalone` | +| `externalDatabase.host` | Database host | `""` | +| `externalDatabase.port` | Database port number | `5432` | +| `externalDatabase.user` | Non-root username for Keycloak | `bn_keycloak` | +| `externalDatabase.password` | Password for the non-root username for Keycloak | `""` | +| `externalDatabase.database` | Keycloak database name | `bitnami_keycloak` | +| `externalDatabase.existingSecret` | Name of an existing secret resource containing the database credentials | `""` | +| `externalDatabase.existingSecretHostKey` | Name of an existing secret key containing the database host name | `""` | +| `externalDatabase.existingSecretPortKey` | Name of an existing secret key containing the database port | `""` | +| `externalDatabase.existingSecretUserKey` | Name of an existing secret key containing the database user | `""` | +| `externalDatabase.existingSecretDatabaseKey` | Name of an existing secret key containing the database name | `""` | +| `externalDatabase.existingSecretPasswordKey` | Name of an existing secret key containing the database credentials | `""` | +| `externalDatabase.annotations` | Additional custom annotations for external database secret object | `{}` | + +### Keycloak Cache parameters + +| Name | Description | Value | +| ----------------- | -------------------------------------------------------------------------- | ------------ | +| `cache.enabled` | Switch to enable or disable the keycloak distributed cache for kubernetes. | `true` | +| `cache.stackName` | Set infinispan cache stack to use | `kubernetes` | +| `cache.stackFile` | Set infinispan cache stack filename to use | `""` | + +### Keycloak Logging parameters + +| Name | Description | Value | +| ---------------- | ------------------------------------------------------------------------------ | --------- | +| `logging.output` | Alternates between the default log output format or json format | `default` | +| `logging.level` | Allowed values as documented: FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL, OFF | `INFO` | + +Specify each parameter using the `--set key=value[,key=value]` argument to `helm install`. For example, + +```console +helm install my-release --set auth.adminPassword=secretpassword oci://REGISTRY_NAME/REPOSITORY_NAME/keycloak +``` + +> Note: You need to substitute the placeholders `REGISTRY_NAME` and `REPOSITORY_NAME` with a reference to your Helm chart registry and repository. For example, in the case of Bitnami, you need to use `REGISTRY_NAME=registry-1.docker.io` and `REPOSITORY_NAME=bitnamicharts`. + +The above command sets the Keycloak administrator password to `secretpassword`. + +> NOTE: Once this chart is deployed, it is not possible to change the application's access credentials, such as usernames or passwords, using Helm. To change these application credentials after deployment, delete any persistent volumes (PVs) used by the chart and re-deploy it, or use the application's built-in administrative tools if available. + +Alternatively, a YAML file that specifies the values for the parameters can be provided while installing the chart. For example, + +```console +helm install my-release -f values.yaml oci://REGISTRY_NAME/REPOSITORY_NAME/keycloak +``` + +> Note: You need to substitute the placeholders `REGISTRY_NAME` and `REPOSITORY_NAME` with a reference to your Helm chart registry and repository. For example, in the case of Bitnami, you need to use `REGISTRY_NAME=registry-1.docker.io` and `REPOSITORY_NAME=bitnamicharts`. +> **Tip**: You can use the default [values.yaml](https://github.com/bitnami/charts/tree/main/bitnami/keycloak/values.yaml) + +Keycloak realms, users and clients can be created from the Keycloak administration panel. + +## Troubleshooting + +Find more information about how to deal with common errors related to Bitnami's Helm charts in [this troubleshooting guide](https://docs.bitnami.com/general/how-to/troubleshoot-helm-chart-issues). + +## Upgrading + +### To 24.1.0 + +With this update the metrics service listening port is switched to 9000, the same as the keycloak management endpoint is using. +This can be changed by setting `metrics.service.ports.http` to a different value, e.g. 8080 like before this change. + +### To 23.0.0 + +This major updates the PostgreSQL subchart to its newest major, 16.0.0, which uses PostgreSQL 17.x. Follow the [official instructions](https://www.postgresql.org/docs/17/upgrading.html) to upgrade to 17.x. + +### To 21.0.0 + +This major release updates the keycloak branch to its newest major, 24.x.x. Follow the [upstream documentation](https://www.keycloak.org/docs/latest/upgrading/index.html#migrating-to-24-0-0) for upgrade instructions. + +### To 20.0.0 + +This major bump changes the following security defaults: + +- `runAsGroup` is changed from `0` to `1001` +- `readOnlyRootFilesystem` is set to `true` +- `resourcesPreset` is changed from `none` to the minimum size working in our test suites (NOTE: `resourcesPreset` is not meant for production usage, but `resources` adapted to your use case). +- `global.compatibility.openshift.adaptSecurityContext` is changed from `disabled` to `auto`. + +This could potentially break any customization or init scripts used in your deployment. If this is the case, change the default values to the previous ones. + +### To 19.0.0 + +This major release bumps the PostgreSQL chart version to [14.x.x](https://github.com/bitnami/charts/pull/22750); no major issues are expected during the upgrade. + +### To 17.0.0 + +This major updates the PostgreSQL subchart to its newest major, 13.0.0. [Here](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#to-1300) you can find more information about the changes introduced in that version. + +### To 15.0.0 + +This major updates the default serviceType from `LoadBalancer` to `ClusterIP` to avoid inadvertently exposing Keycloak directly to the internet without an Ingress. + +### To 12.0.0 + +This major updates the PostgreSQL subchart to its newest major, 12.0.0. [Here](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#to-1200) you can find more information about the changes introduced in that version. + +### To 10.0.0 + +This major release updates Keycloak to its major version `19`. Please, refer to the official [Keycloak migration documentation](https://www.keycloak.org/docs/latest/upgrading/index.html#migrating-to-19-0-0) for a complete list of changes and further information. + +### To 9.0.0 + +This major release updates Keycloak to its major version `18`. Please, refer to the official [Keycloak migration documentation](https://www.keycloak.org/docs/latest/upgrading/index.html#migrating-to-18-0-0) for a complete list of changes and further information. + +### To 8.0.0 + +This major release updates Keycloak to its major version `17`. Among other features, this new version has deprecated WildFly in favor of Quarkus, which introduces breaking changes like: + +- Removal of `/auth` from the default context path. +- Changes in the configuration and deployment of custom providers. +- Significant changes in configuring Keycloak. + +Please, refer to the official [Keycloak migration documentation](https://www.keycloak.org/docs/latest/upgrading/index.html#migrating-to-17-0-0) and [Migrating to Quarkus distribution document](https://www.keycloak.org/migration/migrating-to-quarkus) for a complete list of changes and further information. + +### To 7.0.0 + +This major release updates the PostgreSQL subchart to its newest major *11.x.x*, which contain several changes in the supported values (check the [upgrade notes](https://github.com/bitnami/charts/tree/master/bitnami/postgresql#to-1100) to obtain more information). + +#### Upgrading Instructions + +To upgrade to *7.0.0* from *6.x*, it should be done reusing the PVC(s) used to hold the data on your previous release. To do so, follow the instructions below (the following example assumes that the release name is *keycloak* and the release namespace *default*): + +1. Obtain the credentials and the names of the PVCs used to hold the data on your current release: + +```console +export KEYCLOAK_PASSWORD=$(kubectl get secret --namespace default keycloak -o jsonpath="{.data.admin-password}" | base64 --decode) +export POSTGRESQL_PASSWORD=$(kubectl get secret --namespace default keycloak-postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode) +export POSTGRESQL_PVC=$(kubectl get pvc -l app.kubernetes.io/instance=keycloak,app.kubernetes.io/name=postgresql,role=primary -o jsonpath="{.items[0].metadata.name}") +``` + +1. Delete the PostgreSQL statefulset (notice the option *--cascade=false*) and secret: + +```console +kubectl delete statefulsets.apps --cascade=false keycloak-postgresql +kubectl delete secret keycloak-postgresql --namespace default +``` + +1. Upgrade your release using the same PostgreSQL version: + +```console +CURRENT_PG_VERSION=$(kubectl exec keycloak-postgresql-0 -- bash -c 'echo $BITNAMI_IMAGE_VERSION') +helm upgrade keycloak bitnami/keycloak \ + --set auth.adminPassword=$KEYCLOAK_PASSWORD \ + --set postgresql.image.tag=$CURRENT_PG_VERSION \ + --set postgresql.auth.password=$POSTGRESQL_PASSWORD \ + --set postgresql.persistence.existingClaim=$POSTGRESQL_PVC +``` + +1. Delete the existing PostgreSQL pods and the new statefulset will create a new one: + +```console +kubectl delete pod keycloak-postgresql-0 +``` + +### To 1.0.0 + +[On November 13, 2020, Helm v2 support was formally finished](https://github.com/helm/charts#status-of-the-project), this major version is the result of the required changes applied to the Helm Chart to be able to incorporate the different features added in Helm v3 and to be consistent with the Helm project itself regarding the Helm v2 EOL. + +#### What changes were introduced in this major version? + +- Previous versions of this Helm Chart use `apiVersion: v1` (installable by both Helm 2 and 3), this Helm Chart was updated to `apiVersion: v2` (installable by Helm 3 only). [Here](https://helm.sh/docs/topics/charts/#the-apiversion-field) you can find more information about the `apiVersion` field. +- Move dependency information from the *requirements.yaml* to the *Chart.yaml* +- After running *helm dependency update*, a *Chart.lock* file is generated containing the same structure used in the previous *requirements.lock* +- The different fields present in the *Chart.yaml* file has been ordered alphabetically in a homogeneous way for all the Bitnami Helm Chart. + +#### Considerations when upgrading to this version + +- If you want to upgrade to this version using Helm v2, this scenario is not supported as this version does not support Helm v2 anymore. +- If you installed the previous version with Helm v2 and wants to upgrade to this version with Helm v3, please refer to the [official Helm documentation](https://helm.sh/docs/topics/v2_v3_migration/#migration-use-cases) about migrating from Helm v2 to v3. + +#### Useful links + +- [Bitnami Tutorial](https://techdocs.broadcom.com/us/en/vmware-tanzu/application-catalog/tanzu-application-catalog/services/tac-doc/apps-tutorials-resolve-helm2-helm3-post-migration-issues-index.html) +- [Helm docs](https://helm.sh/docs/topics/v2_v3_migration) +- [Helm Blog](https://helm.sh/blog/migrate-from-helm-v2-to-helm-v3) + +## License + +Copyright © 2024 Broadcom. The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/charts/keycloak/keycloak/templates/NOTES.txt b/charts/keycloak/keycloak/templates/NOTES.txt new file mode 100644 index 0000000..d3ecb69 --- /dev/null +++ b/charts/keycloak/keycloak/templates/NOTES.txt @@ -0,0 +1,104 @@ +CHART NAME: {{ .Chart.Name }} +CHART VERSION: {{ .Chart.Version }} +APP VERSION: {{ .Chart.AppVersion }} + +** Please be patient while the chart is being deployed ** + +Keycloak can be accessed through the following DNS name from within your cluster: + + {{ include "common.names.fullname" . }}.{{ include "common.names.namespace" . }}.svc.{{ .Values.clusterDomain }} (port {{ coalesce .Values.service.ports.http .Values.service.port }}) + +To access Keycloak from outside the cluster execute the following commands: + +{{- if .Values.ingress.enabled }} + +1. Get the Keycloak URL and associate its hostname to your cluster external IP: + + export CLUSTER_IP=$(minikube ip) # On Minikube. Use: `kubectl cluster-info` on others K8s clusters + echo "Keycloak URL: http{{ if .Values.ingress.tls }}s{{ end }}://{{ (tpl .Values.ingress.hostname .) }}/" + echo "$CLUSTER_IP {{ (tpl .Values.ingress.hostname .) }}" | sudo tee -a /etc/hosts + +{{- if .Values.adminIngress.enabled }} +The admin area of Keycloak has been configured to point to a different domain ({{ .Values.adminIngress.hostname }}). Please remember to update the `frontendUrl` property of the `{{ .Values.adminRealm | default "master" }}` (or any other) realm for it to work properly (see README for an example) : + + echo "Keycloak admin URL: http{{ if .Values.adminIngress.tls }}s{{ end }}://{{ (tpl .Values.adminIngress.hostname .) }}/" + echo "$CLUSTER_IP {{ (tpl .Values.adminIngress.hostname .) }}" | sudo tee -a /etc/hosts +{{- end }} + +{{- else }} + +1. Get the Keycloak URL by running these commands: + +{{- if contains "NodePort" .Values.service.type }} + + export HTTP_NODE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[?(@.name=='http')].nodePort}" services {{ include "common.names.fullname" . }}) + {{- if .Values.tls.enabled }} + export HTTPS_NODE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[?(@.name=='https')].nodePort}" services {{ include "common.names.fullname" . }}) + {{- end }} + export NODE_IP=$(kubectl get nodes --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.items[0].status.addresses[0].address}") + + echo "http://${NODE_IP}:${HTTP_NODE_PORT}/" + {{- if .Values.tls.enabled }} + echo "https://${NODE_IP}:${HTTPS_NODE_PORT}/" + {{- end }} + +{{- else if contains "LoadBalancer" .Values.service.type }} + + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch its status by running 'kubectl get --namespace {{ include "common.names.namespace" . }} svc -w {{ include "common.names.fullname" . }}' + + export HTTP_SERVICE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[?(@.name=='http')].port}" services {{ include "common.names.fullname" . }}) + {{- if .Values.tls.enabled }} + export HTTPS_SERVICE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[?(@.name=='https')].port}" services {{ include "common.names.fullname" . }}) + {{- end }} + export SERVICE_IP=$(kubectl get svc --namespace {{ include "common.names.namespace" . }} {{ include "common.names.fullname" . }} -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + + echo "http://${SERVICE_IP}:${HTTP_SERVICE_PORT}/" + {{- if .Values.tls.enabled }} + echo "https://${SERVICE_IP}:${HTTPS_SERVICE_PORT}/" + {{- end }} + +{{- else if contains "ClusterIP" .Values.service.type }} + + export HTTP_SERVICE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[?(@.name=='http')].port}" services {{ include "common.names.fullname" . }}) + {{- if .Values.tls.enabled }} + export HTTPS_SERVICE_PORT=$(kubectl get --namespace {{ include "common.names.namespace" . }} -o jsonpath="{.spec.ports[?(@.name=='https')].port}" services {{ include "common.names.fullname" . }}) + kubectl port-forward --namespace {{ include "common.names.namespace" . }} svc/{{ include "common.names.fullname" . }} ${HTTP_SERVICE_PORT}:${HTTP_SERVICE_PORT} ${HTTPS_SERVICE_PORT}:${HTTPS_SERVICE_PORT} & + {{- else }} + kubectl port-forward --namespace {{ include "common.names.namespace" . }} svc/{{ include "common.names.fullname" . }} ${HTTP_SERVICE_PORT}:${HTTP_SERVICE_PORT} & + {{- end }} + + echo "http://127.0.0.1:${HTTP_SERVICE_PORT}/" + {{- if .Values.tls.enabled }} + echo "https://127.0.0.1:${HTTPS_SERVICE_PORT}/" + {{- end }} + +{{- end }} +{{- end }} + +2. Access Keycloak using the obtained URL. +{{- if and .Values.auth.adminUser .Values.auth.adminPassword }} +3. Access the Administration Console using the following credentials: + + echo Username: {{ .Values.auth.adminUser }} + echo Password: $(kubectl get secret --namespace {{ include "common.names.namespace" . }} {{ include "keycloak.secretName" . }} -o jsonpath="{.data.{{ include "keycloak.secretKey" .}}}" | base64 -d) +{{- end }} +{{- if .Values.metrics.enabled }} + +You can access the Prometheus metrics following the steps below: + +1. Get the Keycloak Prometheus metrics URL by running: + + {{- $metricsPort := coalesce .Values.metrics.service.ports.metrics .Values.metrics.service.port | toString }} + kubectl port-forward --namespace {{ include "common.names.namespace" . }} svc/{{ printf "%s-metrics" (include "common.names.fullname" .) }} {{ $metricsPort }}:{{ $metricsPort }} & + echo "Keycloak Prometheus metrics URL: http://127.0.0.1:{{ $metricsPort }}/metrics" + +2. Open a browser and access Keycloak Prometheus metrics using the obtained URL. + +{{- end }} + +{{- include "keycloak.validateValues" . }} +{{- include "common.warnings.rollingTag" .Values.image }} +{{- include "common.warnings.rollingTag" .Values.keycloakConfigCli.image }} +{{- include "common.warnings.resources" (dict "sections" (list "keycloakConfigCli" "") "context" $) }} +{{- include "common.warnings.modifiedImages" (dict "images" (list .Values.image .Values.keycloakConfigCli.image) "context" $) }} \ No newline at end of file diff --git a/charts/keycloak/keycloak/templates/_helpers.tpl b/charts/keycloak/keycloak/templates/_helpers.tpl new file mode 100644 index 0000000..5f56e70 --- /dev/null +++ b/charts/keycloak/keycloak/templates/_helpers.tpl @@ -0,0 +1,348 @@ +{{/* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{/* +Return the proper Keycloak image name +*/}} +{{- define "keycloak.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the proper keycloak-config-cli image name +*/}} +{{- define "keycloak.keycloakConfigCli.image" -}} +{{ include "common.images.image" (dict "imageRoot" .Values.keycloakConfigCli.image "global" .Values.global) }} +{{- end -}} + +{{/* +Return the keycloak-config-cli configuration configmap. +*/}} +{{- define "keycloak.keycloakConfigCli.configmapName" -}} +{{- if .Values.keycloakConfigCli.existingConfigmap -}} + {{- printf "%s" (tpl .Values.keycloakConfigCli.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s-keycloak-config-cli-configmap" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created for keycloak-config-cli +*/}} +{{- define "keycloak.keycloakConfigCli.createConfigmap" -}} +{{- if and .Values.keycloakConfigCli.enabled .Values.keycloakConfigCli.configuration (not .Values.keycloakConfigCli.existingConfigmap) -}} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the proper Docker Image Registry Secret Names +*/}} +{{- define "keycloak.imagePullSecrets" -}} +{{- include "common.images.renderPullSecrets" (dict "images" (list .Values.image .Values.keycloakConfigCli.image) "context" $) -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "keycloak.postgresql.fullname" -}} +{{- include "common.names.dependency.fullname" (dict "chartName" "postgresql" "chartValues" .Values.postgresql "context" $) -}} +{{- end -}} + +{{/* +Create the name of the service account to use +*/}} +{{- define "keycloak.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "common.names.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Return the path Keycloak is hosted on. This looks at httpRelativePath and returns it with a trailing slash. For example: + / -> / (the default httpRelativePath) + /auth -> /auth/ (trailing slash added) + /custom/ -> /custom/ (unchanged) +*/}} +{{- define "keycloak.httpPath" -}} +{{ ternary .Values.httpRelativePath (printf "%s%s" .Values.httpRelativePath "/") (hasSuffix "/" .Values.httpRelativePath) }} +{{- end -}} + +{{/* +Return the Keycloak configuration configmap +*/}} +{{- define "keycloak.configmapName" -}} +{{- if .Values.existingConfigmap -}} + {{- printf "%s" (tpl .Values.existingConfigmap $) -}} +{{- else -}} + {{- printf "%s-configuration" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a configmap object should be created +*/}} +{{- define "keycloak.createConfigmap" -}} +{{- if and .Values.configuration (not .Values.existingConfigmap) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Return the Database hostname +*/}} +{{- define "keycloak.databaseHost" -}} +{{- if eq .Values.postgresql.architecture "replication" }} +{{- ternary (include "keycloak.postgresql.fullname" .) (tpl .Values.externalDatabase.host $) .Values.postgresql.enabled -}}-primary +{{- else -}} +{{- ternary (include "keycloak.postgresql.fullname" .) (tpl .Values.externalDatabase.host $) .Values.postgresql.enabled -}} +{{- end -}} +{{- end -}} + +{{/* +Return the Database port +*/}} +{{- define "keycloak.databasePort" -}} +{{- ternary "5432" .Values.externalDatabase.port .Values.postgresql.enabled | quote -}} +{{- end -}} + +{{/* +Return the Database database name +*/}} +{{- define "keycloak.databaseName" -}} +{{- if .Values.postgresql.enabled }} + {{- if .Values.global.postgresql }} + {{- if .Values.global.postgresql.auth }} + {{- coalesce .Values.global.postgresql.auth.database .Values.postgresql.auth.database -}} + {{- else -}} + {{- .Values.postgresql.auth.database -}} + {{- end -}} + {{- else -}} + {{- .Values.postgresql.auth.database -}} + {{- end -}} +{{- else -}} + {{- .Values.externalDatabase.database -}} +{{- end -}} +{{- end -}} + +{{/* +Return the Database user +*/}} +{{- define "keycloak.databaseUser" -}} +{{- if .Values.postgresql.enabled -}} + {{- if .Values.global.postgresql -}} + {{- if .Values.global.postgresql.auth -}} + {{- coalesce .Values.global.postgresql.auth.username .Values.postgresql.auth.username -}} + {{- else -}} + {{- .Values.postgresql.auth.username -}} + {{- end -}} + {{- else -}} + {{- .Values.postgresql.auth.username -}} + {{- end -}} +{{- else -}} + {{- .Values.externalDatabase.user -}} +{{- end -}} +{{- end -}} + +{{/* +Return the Database encrypted password +*/}} +{{- define "keycloak.databaseSecretName" -}} +{{- if .Values.postgresql.enabled -}} + {{- if .Values.global.postgresql -}} + {{- if .Values.global.postgresql.auth -}} + {{- if .Values.global.postgresql.auth.existingSecret -}} + {{- tpl .Values.global.postgresql.auth.existingSecret $ -}} + {{- else -}} + {{- default (include "keycloak.postgresql.fullname" .) (tpl .Values.postgresql.auth.existingSecret $) -}} + {{- end -}} + {{- else -}} + {{- default (include "keycloak.postgresql.fullname" .) (tpl .Values.postgresql.auth.existingSecret $) -}} + {{- end -}} + {{- else -}} + {{- default (include "keycloak.postgresql.fullname" .) (tpl .Values.postgresql.auth.existingSecret $) -}} + {{- end -}} +{{- else -}} + {{- default (printf "%s-externaldb" .Release.Name) (tpl .Values.externalDatabase.existingSecret $) -}} +{{- end -}} +{{- end -}} + +{{/* +Add environment variables to configure database values +*/}} +{{- define "keycloak.databaseSecretPasswordKey" -}} +{{- if .Values.postgresql.enabled -}} + {{- printf "%s" (.Values.postgresql.auth.secretKeys.userPasswordKey | default "password") -}} +{{- else -}} + {{- if .Values.externalDatabase.existingSecret -}} + {{- if .Values.externalDatabase.existingSecretPasswordKey -}} + {{- printf "%s" .Values.externalDatabase.existingSecretPasswordKey -}} + {{- else -}} + {{- print "db-password" -}} + {{- end -}} + {{- else -}} + {{- print "db-password" -}} + {{- end -}} +{{- end -}} +{{- end -}} + +{{- define "keycloak.databaseSecretHostKey" -}} + {{- if .Values.externalDatabase.existingSecretHostKey -}} + {{- printf "%s" .Values.externalDatabase.existingSecretHostKey -}} + {{- else -}} + {{- print "db-host" -}} + {{- end -}} +{{- end -}} +{{- define "keycloak.databaseSecretPortKey" -}} + {{- if .Values.externalDatabase.existingSecretPortKey -}} + {{- printf "%s" .Values.externalDatabase.existingSecretPortKey -}} + {{- else -}} + {{- print "db-port" -}} + {{- end -}} +{{- end -}} +{{- define "keycloak.databaseSecretUserKey" -}} + {{- if .Values.externalDatabase.existingSecretUserKey -}} + {{- printf "%s" .Values.externalDatabase.existingSecretUserKey -}} + {{- else -}} + {{- print "db-user" -}} + {{- end -}} +{{- end -}} +{{- define "keycloak.databaseSecretDatabaseKey" -}} + {{- if .Values.externalDatabase.existingSecretDatabaseKey -}} + {{- printf "%s" .Values.externalDatabase.existingSecretDatabaseKey -}} + {{- else -}} + {{- print "db-database" -}} + {{- end -}} +{{- end -}} + +{{/* +Return the Keycloak initdb scripts configmap +*/}} +{{- define "keycloak.initdbScriptsCM" -}} +{{- if .Values.initdbScriptsConfigMap -}} + {{- printf "%s" .Values.initdbScriptsConfigMap -}} +{{- else -}} + {{- printf "%s-init-scripts" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing the Keycloak admin password +*/}} +{{- define "keycloak.secretName" -}} +{{- $secretName := .Values.auth.existingSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret key that contains the Keycloak admin password +*/}} +{{- define "keycloak.secretKey" -}} +{{- $secretName := .Values.auth.existingSecret -}} +{{- if and $secretName .Values.auth.passwordSecretKey -}} + {{- printf "%s" .Values.auth.passwordSecretKey -}} +{{- else -}} + {{- print "admin-password" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing Keycloak HTTPS/TLS certificates +*/}} +{{- define "keycloak.tlsSecretName" -}} +{{- $secretName := .Values.tls.existingSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-crt" (include "common.names.fullname" .) -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing Keycloak HTTPS/TLS keystore and truststore passwords +*/}} +{{- define "keycloak.tlsPasswordsSecretName" -}} +{{- $secretName := .Values.tls.passwordsSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-tls-passwords" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Return the secret containing Keycloak SPI TLS certificates +*/}} +{{- define "keycloak.spiPasswordsSecretName" -}} +{{- $secretName := .Values.spi.passwordsSecret -}} +{{- if $secretName -}} + {{- printf "%s" (tpl $secretName $) -}} +{{- else -}} + {{- printf "%s-spi-passwords" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Return true if a TLS secret object should be created +*/}} +{{- define "keycloak.createTlsSecret" -}} +{{- if and .Values.tls.enabled .Values.tls.autoGenerated (not .Values.tls.existingSecret) }} + {{- true -}} +{{- end -}} +{{- end -}} + +{{/* +Compile all warnings into a single message. +*/}} +{{- define "keycloak.validateValues" -}} +{{- $messages := list -}} +{{- $messages := append $messages (include "keycloak.validateValues.database" .) -}} +{{- $messages := append $messages (include "keycloak.validateValues.tls" .) -}} +{{- $messages := append $messages (include "keycloak.validateValues.production" .) -}} +{{- $messages := without $messages "" -}} +{{- $message := join "\n" $messages -}} + +{{- if $message -}} +{{- printf "\nVALUES VALIDATION:\n%s" $message | fail -}} +{{- end -}} +{{- end -}} + +{{/* Validate values of Keycloak - database */}} +{{- define "keycloak.validateValues.database" -}} +{{- if and (not .Values.postgresql.enabled) (not .Values.externalDatabase.host) (and (not .Values.externalDatabase.password) (not .Values.externalDatabase.existingSecret)) -}} +keycloak: database + You disabled the PostgreSQL sub-chart but did not specify an external PostgreSQL host. + Either deploy the PostgreSQL sub-chart (--set postgresql.enabled=true), + or set a value for the external database host (--set externalDatabase.host=FOO) + and set a value for the external database password (--set externalDatabase.password=BAR) + or existing secret (--set externalDatabase.existingSecret=BAR). +{{- end -}} +{{- end -}} + +{{/* Validate values of Keycloak - TLS enabled */}} +{{- define "keycloak.validateValues.tls" -}} +{{- if and .Values.tls.enabled (not .Values.tls.autoGenerated) (not .Values.tls.existingSecret) }} +keycloak: tls.enabled + In order to enable TLS, you also need to provide + an existing secret containing the Keystore and Truststore or + enable auto-generated certificates. +{{- end -}} +{{- end -}} + +{{/* Validate values of Keycloak - Production mode enabled */}} +{{- define "keycloak.validateValues.production" -}} +{{- if and .Values.production (not .Values.tls.enabled) (not (eq .Values.proxy "edge")) (empty .Values.proxyHeaders) -}} +keycloak: production + In order to enable Production mode, you also need to enable HTTPS/TLS + using the value 'tls.enabled' and providing an existing secret containing the Keystore and Trustore. +{{- end -}} +{{- end -}} diff --git a/charts/keycloak/keycloak/templates/admin-ingress.yaml b/charts/keycloak/keycloak/templates/admin-ingress.yaml new file mode 100644 index 0000000..1869ecf --- /dev/null +++ b/charts/keycloak/keycloak/templates/admin-ingress.yaml @@ -0,0 +1,61 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.adminIngress.enabled }} +apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "common.names.fullname" . }}-admin + namespace: {{ include "common.names.namespace" . | quote }} + {{- $labels := include "common.tplvalues.merge" ( dict "values" ( list .Values.adminIngress.labels .Values.commonLabels ) "context" . ) }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $labels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if or .Values.adminIngress.annotations .Values.commonAnnotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.adminIngress.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.adminIngress.ingressClassName (eq "true" (include "common.ingress.supportsIngressClassname" .)) }} + ingressClassName: {{ .Values.adminIngress.ingressClassName | quote }} + {{- end }} + rules: + {{- if .Values.adminIngress.hostname }} + - host: {{ (tpl .Values.adminIngress.hostname .) | quote }} + http: + paths: + {{- if .Values.adminIngress.extraPaths }} + {{- toYaml .Values.adminIngress.extraPaths | nindent 10 }} + {{- end }} + - path: {{ include "common.tplvalues.render" ( dict "value" .Values.adminIngress.path "context" $) }} + {{- if eq "true" (include "common.ingress.supportsPathType" .) }} + pathType: {{ .Values.adminIngress.pathType }} + {{- end }} + backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" .Values.adminIngress.servicePort "context" $) | nindent 14 }} + {{- end }} + {{- range .Values.adminIngress.extraHosts }} + - host: {{ (tpl .name $) }} + http: + paths: + - path: {{ default "/" .path }} + {{- if eq "true" (include "common.ingress.supportsPathType" $) }} + pathType: {{ default "ImplementationSpecific" .pathType }} + {{- end }} + backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" $) "servicePort" $.Values.adminIngress.servicePort "context" $) | nindent 14 }} + {{- end }} + {{- if .Values.adminIngress.extraRules }} + {{- include "common.tplvalues.render" (dict "value" .Values.adminIngress.extraRules "context" $) | nindent 4 }} + {{- end }} + {{- if or (and .Values.adminIngress.tls (or (include "common.ingress.certManagerRequest" ( dict "annotations" .Values.adminIngress.annotations )) .Values.adminIngress.selfSigned .Values.adminIngress.secrets )) .Values.adminIngress.extraTls }} + tls: + {{- if and .Values.adminIngress.tls (or (include "common.ingress.certManagerRequest" ( dict "annotations" .Values.adminIngress.annotations )) .Values.adminIngress.secrets .Values.adminIngress.selfSigned) }} + - hosts: + - {{ (tpl .Values.adminIngress.hostname .) | quote }} + secretName: {{ printf "%s-tls" (tpl .Values.adminIngress.hostname .) }} + {{- end }} + {{- if .Values.adminIngress.extraTls }} + {{- include "common.tplvalues.render" (dict "value" .Values.adminIngress.extraTls "context" $) | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/configmap-env-vars.yaml b/charts/keycloak/keycloak/templates/configmap-env-vars.yaml new file mode 100644 index 0000000..31a5865 --- /dev/null +++ b/charts/keycloak/keycloak/templates/configmap-env-vars.yaml @@ -0,0 +1,106 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-env-vars" (include "common.names.fullname" .) }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + KEYCLOAK_ADMIN: {{ .Values.auth.adminUser | quote }} + KEYCLOAK_HTTP_PORT: {{ .Values.containerPorts.http | quote }} + {{- if and .Values.proxy (empty .Values.proxyHeaders) }} + KEYCLOAK_PROXY_HEADERS: {{ ternary "" "xforwarded" (eq .Values.proxy "passthrough") }} + {{- else }} + KEYCLOAK_PROXY_HEADERS: {{ .Values.proxyHeaders | quote }} + {{- end }} + {{- if and .Values.adminIngress.enabled .Values.adminIngress.hostname }} + KEYCLOAK_HOSTNAME_ADMIN: |- + {{ ternary "https://" "http://" ( or .Values.adminIngress.tls (eq .Values.proxy "edge") (not (empty .Values.proxyHeaders)) ) -}} + {{- include "common.tplvalues.render" (dict "value" .Values.adminIngress.hostname "context" $) -}} + {{- if eq .Values.adminIngress.controller "default" }} + {{- include "common.tplvalues.render" (dict "value" .Values.adminIngress.path "context" $) }} + {{- else if eq .Values.adminIngress.controller "gce" }} + {{- $path := .Values.adminIngress.path -}} + {{- if hasSuffix "*" $path -}} + {{- $path = trimSuffix "*" $path -}} + {{- end -}} + {{- include "common.tplvalues.render" (dict "value" $path "context" $) }} + {{- end }} + {{- end }} + {{- if and .Values.ingress.enabled .Values.ingress.hostname }} + KEYCLOAK_HOSTNAME: |- + {{ ternary "https://" "http://" ( or .Values.ingress.tls (eq .Values.proxy "edge") (not (empty .Values.proxyHeaders)) ) -}} + {{- include "common.tplvalues.render" (dict "value" .Values.ingress.hostname "context" $) -}} + {{- if eq .Values.ingress.controller "default" }} + {{- include "common.tplvalues.render" (dict "value" .Values.ingress.path "context" $) }} + {{- else if eq .Values.ingress.controller "gce" }} + {{- $path := .Values.ingress.path -}} + {{- if hasSuffix "*" $path -}} + {{- $path = trimSuffix "*" $path -}} + {{- end -}} + {{- include "common.tplvalues.render" (dict "value" $path "context" $) }} + {{- end }} + {{- end }} + {{- if .Values.ingress.enabled }} + KEYCLOAK_HOSTNAME_STRICT: {{ ternary "true" "false" .Values.ingress.hostnameStrict | quote }} + {{- end }} + KEYCLOAK_ENABLE_STATISTICS: {{ ternary "true" "false" .Values.metrics.enabled | quote }} + {{- if not .Values.externalDatabase.existingSecretHostKey }} + KEYCLOAK_DATABASE_HOST: {{ include "keycloak.databaseHost" . | quote }} + {{- end }} + {{- if not .Values.externalDatabase.existingSecretPortKey }} + KEYCLOAK_DATABASE_PORT: {{ include "keycloak.databasePort" . }} + {{- end }} + {{- if not .Values.externalDatabase.existingSecretDatabaseKey }} + KEYCLOAK_DATABASE_NAME: {{ include "keycloak.databaseName" . | quote }} + {{- end }} + {{- if not .Values.externalDatabase.existingSecretUserKey }} + KEYCLOAK_DATABASE_USER: {{ include "keycloak.databaseUser" . | quote }} + {{- end }} + KEYCLOAK_PRODUCTION: {{ ternary "true" "false" .Values.production | quote }} + KEYCLOAK_ENABLE_HTTPS: {{ ternary "true" "false" .Values.tls.enabled | quote }} + {{- if .Values.customCaExistingSecret }} + KC_TRUSTSTORE_PATHS: "/opt/bitnami/keycloak/custom-ca" + {{- end }} + {{- if .Values.tls.enabled }} + KEYCLOAK_HTTPS_PORT: {{ .Values.containerPorts.https | quote }} + KEYCLOAK_HTTPS_USE_PEM: {{ ternary "true" "false" (or .Values.tls.usePem .Values.tls.autoGenerated) | quote }} + {{- if or .Values.tls.usePem .Values.tls.autoGenerated }} + KEYCLOAK_HTTPS_CERTIFICATE_FILE: "/opt/bitnami/keycloak/certs/tls.crt" + KEYCLOAK_HTTPS_CERTIFICATE_KEY_FILE: "/opt/bitnami/keycloak/certs/tls.key" + {{- else }} + KEYCLOAK_HTTPS_KEY_STORE_FILE: {{ printf "/opt/bitnami/keycloak/certs/%s" .Values.tls.keystoreFilename | quote }} + KEYCLOAK_HTTPS_TRUST_STORE_FILE: {{ printf "/opt/bitnami/keycloak/certs/%s" .Values.tls.truststoreFilename | quote }} + {{- end }} + {{- end }} + {{- if .Values.spi.existingSecret }} + {{- if .Values.spi.hostnameVerificationPolicy }} + KEYCLOAK_SPI_TRUSTSTORE_FILE_HOSTNAME_VERIFICATION_POLICY: {{ .Values.spi.hostnameVerificationPolicy | quote }} + {{- end }} + KEYCLOAK_SPI_TRUSTSTORE_FILE: {{ printf "/opt/bitnami/keycloak/spi-certs/%s" .Values.spi.truststoreFilename }} + {{- end }} + {{- if .Values.cache.enabled }} + KEYCLOAK_CACHE_TYPE: "ispn" + {{- if .Values.cache.stackName }} + KEYCLOAK_CACHE_STACK: {{ .Values.cache.stackName | quote }} + {{- end }} + {{- if .Values.cache.stackFile }} + KEYCLOAK_CACHE_CONFIG_FILE: {{ .Values.cache.stackFile | quote }} + {{- end }} + JAVA_OPTS_APPEND: {{ printf "-Djgroups.dns.query=%s-headless.%s.svc.%s" (include "common.names.fullname" .) (include "common.names.namespace" .) .Values.clusterDomain | quote }} + {{- else }} + KEYCLOAK_CACHE_TYPE: "local" + {{- end }} + {{- if .Values.logging }} + KEYCLOAK_LOG_OUTPUT: {{ .Values.logging.output | quote }} + KEYCLOAK_LOG_LEVEL: {{ .Values.logging.level | quote }} + {{- end }} + diff --git a/charts/keycloak/keycloak/templates/configmap.yaml b/charts/keycloak/keycloak/templates/configmap.yaml new file mode 100644 index 0000000..12cca41 --- /dev/null +++ b/charts/keycloak/keycloak/templates/configmap.yaml @@ -0,0 +1,20 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if (include "keycloak.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-configuration" (include "common.names.fullname" .) }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: + keycloak.conf: |- + {{- .Values.configuration | nindent 4 }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/extra-list.yaml b/charts/keycloak/keycloak/templates/extra-list.yaml new file mode 100644 index 0000000..329f5c6 --- /dev/null +++ b/charts/keycloak/keycloak/templates/extra-list.yaml @@ -0,0 +1,9 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- range .Values.extraDeploy }} +--- +{{ include "common.tplvalues.render" (dict "value" . "context" $) }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/headless-service.yaml b/charts/keycloak/keycloak/templates/headless-service.yaml new file mode 100644 index 0000000..c71e9a8 --- /dev/null +++ b/charts/keycloak/keycloak/templates/headless-service.yaml @@ -0,0 +1,40 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-headless" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if or .Values.commonAnnotations .Values.service.headless.annotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.service.headless.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http + port: {{ .Values.containerPorts.http }} + protocol: TCP + targetPort: http + {{- if .Values.tls.enabled }} + - name: https + port: {{ .Values.containerPorts.https }} + protocol: TCP + targetPort: https + {{- end }} + {{- if .Values.service.extraHeadlessPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.extraHeadlessPorts "context" $) | nindent 4 }} + {{- end }} + {{- if .Values.service.headless.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.headless.extraPorts "context" $) | nindent 4 }} + {{- end }} + publishNotReadyAddresses: true + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }} + selector: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak diff --git a/charts/keycloak/keycloak/templates/hpa.yaml b/charts/keycloak/keycloak/templates/hpa.yaml new file mode 100644 index 0000000..ff673c2 --- /dev/null +++ b/charts/keycloak/keycloak/templates/hpa.yaml @@ -0,0 +1,66 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.autoscaling.enabled }} +apiVersion: {{ include "common.capabilities.hpa.apiVersion" ( dict "context" $ ) }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + scaleTargetRef: + apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} + kind: StatefulSet + name: {{ template "common.names.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPU }} + - type: Resource + resource: + name: cpu + {{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }} + targetAverageUtilization: {{ .Values.autoscaling.targetCPU }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPU }} + {{- end }} + {{- end }} + {{- if .Values.autoscaling.targetMemory }} + - type: Resource + resource: + name: memory + {{- if semverCompare "<1.23-0" (include "common.capabilities.kubeVersion" .) }} + targetAverageUtilization: {{ .Values.autoscaling.targetMemory }} + {{- else }} + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemory }} + {{- end }} + {{- end }} + {{- if or .Values.autoscaling.behavior.scaleDown.policies .Values.autoscaling.behavior.scaleUp.policies }} + behavior: + {{- if .Values.autoscaling.behavior.scaleDown.policies }} + scaleDown: + stabilizationWindowSeconds: {{ .Values.autoscaling.behavior.scaleDown.stabilizationWindowSeconds }} + selectPolicy: {{ .Values.autoscaling.behavior.scaleDown.selectPolicy }} + policies: + {{- toYaml .Values.autoscaling.behavior.scaleDown.policies | nindent 8 }} + {{- end }} + {{- if .Values.autoscaling.behavior.scaleUp.policies }} + scaleUp: + stabilizationWindowSeconds: {{ .Values.autoscaling.behavior.scaleUp.stabilizationWindowSeconds }} + selectPolicy: {{ .Values.autoscaling.behavior.scaleUp.selectPolicy }} + policies: + {{- toYaml .Values.autoscaling.behavior.scaleUp.policies | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/ingress.yaml b/charts/keycloak/keycloak/templates/ingress.yaml new file mode 100644 index 0000000..5ffcaa8 --- /dev/null +++ b/charts/keycloak/keycloak/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.ingress.enabled }} +apiVersion: {{ include "common.capabilities.ingress.apiVersion" . }} +kind: Ingress +metadata: + name: {{ include "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + {{- $labels := include "common.tplvalues.merge" ( dict "values" ( list .Values.ingress.labels .Values.commonLabels ) "context" . ) }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $labels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if or .Values.ingress.annotations .Values.commonAnnotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.ingress.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.ingressClassName (eq "true" (include "common.ingress.supportsIngressClassname" .)) }} + ingressClassName: {{ .Values.ingress.ingressClassName | quote }} + {{- end }} + rules: + {{- if .Values.ingress.hostname }} + - host: {{ (tpl .Values.ingress.hostname .) | quote }} + http: + paths: + {{- if .Values.ingress.extraPaths }} + {{- toYaml .Values.ingress.extraPaths | nindent 10 }} + {{- end }} + - path: {{ include "common.tplvalues.render" ( dict "value" .Values.ingress.path "context" $) }} + {{- if eq "true" (include "common.ingress.supportsPathType" .) }} + pathType: {{ .Values.ingress.pathType }} + {{- end }} + backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" .) "servicePort" .Values.ingress.servicePort "context" $) | nindent 14 }} + {{- end }} + {{- range .Values.ingress.extraHosts }} + - host: {{ (tpl .name $) }} + http: + paths: + - path: {{ default "/" .path }} + {{- if eq "true" (include "common.ingress.supportsPathType" $) }} + pathType: {{ default "ImplementationSpecific" .pathType }} + {{- end }} + backend: {{- include "common.ingress.backend" (dict "serviceName" (include "common.names.fullname" $) "servicePort" $.Values.ingress.servicePort "context" $) | nindent 14 }} + {{- end }} + {{- if .Values.ingress.extraRules }} + {{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraRules "context" $) | nindent 4 }} + {{- end }} + {{- if or (and .Values.ingress.tls (or (include "common.ingress.certManagerRequest" ( dict "annotations" .Values.ingress.annotations )) .Values.ingress.selfSigned .Values.ingress.secrets )) .Values.ingress.extraTls }} + tls: + {{- if and .Values.ingress.tls (or (include "common.ingress.certManagerRequest" ( dict "annotations" .Values.ingress.annotations )) .Values.ingress.secrets .Values.ingress.selfSigned) }} + - hosts: + - {{ (tpl .Values.ingress.hostname .) | quote }} + secretName: {{ printf "%s-tls" (tpl .Values.ingress.hostname .) }} + {{- end }} + {{- if .Values.ingress.extraTls }} + {{- include "common.tplvalues.render" (dict "value" .Values.ingress.extraTls "context" $) | nindent 4 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/init-scripts-configmap.yaml b/charts/keycloak/keycloak/templates/init-scripts-configmap.yaml new file mode 100644 index 0000000..a758d40 --- /dev/null +++ b/charts/keycloak/keycloak/templates/init-scripts-configmap.yaml @@ -0,0 +1,19 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.initdbScripts (not .Values.initdbScriptsConfigMap) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ printf "%s-init-scripts" (include "common.names.fullname" .) }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +data: +{{- include "common.tplvalues.render" (dict "value" .Values.initdbScripts "context" .) | nindent 2 }} +{{ end }} diff --git a/charts/keycloak/keycloak/templates/keycloak-config-cli-configmap.yaml b/charts/keycloak/keycloak/templates/keycloak-config-cli-configmap.yaml new file mode 100644 index 0000000..bba17b7 --- /dev/null +++ b/charts/keycloak/keycloak/templates/keycloak-config-cli-configmap.yaml @@ -0,0 +1,23 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if (include "keycloak.keycloakConfigCli.createConfigmap" .) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "keycloak.keycloakConfigCli.configmapName" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak-config-cli +data: + {{- range $fileName, $fileContent := .Values.keycloakConfigCli.configuration }} + {{- if $fileContent }} + {{ $fileName }}: | + {{- include "common.tplvalues.render" (dict "value" $fileContent "context" $) | nindent 4 }} + {{- else }} + {{- ($.Files.Glob $fileName).AsConfig | nindent 2 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/keycloak-config-cli-job.yaml b/charts/keycloak/keycloak/templates/keycloak-config-cli-job.yaml new file mode 100644 index 0000000..3a78854 --- /dev/null +++ b/charts/keycloak/keycloak/templates/keycloak-config-cli-job.yaml @@ -0,0 +1,138 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.keycloakConfigCli.enabled }} +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ printf "%s-keycloak-config-cli" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak-config-cli + {{- if or .Values.keycloakConfigCli.annotations .Values.commonAnnotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.keycloakConfigCli.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + backoffLimit: {{ .Values.keycloakConfigCli.backoffLimit }} + {{- if .Values.keycloakConfigCli.cleanupAfterFinished.enabled }} + ttlSecondsAfterFinished: {{ .Values.keycloakConfigCli.cleanupAfterFinished.seconds }} + {{- end }} + template: + metadata: + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.keycloakConfigCli.podLabels .Values.commonLabels ) "context" . ) }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $podLabels "context" $ ) | nindent 8 }} + app.kubernetes.io/component: keycloak-config-cli + annotations: + {{- if (include "keycloak.keycloakConfigCli.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/keycloak-config-cli-configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.keycloakConfigCli.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.podAnnotations "context" $) | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "keycloak.serviceAccountName" . }} + {{- include "keycloak.imagePullSecrets" . | nindent 6 }} + restartPolicy: Never + {{- if .Values.keycloakConfigCli.podSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.keycloakConfigCli.podSecurityContext "context" $) | nindent 8 }} + {{- end }} + automountServiceAccountToken: {{ .Values.keycloakConfigCli.automountServiceAccountToken }} + {{- if .Values.keycloakConfigCli.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.keycloakConfigCli.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.keycloakConfigCli.podTolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.podTolerations "context" .) | nindent 8 }} + {{- end }} + {{- if .Values.keycloakConfigCli.initContainers }} + initContainers: + {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.initContainers "context" $) | nindent 8 }} + {{- end }} + containers: + - name: keycloak-config-cli + image: {{ template "keycloak.keycloakConfigCli.image" . }} + imagePullPolicy: {{ .Values.keycloakConfigCli.image.pullPolicy }} + {{- if .Values.keycloakConfigCli.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.command "context" $) | nindent 12 }} + {{- else }} + command: + - java + - -jar + - /opt/bitnami/keycloak-config-cli/keycloak-config-cli.jar + {{- end }} + {{- if .Values.keycloakConfigCli.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.args "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.keycloakConfigCli.containerSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.keycloakConfigCli.containerSecurityContext "context" $) | nindent 12 }} + {{- end }} + env: + - name: KEYCLOAK_URL + value: {{ printf "http://%s-headless:%d%s" (include "common.names.fullname" .) (.Values.containerPorts.http | int) (.Values.httpRelativePath) }} + - name: KEYCLOAK_USER + value: {{ .Values.auth.adminUser | quote }} + - name: KEYCLOAK_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.secretName" . }} + key: {{ include "keycloak.secretKey" . }} + {{- if or .Values.keycloakConfigCli.configuration .Values.keycloakConfigCli.existingConfigmap }} + - name: IMPORT_FILES_LOCATIONS + value: /config/* + {{- end }} + - name: KEYCLOAK_AVAILABILITYCHECK_ENABLED + value: "true" + {{- if .Values.keycloakConfigCli.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + {{- if or .Values.keycloakConfigCli.extraEnvVarsCM .Values.keycloakConfigCli.extraEnvVarsSecret }} + envFrom: + {{- if .Values.keycloakConfigCli.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.keycloakConfigCli.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.extraEnvVarsSecret "context" $) }} + {{- end }} + {{- end }} + {{- if or .Values.keycloakConfigCli.configuration .Values.keycloakConfigCli.existingConfigmap .Values.keycloakConfigCli.extraVolumeMounts }} + volumeMounts: + - name: empty-dir + mountPath: /tmp + subPath: tmp-dir + {{- if or .Values.keycloakConfigCli.configuration .Values.keycloakConfigCli.existingConfigmap }} + - name: config-volume + mountPath: /config + {{- end }} + {{- if .Values.keycloakConfigCli.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- end }} + {{- if .Values.keycloakConfigCli.resources }} + resources: {{- toYaml .Values.keycloakConfigCli.resources | nindent 12 }} + {{- else if ne .Values.keycloakConfigCli.resourcesPreset "none" }} + resources: {{- include "common.resources.preset" (dict "type" .Values.keycloakConfigCli.resourcesPreset) | nindent 12 }} + {{- end }} + {{- if .Values.keycloakConfigCli.sidecars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.keycloakConfigCli.sidecars "context" $) | nindent 8 }} + {{- end }} + {{- if or .Values.keycloakConfigCli.configuration .Values.keycloakConfigCli.existingConfigmap .Values.keycloakConfigCli.extraVolumes }} + volumes: + - name: empty-dir + emptyDir: {} + {{- if or .Values.keycloakConfigCli.configuration .Values.keycloakConfigCli.existingConfigmap }} + - name: config-volume + configMap: + name: {{ include "keycloak.keycloakConfigCli.configmapName" . }} + {{- end }} + {{- if .Values.keycloakConfigCli.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.keycloakConfigCli.extraVolumes "context" $) | nindent 8 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/metrics-service.yaml b/charts/keycloak/keycloak/templates/metrics-service.yaml new file mode 100644 index 0000000..8987b17 --- /dev/null +++ b/charts/keycloak/keycloak/templates/metrics-service.yaml @@ -0,0 +1,41 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.metrics.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ printf "%s-metrics" (include "common.names.fullname" .) }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: metrics + {{- if or .Values.metrics.service.annotations .Values.commonAnnotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.metrics.service.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + ports: + - name: metrics + port: {{ .Values.metrics.service.ports.metrics }} + protocol: TCP + targetPort: {{ .Values.containerPorts.metrics }} + - name: http + port: {{ .Values.metrics.service.ports.http }} + protocol: TCP + targetPort: {{ .Values.containerPorts.http }} + {{- if .Values.tls.enabled }} + - name: https + port: {{ .Values.metrics.service.ports.https }} + protocol: TCP + targetPort: {{ .Values.containerPorts.https }} + {{- end }} + {{- if .Values.metrics.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }} + selector: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak +{{- end }} diff --git a/charts/keycloak/keycloak/templates/networkpolicy.yaml b/charts/keycloak/keycloak/templates/networkpolicy.yaml new file mode 100644 index 0000000..ffb5828 --- /dev/null +++ b/charts/keycloak/keycloak/templates/networkpolicy.yaml @@ -0,0 +1,102 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.networkPolicy.enabled }} +kind: NetworkPolicy +apiVersion: {{ include "common.capabilities.networkPolicy.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }} + podSelector: + matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }} + app.kubernetes.io/component: keycloak + policyTypes: + - Ingress + - Egress + {{- if .Values.networkPolicy.allowExternalEgress }} + egress: + - {} + {{- else }} + egress: + - ports: + # Allow dns resolution + - port: 53 + protocol: UDP + - port: 53 + protocol: TCP + {{- range $port := .Values.networkPolicy.kubeAPIServerPorts }} + - port: {{ $port }} + {{- end }} + # Allow connection to PostgreSQL + - ports: + - port: {{ include "keycloak.databasePort" . | trimAll "\"" | int }} + {{- if .Values.postgresql.enabled }} + to: + - podSelector: + matchLabels: + app.kubernetes.io/name: postgresql + app.kubernetes.io/instance: {{ .Release.Name }} + {{- end }} + # Allow connection to other keycloak nodes + - ports: + {{- /* Constant in code: https://github.com/keycloak/keycloak/blob/ce8e925c1ad9bf7a3180d1496e181aeea0ab5f8a/operator/src/main/java/org/keycloak/operator/Constants.java#L60 */}} + - port: 7800 + - port: {{ .Values.containerPorts.http }} + {{- if .Values.tls.enabled }} + - port: {{ .Values.containerPorts.https }} + {{- end }} + to: + - podSelector: + matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 14 }} + app.kubernetes.io/component: keycloak + {{- if .Values.networkPolicy.extraEgress }} + {{- include "common.tplvalues.render" ( dict "value" .Values.networkPolicy.extraEgress "context" $ ) | nindent 4 }} + {{- end }} + {{- end }} + ingress: + - ports: + {{- /* Constant in code: https://github.com/keycloak/keycloak/blob/ce8e925c1ad9bf7a3180d1496e181aeea0ab5f8a/operator/src/main/java/org/keycloak/operator/Constants.java#L60 */}} + - port: 7800 + {{- if and (.Values.metrics.enabled) (not (eq (.Values.containerPorts.http | int) (.Values.containerPorts.metrics | int) )) }} + - port: {{ .Values.containerPorts.metrics }} # metrics and health + {{- end }} + - port: {{ .Values.containerPorts.http }} + {{- if .Values.tls.enabled }} + - port: {{ .Values.containerPorts.https }} + {{- end }} + {{- if not .Values.networkPolicy.allowExternal }} + from: + - podSelector: + matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 14 }} + - podSelector: + matchLabels: + {{ template "common.names.fullname" . }}-client: "true" + {{- if .Values.networkPolicy.ingressNSMatchLabels }} + - namespaceSelector: + matchLabels: + {{- range $key, $value := .Values.networkPolicy.ingressNSMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- if .Values.networkPolicy.ingressNSPodMatchLabels }} + podSelector: + matchLabels: + {{- range $key, $value := .Values.networkPolicy.ingressNSPodMatchLabels }} + {{ $key | quote }}: {{ $value | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- $extraIngress := coalesce .Values.networkPolicy.additionalRules .Values.networkPolicy.extraIngress }} + {{- if $extraIngress }} + {{- include "common.tplvalues.render" ( dict "value" $extraIngress "context" $ ) | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/pdb.yaml b/charts/keycloak/keycloak/templates/pdb.yaml new file mode 100644 index 0000000..653b953 --- /dev/null +++ b/charts/keycloak/keycloak/templates/pdb.yaml @@ -0,0 +1,28 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.pdb.create }} +apiVersion: {{ include "common.capabilities.policy.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.pdb.minAvailable }} + minAvailable: {{ .Values.pdb.minAvailable }} + {{- end }} + {{- if or .Values.pdb.maxUnavailable ( not .Values.pdb.minAvailable ) }} + maxUnavailable: {{ .Values.pdb.maxUnavailable | default 1 }} + {{- end }} + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }} + selector: + matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }} + app.kubernetes.io/component: keycloak +{{- end }} diff --git a/charts/keycloak/keycloak/templates/prometheusrule.yaml b/charts/keycloak/keycloak/templates/prometheusrule.yaml new file mode 100644 index 0000000..2792392 --- /dev/null +++ b/charts/keycloak/keycloak/templates/prometheusrule.yaml @@ -0,0 +1,20 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.metrics.enabled .Values.metrics.prometheusRule.enabled .Values.metrics.prometheusRule.groups}} +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ default (include "common.names.namespace" .) .Values.metrics.prometheusRule.namespace }} + {{- $labels := include "common.tplvalues.merge" ( dict "values" ( list .Values.metrics.prometheusRule.labels .Values.commonLabels ) "context" . ) }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $labels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + groups: {{- include "common.tplvalues.render" (dict "value" .Values.metrics.prometheusRule.groups "context" .) | nindent 4 }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/role.yaml b/charts/keycloak/keycloak/templates/role.yaml new file mode 100644 index 0000000..e913b15 --- /dev/null +++ b/charts/keycloak/keycloak/templates/role.yaml @@ -0,0 +1,28 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.serviceAccount.create .Values.rbac.create }} +kind: Role +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +rules: + {{- if .Values.rbac.rules }} + {{- include "common.tplvalues.render" ( dict "value" .Values.rbac.rules "context" $ ) | nindent 2 }} + {{- end }} + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - list +{{- end }} diff --git a/charts/keycloak/keycloak/templates/rolebinding.yaml b/charts/keycloak/keycloak/templates/rolebinding.yaml new file mode 100644 index 0000000..c954da8 --- /dev/null +++ b/charts/keycloak/keycloak/templates/rolebinding.yaml @@ -0,0 +1,25 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.serviceAccount.create .Values.rbac.create }} +kind: RoleBinding +apiVersion: {{ include "common.capabilities.rbac.apiVersion" . }} +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "common.names.fullname" . }} +subjects: + - kind: ServiceAccount + name: {{ template "keycloak.serviceAccountName" . }} + namespace: {{ include "common.names.namespace" . | quote }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/secret-external-db.yaml b/charts/keycloak/keycloak/templates/secret-external-db.yaml new file mode 100644 index 0000000..0ff5367 --- /dev/null +++ b/charts/keycloak/keycloak/templates/secret-external-db.yaml @@ -0,0 +1,19 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and (not .Values.postgresql.enabled) (not .Values.externalDatabase.existingSecret) (not .Values.postgresql.existingSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-externaldb" .Release.Name }} + namespace: {{ .Release.Namespace | quote }} + labels: {{- include "common.labels.standard" (dict "customLabels" .Values.commonLabels "context" $) | nindent 4 }} + {{- if or .Values.externalDatabase.annotations .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.merge" (dict "values" (list .Values.externalDatabase.annotations .Values.commonAnnotations) "context" $) | nindent 4 }} + {{- end }} +type: Opaque +data: + db-password: {{ include "common.secrets.passwords.manage" (dict "secret" (printf "%s-externaldb" .Release.Name) "key" "db-password" "length" 10 "providedValues" (list "externalDatabase.password") "context" $) }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/secrets.yaml b/charts/keycloak/keycloak/templates/secrets.yaml new file mode 100644 index 0000000..f1b9025 --- /dev/null +++ b/charts/keycloak/keycloak/templates/secrets.yaml @@ -0,0 +1,20 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if not .Values.auth.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" (dict "customLabels" .Values.commonLabels "context" $) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if or .Values.auth.annotations .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.merge" (dict "values" (list .Values.auth.annotations .Values.commonAnnotations) "context" $) | nindent 4 }} + {{- end }} +type: Opaque +data: + admin-password: {{ include "common.secrets.passwords.manage" (dict "secret" (printf "%s" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-") "key" "admin-password" "length" 10 "providedValues" (list "auth.adminPassword") "context" $) }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/service.yaml b/charts/keycloak/keycloak/templates/service.yaml new file mode 100644 index 0000000..dec9cb5 --- /dev/null +++ b/charts/keycloak/keycloak/templates/service.yaml @@ -0,0 +1,65 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if or .Values.service.annotations .Values.commonAnnotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.service.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +spec: + type: {{ .Values.service.type }} + {{- if and .Values.service.clusterIP (eq .Values.service.type "ClusterIP") }} + clusterIP: {{ .Values.service.clusterIP }} + {{- end }} + {{- if or (eq .Values.service.type "LoadBalancer") (eq .Values.service.type "NodePort") }} + externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy | quote }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerSourceRanges)) }} + loadBalancerSourceRanges: {{ .Values.service.loadBalancerSourceRanges }} + {{- end }} + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + {{- if .Values.service.sessionAffinity }} + sessionAffinity: {{ .Values.service.sessionAffinity }} + {{- end }} + {{- if .Values.service.sessionAffinityConfig }} + sessionAffinityConfig: {{- include "common.tplvalues.render" (dict "value" .Values.service.sessionAffinityConfig "context" $) | nindent 4 }} + {{- end }} + ports: + {{- if .Values.service.http.enabled }} + - name: http + port: {{ coalesce .Values.service.ports.http .Values.service.port }} + protocol: TCP + targetPort: http + {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.http))) }} + nodePort: {{ .Values.service.nodePorts.http }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: https + port: {{ coalesce .Values.service.ports.https .Values.service.httpsPort }} + protocol: TCP + targetPort: https + {{- if (and (or (eq .Values.service.type "NodePort") (eq .Values.service.type "LoadBalancer")) (not (empty .Values.service.nodePorts.https))) }} + nodePort: {{ .Values.service.nodePorts.https }} + {{- else if eq .Values.service.type "ClusterIP" }} + nodePort: null + {{- end }} + {{- end }} + {{- if .Values.service.extraPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.service.extraPorts "context" $) | nindent 4 }} + {{- end }} + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }} + selector: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak diff --git a/charts/keycloak/keycloak/templates/serviceaccount.yaml b/charts/keycloak/keycloak/templates/serviceaccount.yaml new file mode 100644 index 0000000..467e14b --- /dev/null +++ b/charts/keycloak/keycloak/templates/serviceaccount.yaml @@ -0,0 +1,22 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "keycloak.serviceAccountName" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.serviceAccount.extraLabels }} + {{- include "common.tplvalues.render" (dict "value" .Values.serviceAccount.extraLabels "context" $) | nindent 4 }} + {{- end }} + {{- if or .Values.serviceAccount.annotations .Values.commonAnnotations }} + {{- $annotations := include "common.tplvalues.merge" ( dict "values" ( list .Values.serviceAccount.annotations .Values.commonAnnotations ) "context" . ) }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $annotations "context" $) | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/servicemonitor.yaml b/charts/keycloak/keycloak/templates/servicemonitor.yaml new file mode 100644 index 0000000..a63d53e --- /dev/null +++ b/charts/keycloak/keycloak/templates/servicemonitor.yaml @@ -0,0 +1,58 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and .Values.metrics.enabled .Values.metrics.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ default (include "common.names.namespace" .) .Values.metrics.serviceMonitor.namespace }} + {{- $labels := include "common.tplvalues.merge" ( dict "values" ( list .Values.metrics.serviceMonitor.labels .Values.commonLabels ) "context" . ) }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $labels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if .Values.metrics.serviceMonitor.jobLabel }} + jobLabel: {{ .Values.metrics.serviceMonitor.jobLabel }} + {{- end }} + endpoints: + {{- $defaultEndpoint := pick .Values.metrics.serviceMonitor "port" "scheme" "tlsConfig" "interval" "scrapeTimeout" "relabelings" "metricRelabelings" "honorLabels" }} + {{- $endpoints := ternary (.Values.metrics.serviceMonitor.endpoints) (list (dict "path" .Values.metrics.serviceMonitor.path)) (empty .Values.metrics.serviceMonitor.path) }} + {{- range $endpoints }} + {{- $endpoint := merge . $defaultEndpoint }} + - port: {{ $endpoint.port | quote }} + scheme: {{ $endpoint.scheme | quote }} + {{- if $endpoint.tlsConfig }} + tlsConfig: {{- include "common.tplvalues.render" ( dict "value" $endpoint.tlsConfig "context" $) | nindent 8 }} + {{- end }} + path: {{ include "common.tplvalues.render" ( dict "value" $endpoint.path "context" $) }} + {{- if $endpoint.interval }} + interval: {{ $endpoint.interval }} + {{- end }} + {{- if $endpoint.scrapeTimeout }} + scrapeTimeout: {{ $endpoint.scrapeTimeout }} + {{- end }} + {{- if $endpoint.relabelings }} + relabelings: {{- include "common.tplvalues.render" ( dict "value" $endpoint.relabelings "context" $) | nindent 6 }} + {{- end }} + {{- if $endpoint.metricRelabelings }} + metricRelabelings: {{- include "common.tplvalues.render" ( dict "value" $endpoint.metricRelabelings "context" $) | nindent 6 }} + {{- end }} + {{- if $endpoint.honorLabels }} + honorLabels: {{ $endpoint.honorLabels }} + {{- end }} + {{- end }} + namespaceSelector: + matchNames: + - {{ include "common.names.namespace" . | quote }} + selector: + matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 6 }} + {{- if .Values.metrics.serviceMonitor.selector }} + {{- include "common.tplvalues.render" (dict "value" .Values.metrics.serviceMonitor.selector "context" $) | nindent 6 }} + {{- end }} + app.kubernetes.io/component: metrics +{{- end }} diff --git a/charts/keycloak/keycloak/templates/statefulset.yaml b/charts/keycloak/keycloak/templates/statefulset.yaml new file mode 100644 index 0000000..b15f980 --- /dev/null +++ b/charts/keycloak/keycloak/templates/statefulset.yaml @@ -0,0 +1,371 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +apiVersion: {{ include "common.capabilities.statefulset.apiVersion" . }} +kind: StatefulSet +metadata: + name: {{ template "common.names.fullname" . }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if or .Values.statefulsetAnnotations .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.merge" ( dict "values" ( list .Values.statefulsetAnnotations .Values.commonAnnotations ) "context" $ ) | nindent 4 }} + {{- end }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimitCount }} + podManagementPolicy: {{ .Values.podManagementPolicy }} + serviceName: {{ printf "%s-headless" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + updateStrategy: + {{- include "common.tplvalues.render" (dict "value" .Values.updateStrategy "context" $ ) | nindent 4 }} + {{- if .Values.minReadySeconds }} + minReadySeconds: {{ .Values.minReadySeconds }} + {{- end }} + {{- $podLabels := include "common.tplvalues.merge" ( dict "values" ( list .Values.podLabels .Values.commonLabels ) "context" . ) }} + selector: + matchLabels: {{- include "common.labels.matchLabels" ( dict "customLabels" $podLabels "context" $ ) | nindent 6 }} + app.kubernetes.io/component: keycloak + template: + metadata: + annotations: + checksum/configmap-env-vars: {{ include (print $.Template.BasePath "/configmap-env-vars.yaml") . | sha256sum }} + {{- if not .Values.auth.existingSecret }} + checksum/secrets: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }} + {{- end }} + {{- if (include "keycloak.createConfigmap" .) }} + checksum/configuration: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + {{- end }} + {{- if .Values.podAnnotations }} + {{- include "common.tplvalues.render" (dict "value" .Values.podAnnotations "context" $) | nindent 8 }} + {{- end }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $podLabels "context" $ ) | nindent 8 }} + app.kubernetes.io/component: keycloak + spec: + serviceAccountName: {{ template "keycloak.serviceAccountName" . }} + {{- include "keycloak.imagePullSecrets" . | nindent 6 }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + {{- if .Values.hostAliases }} + hostAliases: {{- include "common.tplvalues.render" (dict "value" .Values.hostAliases "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.affinity }} + affinity: {{- include "common.tplvalues.render" ( dict "value" .Values.affinity "context" $) | nindent 8 }} + {{- else }} + affinity: + podAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAffinityPreset "customLabels" $podLabels "context" $) | nindent 10 }} + podAntiAffinity: {{- include "common.affinities.pods" (dict "type" .Values.podAntiAffinityPreset "customLabels" $podLabels "context" $) | nindent 10 }} + nodeAffinity: {{- include "common.affinities.nodes" (dict "type" .Values.nodeAffinityPreset.type "key" .Values.nodeAffinityPreset.key "values" .Values.nodeAffinityPreset.values) | nindent 10 }} + {{- end }} + {{- if .Values.nodeSelector }} + nodeSelector: {{- include "common.tplvalues.render" ( dict "value" .Values.nodeSelector "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: {{- include "common.tplvalues.render" (dict "value" .Values.tolerations "context" .) | nindent 8 }} + {{- end }} + {{- if .Values.topologySpreadConstraints }} + topologySpreadConstraints: {{- include "common.tplvalues.render" (dict "value" .Values.topologySpreadConstraints "context" .) | nindent 8 }} + {{- end }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName | quote }} + {{- end }} + {{- if .Values.schedulerName }} + schedulerName: {{ .Values.schedulerName }} + {{- end }} + {{- if .Values.podSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.podSecurityContext "context" $) | nindent 8 }} + {{- end }} + {{- if .Values.dnsPolicy }} + dnsPolicy: {{ .Values.dnsPolicy }} + {{- end }} + {{- if .Values.dnsConfig }} + dnsConfig: {{- include "common.tplvalues.render" (dict "value" .Values.dnsConfig "context" .) | nindent 8 }} + {{- end }} + {{- if semverCompare ">= 1.13" (include "common.capabilities.kubeVersion" .) }} + enableServiceLinks: {{ .Values.enableServiceLinks }} + {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} + {{- if or .Values.enableDefaultInitContainers .Values.initContainers }} + initContainers: + {{- if .Values.enableDefaultInitContainers }} + - name: prepare-write-dirs + image: {{ template "keycloak.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + command: + - /bin/bash + args: + - -ec + - | + . /opt/bitnami/scripts/liblog.sh + + info "Copying writable dirs to empty dir" + # In order to not break the application functionality we need to make some + # directories writable, so we need to copy it to an empty dir volume + cp -r --preserve=mode /opt/bitnami/keycloak/lib/quarkus /emptydir/app-quarkus-dir + cp -r --preserve=mode /opt/bitnami/keycloak/data /emptydir/app-data-dir + cp -r --preserve=mode /opt/bitnami/keycloak/providers /emptydir/app-providers-dir + info "Copy operation completed" + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.containerSecurityContext "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- else if ne .Values.resourcesPreset "none" }} + resources: {{- include "common.resources.preset" (dict "type" .Values.resourcesPreset) | nindent 12 }} + {{- end }} + volumeMounts: + - name: empty-dir + mountPath: /emptydir + {{- end }} + {{- if .Values.initContainers }} + {{- include "common.tplvalues.render" (dict "value" .Values.initContainers "context" $) | nindent 8 }} + {{- end }} + {{- end }} + containers: + - name: keycloak + image: {{ template "keycloak.image" . }} + imagePullPolicy: {{ .Values.image.pullPolicy }} + {{- if .Values.lifecycleHooks }} + lifecycle: {{- include "common.tplvalues.render" (dict "value" .Values.lifecycleHooks "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.containerSecurityContext.enabled }} + securityContext: {{- include "common.compatibility.renderSecurityContext" (dict "secContext" .Values.containerSecurityContext "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.command "context" $) | nindent 12 }} + {{- else if .Values.command }} + command: {{- include "common.tplvalues.render" (dict "value" .Values.command "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.diagnosticMode.enabled }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.diagnosticMode.args "context" $) | nindent 12 }} + {{- else if .Values.args }} + args: {{- include "common.tplvalues.render" (dict "value" .Values.args "context" $) | nindent 12 }} + {{- end }} + env: + - name: KUBERNETES_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + - name: BITNAMI_DEBUG + value: {{ ternary "true" "false" .Values.image.debug | quote }} + - name: KEYCLOAK_ADMIN_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.secretName" . }} + key: {{ include "keycloak.secretKey" . }} + - name: KEYCLOAK_DATABASE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.databaseSecretName" . }} + key: {{ include "keycloak.databaseSecretPasswordKey" . }} + {{- if .Values.externalDatabase.existingSecretHostKey }} + - name: KEYCLOAK_DATABASE_HOST + valueFrom: + secretKeyRef: + name: {{ include "keycloak.databaseSecretName" . }} + key: {{ include "keycloak.databaseSecretHostKey" . }} + {{- end }} + {{- if .Values.externalDatabase.existingSecretPortKey }} + - name: KEYCLOAK_DATABASE_PORT + valueFrom: + secretKeyRef: + name: {{ include "keycloak.databaseSecretName" . }} + key: {{ include "keycloak.databaseSecretPortKey" . }} + {{- end }} + {{- if .Values.externalDatabase.existingSecretUserKey }} + - name: KEYCLOAK_DATABASE_USER + valueFrom: + secretKeyRef: + name: {{ include "keycloak.databaseSecretName" . }} + key: {{ include "keycloak.databaseSecretUserKey" . }} + {{- end }} + {{- if .Values.externalDatabase.existingSecretDatabaseKey }} + - name: KEYCLOAK_DATABASE_NAME + valueFrom: + secretKeyRef: + name: {{ include "keycloak.databaseSecretName" . }} + key: {{ include "keycloak.databaseSecretDatabaseKey" . }} + {{- end }} + {{- if and .Values.tls.enabled (or .Values.tls.keystorePassword .Values.tls.passwordsSecret) }} + - name: KEYCLOAK_HTTPS_KEY_STORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.tlsPasswordsSecretName" . }} + key: "tls-keystore-password" + {{- end }} + {{- if and .Values.tls.enabled (or .Values.tls.truststorePassword .Values.tls.passwordsSecret) }} + - name: KEYCLOAK_HTTPS_TRUST_STORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.tlsPasswordsSecretName" . }} + key: "tls-truststore-password" + {{- end }} + {{- if and .Values.spi.existingSecret (or .Values.spi.truststorePassword .Values.spi.passwordsSecret) }} + - name: KEYCLOAK_SPI_TRUSTSTORE_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "keycloak.spiPasswordsSecretName" . }} + key: "spi-truststore-password" + {{- end }} + - name: KEYCLOAK_HTTP_RELATIVE_PATH + value: {{ .Values.httpRelativePath | quote }} + {{- if .Values.extraStartupArgs }} + - name: KEYCLOAK_EXTRA_ARGS + value: {{ .Values.extraStartupArgs | quote }} + {{- end }} + {{- if .Values.adminRealm }} + - name: KC_SPI_ADMIN_REALM + value: "{{ .Values.adminRealm }}" + {{- end }} + {{- if .Values.extraEnvVars }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraEnvVars "context" $) | nindent 12 }} + {{- end }} + envFrom: + - configMapRef: + name: {{ printf "%s-env-vars" (include "common.names.fullname" .) }} + {{- if .Values.extraEnvVarsCM }} + - configMapRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsCM "context" $) }} + {{- end }} + {{- if .Values.extraEnvVarsSecret }} + - secretRef: + name: {{ include "common.tplvalues.render" (dict "value" .Values.extraEnvVarsSecret "context" $) }} + {{- end }} + {{- if .Values.resources }} + resources: {{- toYaml .Values.resources | nindent 12 }} + {{- else if ne .Values.resourcesPreset "none" }} + resources: {{- include "common.resources.preset" (dict "type" .Values.resourcesPreset) | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: {{ .Values.containerPorts.http }} + protocol: TCP + {{- if .Values.tls.enabled }} + - name: https + containerPort: {{ .Values.containerPorts.https }} + protocol: TCP + {{- end }} + {{- if and (.Values.metrics.enabled) (not (eq (.Values.containerPorts.http | int) (.Values.containerPorts.metrics | int) )) }} + - name: metrics + containerPort: {{ .Values.containerPorts.metrics }} + protocol: TCP + {{- end}} + {{- /* Constant in code: https://github.com/keycloak/keycloak/blob/ce8e925c1ad9bf7a3180d1496e181aeea0ab5f8a/operator/src/main/java/org/keycloak/operator/Constants.java#L60 */}} + - name: discovery + containerPort: 7800 + {{- if .Values.extraContainerPorts }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraContainerPorts "context" $) | nindent 12 }} + {{- end }} + {{- if not .Values.diagnosticMode.enabled }} + {{- if .Values.customStartupProbe }} + startupProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customStartupProbe "context" $) | nindent 12 }} + {{- else if .Values.startupProbe.enabled }} + startupProbe: {{- omit .Values.startupProbe "enabled" | toYaml | nindent 12 }} + httpGet: + path: {{ .Values.httpRelativePath }} + port: http + {{- end }} + {{- if .Values.customLivenessProbe }} + livenessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customLivenessProbe "context" $) | nindent 12 }} + {{- else if .Values.livenessProbe.enabled }} + livenessProbe: {{- omit .Values.livenessProbe "enabled" | toYaml | nindent 12 }} + tcpSocket: + port: http + {{- end }} + {{- if .Values.customReadinessProbe }} + readinessProbe: {{- include "common.tplvalues.render" (dict "value" .Values.customReadinessProbe "context" $) | nindent 12 }} + {{- else if .Values.readinessProbe.enabled }} + readinessProbe: {{- omit .Values.readinessProbe "enabled" | toYaml | nindent 12 }} + httpGet: + path: {{ .Values.httpRelativePath }}realms/{{ .Values.adminRealm | default "master" }} + port: http + {{- end }} + {{- end }} + volumeMounts: + - name: empty-dir + mountPath: /tmp + subPath: tmp-dir + - name: empty-dir + mountPath: /bitnami/keycloak + subPath: app-volume-dir + - name: empty-dir + mountPath: /opt/bitnami/keycloak/conf + subPath: app-conf-dir + - name: empty-dir + mountPath: /opt/bitnami/keycloak/lib/quarkus + subPath: app-quarkus-dir + - name: empty-dir + mountPath: /opt/bitnami/keycloak/data + subPath: app-data-dir + - name: empty-dir + mountPath: /opt/bitnami/keycloak/providers + subPath: app-providers-dir + {{- if or .Values.configuration .Values.existingConfigmap }} + - name: keycloak-config + mountPath: /bitnami/keycloak/conf/keycloak.conf + subPath: keycloak.conf + {{- end }} + {{- if .Values.tls.enabled }} + - name: certificates + mountPath: /opt/bitnami/keycloak/certs + readOnly: true + {{- end }} + {{- if .Values.customCaExistingSecret }} + - name: custom-ca + mountPath: /opt/bitnami/keycloak/custom-ca + readOnly: true + {{- end }} + {{- if .Values.spi.existingSecret }} + - name: spi-certificates + mountPath: /opt/bitnami/keycloak/spi-certs + readOnly: true + {{- end }} + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + mountPath: /docker-entrypoint-initdb.d + {{- end }} + {{- if .Values.extraVolumeMounts }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumeMounts "context" $) | nindent 12 }} + {{- end }} + {{- if .Values.sidecars }} + {{- include "common.tplvalues.render" ( dict "value" .Values.sidecars "context" $) | nindent 8 }} + {{- end }} + volumes: + - name: empty-dir + emptyDir: {} + {{- if or .Values.configuration .Values.existingConfigmap }} + - name: keycloak-config + configMap: + name: {{ include "keycloak.configmapName" . }} + {{- end }} + {{- if .Values.tls.enabled }} + - name: certificates + secret: + secretName: {{ include "keycloak.tlsSecretName" . }} + defaultMode: 420 + {{- end }} + {{- if .Values.customCaExistingSecret }} + - name: custom-ca + secret: + secretName: {{ .Values.customCaExistingSecret }} + defaultMode: 420 + {{- end }} + {{- if .Values.spi.existingSecret }} + - name: spi-certificates + secret: + secretName: {{ .Values.spi.existingSecret }} + defaultMode: 420 + {{- end }} + {{- if or .Values.initdbScriptsConfigMap .Values.initdbScripts }} + - name: custom-init-scripts + configMap: + name: {{ include "keycloak.initdbScriptsCM" . }} + {{- end }} + {{- if .Values.extraVolumes }} + {{- include "common.tplvalues.render" (dict "value" .Values.extraVolumes "context" $) | nindent 8 }} + {{- end }} diff --git a/charts/keycloak/keycloak/templates/tls-pass-secret.yaml b/charts/keycloak/keycloak/templates/tls-pass-secret.yaml new file mode 100644 index 0000000..cedcab5 --- /dev/null +++ b/charts/keycloak/keycloak/templates/tls-pass-secret.yaml @@ -0,0 +1,43 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if and (or .Values.tls.keystorePassword .Values.tls.truststorePassword) (not .Values.tls.passwordsSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-tls-passwords" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + {{- if .Values.tls.keystorePassword }} + tls-keystore-password: {{ .Values.tls.keystorePassword | b64enc | quote }} + {{- end }} + {{- if .Values.tls.truststorePassword }} + tls-truststore-password: {{ .Values.tls.truststorePassword | b64enc | quote }} + {{- end }} +--- +{{- end }} +{{- if and .Values.spi.truststorePassword (not .Values.spi.passwordsSecret) }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ printf "%s-spi-passwords" (include "common.names.fullname" .) | trunc 63 | trimSuffix "-" }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + {{- if .Values.spi.truststorePassword }} + spi-truststore-password: {{ .Values.spi.truststorePassword | b64enc | quote }} + {{- end }} +{{- end }} diff --git a/charts/keycloak/keycloak/templates/tls-secret.yaml b/charts/keycloak/keycloak/templates/tls-secret.yaml new file mode 100644 index 0000000..91e0647 --- /dev/null +++ b/charts/keycloak/keycloak/templates/tls-secret.yaml @@ -0,0 +1,71 @@ +{{- /* +Copyright Broadcom, Inc. All Rights Reserved. +SPDX-License-Identifier: APACHE-2.0 +*/}} + +{{- if .Values.ingress.enabled }} +{{- if .Values.ingress.secrets }} +{{- range .Values.ingress.secrets }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "common.tplvalues.render" ( dict "value" .name "context" $ ) }} + namespace: {{ include "common.names.namespace" $ | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" $.Values.commonLabels "context" $ ) | nindent 4 }} + {{- if $.Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" $.Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + tls.crt: {{ include "common.tplvalues.render" ( dict "value" .certificate "context" $ ) | b64enc }} + tls.key: {{ include "common.tplvalues.render" ( dict "value" .key "context" $ ) | b64enc }} +--- +{{- end }} +{{- end }} +{{- if and .Values.ingress.tls .Values.ingress.selfSigned }} +{{- $secretName := printf "%s-tls" .Values.ingress.hostname }} +{{- $ca := genCA "keycloak-ca" 365 }} +{{- $cert := genSignedCert (tpl .Values.ingress.hostname .) nil (list (tpl .Values.ingress.hostname .)) 365 $ca }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: kubernetes.io/tls +data: + tls.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.crt" "defaultValue" $cert.Cert "context" $) }} + tls.key: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.key" "defaultValue" $cert.Key "context" $) }} + ca.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "ca.crt" "defaultValue" $ca.Cert "context" $) }} +{{- end }} +{{- end }} +{{- if (include "keycloak.createTlsSecret" $) }} +{{- $secretName := printf "%s-crt" (include "common.names.fullname" .) }} +{{- $ca := genCA "keycloak-ca" 365 }} +{{- $releaseNamespace := include "common.names.namespace" . }} +{{- $clusterDomain := .Values.clusterDomain }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ $secretName }} + namespace: {{ include "common.names.namespace" . | quote }} + labels: {{- include "common.labels.standard" ( dict "customLabels" .Values.commonLabels "context" $ ) | nindent 4 }} + app.kubernetes.io/component: keycloak + {{- if .Values.commonAnnotations }} + annotations: {{- include "common.tplvalues.render" ( dict "value" .Values.commonAnnotations "context" $ ) | nindent 4 }} + {{- end }} +type: Opaque +data: + {{- $replicaCount := int .Values.replicaCount }} + {{- $svcName := include "common.names.fullname" . }} + {{- $altNames := list (printf "%s.%s.svc.%s" $svcName $releaseNamespace $clusterDomain) (printf "%s.%s" $svcName $releaseNamespace) $svcName }} + {{- $cert := genSignedCert $svcName nil $altNames 365 $ca }} + tls.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.crt" "defaultValue" $cert.Cert "context" $) }} + tls.key: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "tls.key" "defaultValue" $cert.Key "context" $) }} + ca.crt: {{ include "common.secrets.lookup" (dict "secret" $secretName "key" "ca.crt" "defaultValue" $ca.Cert "context" $) }} +{{- end }} + diff --git a/charts/keycloak/keycloak/values.yaml b/charts/keycloak/keycloak/values.yaml new file mode 100644 index 0000000..1a3adbb --- /dev/null +++ b/charts/keycloak/keycloak/values.yaml @@ -0,0 +1,1383 @@ +# Copyright Broadcom, Inc. All Rights Reserved. +# SPDX-License-Identifier: APACHE-2.0 + +## @section Global parameters +## Global Docker image parameters +## Please, note that this will override the image parameters, including dependencies, configured to use the global value +## Current available global Docker image parameters: imageRegistry, imagePullSecrets and storageClass +## + +## @param global.imageRegistry Global Docker image registry +## @param global.imagePullSecrets Global Docker registry secret names as an array +## @param global.defaultStorageClass Global default StorageClass for Persistent Volume(s) +## @param global.storageClass DEPRECATED: use global.defaultStorageClass instead +## +global: + imageRegistry: "" + ## E.g. + ## imagePullSecrets: + ## - myRegistryKeySecretName + ## + imagePullSecrets: [] + defaultStorageClass: "" + storageClass: "" + ## Compatibility adaptations for Kubernetes platforms + ## + compatibility: + ## Compatibility adaptations for Openshift + ## + openshift: + ## @param global.compatibility.openshift.adaptSecurityContext Adapt the securityContext sections of the deployment to make them compatible with Openshift restricted-v2 SCC: remove runAsUser, runAsGroup and fsGroup and let the platform use their allowed default IDs. Possible values: auto (apply if the detected running cluster is Openshift), force (perform the adaptation always), disabled (do not perform adaptation) + ## + adaptSecurityContext: auto +## @section Common parameters +## + +## @param kubeVersion Force target Kubernetes version (using Helm capabilities if not set) +## +kubeVersion: "" +## @param nameOverride String to partially override common.names.fullname +## +nameOverride: "" +## @param fullnameOverride String to fully override common.names.fullname +## +fullnameOverride: "" +## @param namespaceOverride String to fully override common.names.namespace +## +namespaceOverride: "" +## @param commonLabels Labels to add to all deployed objects +## +commonLabels: {} +## @param enableServiceLinks If set to false, disable Kubernetes service links in the pod spec +## Ref: https://kubernetes.io/docs/tutorials/services/connect-applications-service/#accessing-the-service +## +enableServiceLinks: true +## @param commonAnnotations Annotations to add to all deployed objects +## +commonAnnotations: {} +## @param dnsPolicy DNS Policy for pod +## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ +## E.g. +## dnsPolicy: ClusterFirst +dnsPolicy: "" +## @param dnsConfig DNS Configuration pod +## ref: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/ +## E.g. +## dnsConfig: +## options: +## - name: ndots +## value: "4" +dnsConfig: {} +## @param clusterDomain Default Kubernetes cluster domain +## +clusterDomain: cluster.local +## @param extraDeploy Array of extra objects to deploy with the release +## +extraDeploy: [] +## Enable diagnostic mode in the statefulset +## +diagnosticMode: + ## @param diagnosticMode.enabled Enable diagnostic mode (all probes will be disabled and the command will be overridden) + ## + enabled: false + ## @param diagnosticMode.command Command to override all containers in the the statefulset + ## + command: + - sleep + ## @param diagnosticMode.args Args to override all containers in the the statefulset + ## + args: + - infinity +## @section Keycloak parameters + +## Bitnami Keycloak image version +## ref: https://hub.docker.com/r/bitnami/keycloak/tags/ +## @param image.registry [default: REGISTRY_NAME] Keycloak image registry +## @param image.repository [default: REPOSITORY_NAME/keycloak] Keycloak image repository +## @skip image.tag Keycloak image tag (immutable tags are recommended) +## @param image.digest Keycloak image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag +## @param image.pullPolicy Keycloak image pull policy +## @param image.pullSecrets Specify docker-registry secret names as an array +## @param image.debug Specify if debug logs should be enabled +## +image: + registry: docker.io + repository: bitnami/keycloak + tag: 26.0.6-debian-12-r0 + digest: "" + ## Specify a imagePullPolicy + ## ref: https://kubernetes.io/docs/concepts/containers/images/#pre-pulled-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## Example: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## Set to true if you would like to see extra information on logs + ## + debug: false +## Keycloak authentication parameters +## ref: https://github.com/bitnami/containers/tree/main/bitnami/keycloak#admin-credentials +## +auth: + ## @param auth.adminUser Keycloak administrator user + ## + adminUser: user + ## @param auth.adminPassword Keycloak administrator password for the new user + ## + adminPassword: "" + ## @param auth.existingSecret Existing secret containing Keycloak admin password + ## + existingSecret: "" + ## @param auth.passwordSecretKey Key where the Keycloak admin password is being stored inside the existing secret. + ## + passwordSecretKey: "" + ## @param auth.annotations Additional custom annotations for Keycloak auth secret object + ## + annotations: {} +## Custom Certificates +## @param customCaExistingSecret Name of the secret containing the Keycloak custom CA certificates. The secret will be mounted as a directory and configured using KC_TRUSTSTORE_PATHS. +## https://www.keycloak.org/server/keycloak-truststore +## Could be created like this: kubectl create secret generic secretName --from-file=./certificateToMerge.pem +customCaExistingSecret: "" +## HTTPS settings +## ref: https://github.com/bitnami/containers/tree/main/bitnami/keycloak#tls-encryption +## +tls: + ## @param tls.enabled Enable TLS encryption. Required for HTTPs traffic. + ## + enabled: false + ## @param tls.autoGenerated Generate automatically self-signed TLS certificates. Currently only supports PEM certificates + ## + autoGenerated: false + ## @param tls.existingSecret Existing secret containing the TLS certificates per Keycloak replica + ## Create this secret following the steps below: + ## 1) Generate your truststore and keystore files (more info at https://www.keycloak.org/docs/latest/server_installation/#_setting_up_ssl) + ## 2) Rename your truststore to `keycloak.truststore.jks` or use a different name overwriting the value 'tls.truststoreFilename'. + ## 3) Rename your keystores to `keycloak.keystore.jks` or use a different name overwriting the value 'tls.keystoreFilename'. + ## 4) Run the command below where SECRET_NAME is the name of the secret you want to create: + ## kubectl create secret generic SECRET_NAME --from-file=./keycloak.truststore.jks --from-file=./keycloak.keystore.jks + ## NOTE: If usePem enabled, make sure the PEM key and cert are named 'tls.key' and 'tls.crt' respectively. + ## + existingSecret: "" + ## @param tls.usePem Use PEM certificates as input instead of PKS12/JKS stores + ## If "true", the Keycloak chart will look for the files tls.key and tls.crt inside the secret provided with 'existingSecret'. + ## + usePem: false + ## @param tls.truststoreFilename Truststore filename inside the existing secret + ## + truststoreFilename: "keycloak.truststore.jks" + ## @param tls.keystoreFilename Keystore filename inside the existing secret + ## + keystoreFilename: "keycloak.keystore.jks" + ## @param tls.keystorePassword Password to access the keystore when it's password-protected + ## + keystorePassword: "" + ## @param tls.truststorePassword Password to access the truststore when it's password-protected + ## + truststorePassword: "" + ## @param tls.passwordsSecret Secret containing the Keystore and Truststore passwords. + ## The secret must have "tls-keystore-password" and "tls-truststore-password" keys for the keystore and truststore respectively. + ## + passwordsSecret: "" +## SPI TLS settings +## ref: https://www.keycloak.org/server/keycloak-truststore +## +spi: + ## @param spi.existingSecret Existing secret containing the Keycloak truststore for SPI connection over HTTPS/TLS + ## Create this secret following the steps below: + ## 1) Rename your truststore to `keycloak-spi.truststore.jks` or use a different name overwriting the value 'spi.truststoreFilename'. + ## 2) Run the command below where SECRET_NAME is the name of the secret you want to create: + ## kubectl create secret generic SECRET_NAME --from-file=./keycloak-spi.truststore.jks --from-file=./keycloak.keystore.jks + ## + existingSecret: "" + ## @param spi.truststorePassword Password to access the truststore when it's password-protected + ## + truststorePassword: "" + ## @param spi.truststoreFilename Truststore filename inside the existing secret + ## + truststoreFilename: "keycloak-spi.truststore.jks" + ## @param spi.passwordsSecret Secret containing the SPI Truststore passwords. + ## The secret must have "spi-truststore-password" key. + ## + passwordsSecret: "" + ## @param spi.hostnameVerificationPolicy Verify the hostname of the server's certificate. Allowed values: "ANY", "WILDCARD", "STRICT". + ## + hostnameVerificationPolicy: "" +## @param adminRealm Name of the admin realm +## +adminRealm: "master" +## @param production Run Keycloak in production mode. TLS configuration is required except when using proxy=edge. +## +production: false +## @param proxyHeaders Set Keycloak proxy headers +## +proxyHeaders: "" +## @param proxy reverse Proxy mode edge, reencrypt, passthrough or none +## DEPRECATED: use proxyHeaders instead +## ref: https://www.keycloak.org/server/reverseproxy +## +proxy: "" +## @param httpRelativePath Set the path relative to '/' for serving resources. Useful if you are migrating from older version which were using '/auth/' +## ref: https://www.keycloak.org/migration/migrating-to-quarkus#_default_context_path_changed +## +httpRelativePath: "/" +## Keycloak Service Discovery settings +## ref: https://github.com/bitnami/containers/tree/main/bitnami/keycloak#cluster-configuration +## +## @param configuration Keycloak Configuration. Auto-generated based on other parameters when not specified +## Specify content for keycloak.conf +## NOTE: This will override configuring Keycloak based on environment variables (including those set by the chart) +## The keycloak.conf is auto-generated based on other parameters when this parameter is not specified +## +## Example: +## configuration: |- +## foo: bar +## baz: +## +configuration: "" +## @param existingConfigmap Name of existing ConfigMap with Keycloak configuration +## NOTE: When it's set the configuration parameter is ignored +## +existingConfigmap: "" +## @param extraStartupArgs Extra default startup args +## +extraStartupArgs: "" +## @param enableDefaultInitContainers Deploy default init containers +## Disable this parameter could be helpful for 3rd party images e.g native Keycloak image. +## +enableDefaultInitContainers: true +## @param initdbScripts Dictionary of initdb scripts +## Specify dictionary of scripts to be run at first boot +## ref: https://github.com/bitnami/containers/tree/main/bitnami/keycloak#initializing-a-new-instance +## Example: +## initdbScripts: +## my_init_script.sh: | +## #!/bin/bash +## echo "Do something." +## +initdbScripts: {} +## @param initdbScriptsConfigMap ConfigMap with the initdb scripts (Note: Overrides `initdbScripts`) +## +initdbScriptsConfigMap: "" +## @param command Override default container command (useful when using custom images) +## +command: [] +## @param args Override default container args (useful when using custom images) +## +args: [] +## @param extraEnvVars Extra environment variables to be set on Keycloak container +## Example: +## extraEnvVars: +## - name: FOO +## value: "bar" +## +extraEnvVars: [] +## @param extraEnvVarsCM Name of existing ConfigMap containing extra env vars +## +extraEnvVarsCM: "" +## @param extraEnvVarsSecret Name of existing Secret containing extra env vars +## +extraEnvVarsSecret: "" +## @section Keycloak statefulset parameters + +## @param replicaCount Number of Keycloak replicas to deploy +## +replicaCount: 1 +## @param revisionHistoryLimitCount Number of controller revisions to keep +## +revisionHistoryLimitCount: 10 +## @param containerPorts.http Keycloak HTTP container port +## @param containerPorts.https Keycloak HTTPS container port +## @param containerPorts.metrics Keycloak metrics container port +## +containerPorts: + http: 8080 + https: 8443 + metrics: 9000 +## @param extraContainerPorts Optionally specify extra list of additional port-mappings for Keycloak container +## +extraContainerPorts: [] +## @param statefulsetAnnotations Optionally add extra annotations on the statefulset resource +statefulsetAnnotations: {} +## +## Keycloak pods' SecurityContext +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod +## @param podSecurityContext.enabled Enabled Keycloak pods' Security Context +## @param podSecurityContext.fsGroupChangePolicy Set filesystem group change policy +## @param podSecurityContext.sysctls Set kernel settings using the sysctl interface +## @param podSecurityContext.supplementalGroups Set filesystem extra groups +## @param podSecurityContext.fsGroup Set Keycloak pod's Security Context fsGroup +## +podSecurityContext: + enabled: true + fsGroupChangePolicy: Always + sysctls: [] + supplementalGroups: [] + fsGroup: 1001 +## Keycloak containers' Security Context +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container +## @param containerSecurityContext.enabled Enabled containers' Security Context +## @param containerSecurityContext.seLinuxOptions [object,nullable] Set SELinux options in container +## @param containerSecurityContext.runAsUser Set containers' Security Context runAsUser +## @param containerSecurityContext.runAsGroup Set containers' Security Context runAsGroup +## @param containerSecurityContext.runAsNonRoot Set container's Security Context runAsNonRoot +## @param containerSecurityContext.privileged Set container's Security Context privileged +## @param containerSecurityContext.readOnlyRootFilesystem Set container's Security Context readOnlyRootFilesystem +## @param containerSecurityContext.allowPrivilegeEscalation Set container's Security Context allowPrivilegeEscalation +## @param containerSecurityContext.capabilities.drop List of capabilities to be dropped +## @param containerSecurityContext.seccompProfile.type Set container's Security Context seccomp profile +## +containerSecurityContext: + enabled: true + seLinuxOptions: {} + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + privileged: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: "RuntimeDefault" +## Keycloak resource requests and limits +## ref: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ +## @param resourcesPreset Set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). This is ignored if resources is set (resources is recommended for production). +## More information: https://github.com/bitnami/charts/blob/main/bitnami/common/templates/_resources.tpl#L15 +## +resourcesPreset: "small" +## @param resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) +## Example: +## resources: +## requests: +## cpu: 2 +## memory: 512Mi +## limits: +## cpu: 3 +## memory: 1024Mi +## +resources: {} +## Configure extra options for Keycloak containers' liveness, readiness and startup probes +## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/#configure-probes +## @param livenessProbe.enabled Enable livenessProbe on Keycloak containers +## @param livenessProbe.initialDelaySeconds Initial delay seconds for livenessProbe +## @param livenessProbe.periodSeconds Period seconds for livenessProbe +## @param livenessProbe.timeoutSeconds Timeout seconds for livenessProbe +## @param livenessProbe.failureThreshold Failure threshold for livenessProbe +## @param livenessProbe.successThreshold Success threshold for livenessProbe +## +livenessProbe: + enabled: true + initialDelaySeconds: 300 + periodSeconds: 1 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 +## @param readinessProbe.enabled Enable readinessProbe on Keycloak containers +## @param readinessProbe.initialDelaySeconds Initial delay seconds for readinessProbe +## @param readinessProbe.periodSeconds Period seconds for readinessProbe +## @param readinessProbe.timeoutSeconds Timeout seconds for readinessProbe +## @param readinessProbe.failureThreshold Failure threshold for readinessProbe +## @param readinessProbe.successThreshold Success threshold for readinessProbe +## +readinessProbe: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + successThreshold: 1 +## When enabling this, make sure to set initialDelaySeconds to 0 for livenessProbe and readinessProbe +## @param startupProbe.enabled Enable startupProbe on Keycloak containers +## @param startupProbe.initialDelaySeconds Initial delay seconds for startupProbe +## @param startupProbe.periodSeconds Period seconds for startupProbe +## @param startupProbe.timeoutSeconds Timeout seconds for startupProbe +## @param startupProbe.failureThreshold Failure threshold for startupProbe +## @param startupProbe.successThreshold Success threshold for startupProbe +## +startupProbe: + enabled: false + initialDelaySeconds: 30 + periodSeconds: 5 + timeoutSeconds: 1 + failureThreshold: 60 + successThreshold: 1 +## @param customLivenessProbe Custom Liveness probes for Keycloak +## +customLivenessProbe: {} +## @param customReadinessProbe Custom Rediness probes Keycloak +## +customReadinessProbe: {} +## @param customStartupProbe Custom Startup probes for Keycloak +## +customStartupProbe: {} +## @param lifecycleHooks LifecycleHooks to set additional configuration at startup +## +lifecycleHooks: {} +## @param automountServiceAccountToken Mount Service Account token in pod +## +automountServiceAccountToken: true +## @param hostAliases Deployment pod host aliases +## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ +## +hostAliases: [] +## @param podLabels Extra labels for Keycloak pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +## +podLabels: {} +## @param podAnnotations Annotations for Keycloak pods +## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ +## +podAnnotations: {} +## @param podAffinityPreset Pod affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAffinityPreset: "" +## @param podAntiAffinityPreset Pod anti-affinity preset. Ignored if `affinity` is set. Allowed values: `soft` or `hard` +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#inter-pod-affinity-and-anti-affinity +## +podAntiAffinityPreset: soft +## Node affinity preset +## Ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity +## +nodeAffinityPreset: + ## @param nodeAffinityPreset.type Node affinity preset type. Ignored if `affinity` is set. Allowed values: `soft` or `hard` + ## + type: "" + ## @param nodeAffinityPreset.key Node label key to match. Ignored if `affinity` is set. + ## E.g. + ## key: "kubernetes.io/e2e-az-name" + ## + key: "" + ## @param nodeAffinityPreset.values Node label values to match. Ignored if `affinity` is set. + ## E.g. + ## values: + ## - e2e-az1 + ## - e2e-az2 + ## + values: [] +## @param affinity Affinity for pod assignment +## Ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +## +affinity: {} +## @param nodeSelector Node labels for pod assignment +## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ +## +nodeSelector: {} +## @param tolerations Tolerations for pod assignment +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +## @param topologySpreadConstraints Topology Spread Constraints for pod assignment spread across your cluster among failure-domains. Evaluated as a template +## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/#spread-constraints-for-pods +## +topologySpreadConstraints: [] +## @param podManagementPolicy Pod management policy for the Keycloak statefulset +## +podManagementPolicy: Parallel +## @param priorityClassName Keycloak pods' Priority Class Name +## ref: https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ +## +priorityClassName: "" +## @param schedulerName Use an alternate scheduler, e.g. "stork". +## ref: https://kubernetes.io/docs/tasks/administer-cluster/configure-multiple-schedulers/ +## +schedulerName: "" +## @param terminationGracePeriodSeconds Seconds Keycloak pod needs to terminate gracefully +## ref: https://kubernetes.io/docs/concepts/workloads/pods/pod/#termination-of-pods +## +terminationGracePeriodSeconds: "" +## @param updateStrategy.type Keycloak statefulset strategy type +## @param updateStrategy.rollingUpdate Keycloak statefulset rolling update configuration parameters +## ref: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#update-strategies +## +updateStrategy: + type: RollingUpdate + rollingUpdate: {} +## @param minReadySeconds How many seconds a pod needs to be ready before killing the next, during update +## +minReadySeconds: 0 +## @param extraVolumes Optionally specify extra list of additional volumes for Keycloak pods +## +extraVolumes: [] +## @param extraVolumeMounts Optionally specify extra list of additional volumeMounts for Keycloak container(s) +## +extraVolumeMounts: [] +## @param initContainers Add additional init containers to the Keycloak pods +## Example: +## initContainers: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +initContainers: [] +## @param sidecars Add additional sidecar containers to the Keycloak pods +## Example: +## sidecars: +## - name: your-image-name +## image: your-image +## imagePullPolicy: Always +## ports: +## - name: portname +## containerPort: 1234 +## +sidecars: [] +## @section Exposure parameters +## + +## Service configuration +## +service: + ## @param service.type Kubernetes service type + ## + type: ClusterIP + ## @param service.http.enabled Enable http port on service + ## + http: + enabled: true + ## @param service.ports.http Keycloak service HTTP port + ## @param service.ports.https Keycloak service HTTPS port + ## + ports: + http: 80 + https: 443 + ## @param service.nodePorts [object] Specify the nodePort values for the LoadBalancer and NodePort service types. + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport + ## + nodePorts: + http: "" + https: "" + ## @param service.sessionAffinity Control where client requests go, to the same pod or round-robin + ## Values: ClientIP or None + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/ + ## + sessionAffinity: None + ## @param service.sessionAffinityConfig Additional settings for the sessionAffinity + ## sessionAffinityConfig: + ## clientIP: + ## timeoutSeconds: 300 + ## + sessionAffinityConfig: {} + ## @param service.clusterIP Keycloak service clusterIP IP + ## e.g: + ## clusterIP: None + ## + clusterIP: "" + ## @param service.loadBalancerIP loadBalancerIP for the SuiteCRM Service (optional, cloud specific) + ## ref: https://kubernetes.io/docs/concepts/services-networking/service/#type-loadbalancer + ## + loadBalancerIP: "" + ## @param service.loadBalancerSourceRanges Address that are allowed when service is LoadBalancer + ## https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/#restrict-access-for-loadbalancer-service + ## Example: + ## loadBalancerSourceRanges: + ## - 10.10.10.0/24 + ## + loadBalancerSourceRanges: [] + ## @param service.externalTrafficPolicy Enable client source IP preservation + ## ref https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/#preserving-the-client-source-ip + ## + externalTrafficPolicy: Cluster + ## @param service.annotations Additional custom annotations for Keycloak service + ## + annotations: {} + ## @param service.extraPorts Extra port to expose on Keycloak service + ## + extraPorts: [] + # DEPRECATED service.extraHeadlessPorts will be removed in a future release, please use service.headless.extraPorts instead + ## @param service.extraHeadlessPorts Extra ports to expose on Keycloak headless service + ## + extraHeadlessPorts: [] + ## Headless service properties + ## + headless: + ## @param service.headless.annotations Annotations for the headless service. + ## + annotations: {} + ## @param service.headless.extraPorts Extra ports to expose on Keycloak headless service + ## + extraPorts: [] +## Keycloak ingress parameters +## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/ +## +ingress: + ## @param ingress.enabled Enable ingress record generation for Keycloak + ## + enabled: false + ## @param ingress.ingressClassName IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) + ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster . + ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/ + ## + ingressClassName: "" + ## @param ingress.pathType Ingress path type + ## + pathType: ImplementationSpecific + ## @param ingress.apiVersion Force Ingress API version (automatically detected if not set) + ## + apiVersion: "" + ## @param ingress.controller The ingress controller type. Currently supports `default` and `gce` + ## leave as `default` for most ingress controllers. + ## set to `gce` if using the GCE ingress controller + ## + controller: default + ## @param ingress.hostname Default host for the ingress record (evaluated as template) + ## + hostname: keycloak.local + ## @param ingress.hostnameStrict Disables dynamically resolving the hostname from request headers. + ## Should always be set to true in production, unless your reverse proxy overwrites the Host header. + ## If enabled, the hostname option needs to be specified. + ## + hostnameStrict: false + ## @param ingress.path [string] Default path for the ingress record (evaluated as template) + ## + path: "{{ .Values.httpRelativePath }}" + ## @param ingress.servicePort Backend service port to use + ## Default is http. Alternative is https. + ## + servicePort: http + ## @param ingress.annotations [object] Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. + ## Use this parameter to set the required annotations for cert-manager, see + ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations + ## e.g: + ## annotations: + ## kubernetes.io/ingress.class: nginx + ## cert-manager.io/cluster-issuer: cluster-issuer-name + ## + annotations: {} + ## @param ingress.labels Additional labels for the Ingress resource. + ## e.g: + ## labels: + ## app: keycloak + ## + labels: {} + ## @param ingress.tls Enable TLS configuration for the host defined at `ingress.hostname` parameter + ## TLS certificates will be retrieved from a TLS secret with name: `{{- printf "%s-tls" (tpl .Values.ingress.hostname .) }}` + ## You can: + ## - Use the `ingress.secrets` parameter to create this TLS secret + ## - Rely on cert-manager to create it by setting the corresponding annotations + ## - Rely on Helm to create self-signed certificates by setting `ingress.selfSigned=true` + ## + tls: false + ## @param ingress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm + ## + selfSigned: false + ## @param ingress.extraHosts An array with additional hostname(s) to be covered with the ingress record + ## e.g: + ## extraHosts: + ## - name: keycloak.local + ## path: / + ## + extraHosts: [] + ## @param ingress.extraPaths Any additional arbitrary paths that may need to be added to the ingress under the main host. + ## For example: The ALB ingress controller requires a special rule for handling SSL redirection. + ## extraPaths: + ## - path: /* + ## backend: + ## serviceName: ssl-redirect + ## servicePort: use-annotation + ## + extraPaths: [] + ## @param ingress.extraTls The tls configuration for additional hostnames to be covered with this ingress record. + ## see: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls + ## extraTls: + ## - hosts: + ## - keycloak.local + ## secretName: keycloak.local-tls + ## + extraTls: [] + ## @param ingress.secrets If you're providing your own certificates, please use this to add the certificates as secrets + ## key and certificate should start with -----BEGIN CERTIFICATE----- or + ## -----BEGIN RSA PRIVATE KEY----- + ## + ## name should line up with a tlsSecret set further up + ## If you're using cert-manager, this is unneeded, as it will create the secret for you if it is not set + ## + ## It is also possible to create and manage the certificates outside of this helm chart + ## Please see README.md for more information + ## e.g: + ## - name: keycloak.local-tls + ## key: + ## certificate: + ## + secrets: [] + ## @param ingress.extraRules Additional rules to be covered with this ingress record + ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules + ## e.g: + ## extraRules: + ## - host: keycloak.local + ## http: + ## path: / + ## backend: + ## service: + ## name: keycloak + ## port: + ## name: http + ## + extraRules: [] +## Keycloak admin ingress parameters +## ref: https://kubernetes.io/docs/user-guide/ingress/ +## +adminIngress: + ## @param adminIngress.enabled Enable admin ingress record generation for Keycloak + ## + enabled: false + ## @param adminIngress.ingressClassName IngressClass that will be be used to implement the Ingress (Kubernetes 1.18+) + ## This is supported in Kubernetes 1.18+ and required if you have more than one IngressClass marked as the default for your cluster . + ## ref: https://kubernetes.io/blog/2020/04/02/improvements-to-the-ingress-api-in-kubernetes-1.18/ + ## + ingressClassName: "" + ## @param adminIngress.pathType Ingress path type + ## + pathType: ImplementationSpecific + ## @param adminIngress.apiVersion Force Ingress API version (automatically detected if not set) + ## + apiVersion: "" + ## @param adminIngress.controller The ingress controller type. Currently supports `default` and `gce` + ## leave as `default` for most ingress controllers. + ## set to `gce` if using the GCE ingress controller + ## + controller: default + ## @param adminIngress.hostname Default host for the admin ingress record (evaluated as template) + ## + hostname: keycloak.local + ## @param adminIngress.path [string] Default path for the admin ingress record (evaluated as template) + ## + path: "{{ .Values.httpRelativePath }}" + ## @param adminIngress.servicePort Backend service port to use + ## Default is http. Alternative is https. + ## + servicePort: http + ## @param adminIngress.annotations [object] Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. + ## Use this parameter to set the required annotations for cert-manager, see + ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations + ## e.g: + ## annotations: + ## kubernetes.io/ingress.class: nginx + ## cert-manager.io/cluster-issuer: cluster-issuer-name + ## + annotations: {} + ## @param adminIngress.labels Additional labels for the Ingress resource. + ## e.g: + ## labels: + ## app: keycloak + ## + labels: {} + ## @param adminIngress.tls Enable TLS configuration for the host defined at `adminIngress.hostname` parameter + ## TLS certificates will be retrieved from a TLS secret with name: `{{- printf "%s-tls" (tpl .Values.adminIngress.hostname .) }}` + ## You can: + ## - Use the `adminIngress.secrets` parameter to create this TLS secret + ## - Rely on cert-manager to create it by setting the corresponding annotations + ## - Rely on Helm to create self-signed certificates by setting `adminIngress.selfSigned=true` + ## + tls: false + ## @param adminIngress.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm + ## + selfSigned: false + ## @param adminIngress.extraHosts An array with additional hostname(s) to be covered with the admin ingress record + ## e.g: + ## extraHosts: + ## - name: keycloak.local + ## path: / + ## + extraHosts: [] + ## @param adminIngress.extraPaths Any additional arbitrary paths that may need to be added to the admin ingress under the main host. + ## For example: The ALB ingress controller requires a special rule for handling SSL redirection. + ## extraPaths: + ## - path: /* + ## backend: + ## serviceName: ssl-redirect + ## servicePort: use-annotation + ## + extraPaths: [] + ## @param adminIngress.extraTls The tls configuration for additional hostnames to be covered with this ingress record. + ## see: https://kubernetes.io/docs/concepts/services-networking/ingress/#tls + ## extraTls: + ## - hosts: + ## - keycloak.local + ## secretName: keycloak.local-tls + ## + extraTls: [] + ## @param adminIngress.secrets If you're providing your own certificates, please use this to add the certificates as secrets + ## key and certificate should start with -----BEGIN CERTIFICATE----- or + ## -----BEGIN RSA PRIVATE KEY----- + ## + ## name should line up with a tlsSecret set further up + ## If you're using cert-manager, this is unneeded, as it will create the secret for you if it is not set + ## + ## It is also possible to create and manage the certificates outside of this helm chart + ## Please see README.md for more information + ## e.g: + ## - name: keycloak.local-tls + ## key: + ## certificate: + ## + secrets: [] + ## @param adminIngress.extraRules Additional rules to be covered with this ingress record + ## ref: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-rules + ## e.g: + ## extraRules: + ## - host: keycloak.local + ## http: + ## path: / + ## backend: + ## service: + ## name: keycloak + ## port: + ## name: http + ## + extraRules: [] +## Network Policy configuration +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +## +networkPolicy: + ## @param networkPolicy.enabled Specifies whether a NetworkPolicy should be created + ## + enabled: true + ## @param networkPolicy.allowExternal Don't require server label for connections + ## The Policy model to apply. When set to false, only pods with the correct + ## server label will have network access to the ports server is listening + ## on. When true, server will accept connections from any source + ## (with the correct destination port). + ## + allowExternal: true + ## @param networkPolicy.allowExternalEgress Allow the pod to access any range of port and all destinations. + ## + allowExternalEgress: true + ## @param networkPolicy.kubeAPIServerPorts [array] List of possible endpoints to kube-apiserver (limit to your cluster settings to increase security) + ## + kubeAPIServerPorts: [443, 6443, 8443] + ## @param networkPolicy.extraIngress [array] Add extra ingress rules to the NetworkPolicy + ## e.g: + ## extraIngress: + ## - ports: + ## - port: 1234 + ## from: + ## - podSelector: + ## - matchLabels: + ## - role: frontend + ## - podSelector: + ## - matchExpressions: + ## - key: role + ## operator: In + ## values: + ## - frontend + extraIngress: [] + ## @param networkPolicy.extraEgress [array] Add extra ingress rules to the NetworkPolicy + ## e.g: + ## extraEgress: + ## - ports: + ## - port: 1234 + ## to: + ## - podSelector: + ## - matchLabels: + ## - role: frontend + ## - podSelector: + ## - matchExpressions: + ## - key: role + ## operator: In + ## values: + ## - frontend + ## + extraEgress: [] + ## @param networkPolicy.ingressNSMatchLabels [object] Labels to match to allow traffic from other namespaces + ## @param networkPolicy.ingressNSPodMatchLabels [object] Pod labels to match to allow traffic from other namespaces + ## + ingressNSMatchLabels: {} + ingressNSPodMatchLabels: {} +## @section RBAC parameter +## Specifies whether a ServiceAccount should be created +## +serviceAccount: + ## @param serviceAccount.create Enable the creation of a ServiceAccount for Keycloak pods + ## + create: true + ## @param serviceAccount.name Name of the created ServiceAccount + ## If not set and create is true, a name is generated using the fullname template + ## + name: "" + ## @param serviceAccount.automountServiceAccountToken Auto-mount the service account token in the pod + ## + automountServiceAccountToken: false + ## @param serviceAccount.annotations Additional custom annotations for the ServiceAccount + ## + annotations: {} + ## @param serviceAccount.extraLabels Additional labels for the ServiceAccount + ## + extraLabels: {} +## Specifies whether RBAC resources should be created +## +rbac: + ## @param rbac.create Whether to create and use RBAC resources or not + ## + create: false + ## @param rbac.rules Custom RBAC rules + ## Example: + ## rules: + ## - apiGroups: + ## - "" + ## resources: + ## - pods + ## verbs: + ## - get + ## - list + ## + rules: [] +## @section Other parameters +## + +## Keycloak Pod Disruption Budget configuration +## ref: https://kubernetes.io/docs/tasks/run-application/configure-pdb/ +## +pdb: + ## @param pdb.create Enable/disable a Pod Disruption Budget creation + ## + create: true + ## @param pdb.minAvailable Minimum number/percentage of pods that should remain scheduled + ## + minAvailable: "" + ## @param pdb.maxUnavailable Maximum number/percentage of pods that may be made unavailable + ## + maxUnavailable: "" +## Keycloak Autoscaling configuration +## ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/ +## @param autoscaling.enabled Enable autoscaling for Keycloak +## @param autoscaling.minReplicas Minimum number of Keycloak replicas +## @param autoscaling.maxReplicas Maximum number of Keycloak replicas +## @param autoscaling.targetCPU Target CPU utilization percentage +## @param autoscaling.targetMemory Target Memory utilization percentage +## +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 11 + targetCPU: "" + targetMemory: "" + ## HPA Scaling Behavior + ## ref: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/#configurable-scaling-behavior + ## + behavior: + ## HPA behavior when scaling up + ## @param autoscaling.behavior.scaleUp.stabilizationWindowSeconds The number of seconds for which past recommendations should be considered while scaling up + ## @param autoscaling.behavior.scaleUp.selectPolicy The priority of policies that the autoscaler will apply when scaling up + ## @param autoscaling.behavior.scaleUp.policies [array] HPA scaling policies when scaling up + ## e.g: + ## Policy to scale 20% of the pod in 60s + ## - type: Percent + ## value: 20 + ## periodSeconds: 60 + ## + scaleUp: + stabilizationWindowSeconds: 120 + selectPolicy: Max + policies: [] + ## HPA behavior when scaling down + ## @param autoscaling.behavior.scaleDown.stabilizationWindowSeconds The number of seconds for which past recommendations should be considered while scaling down + ## @param autoscaling.behavior.scaleDown.selectPolicy The priority of policies that the autoscaler will apply when scaling down + ## @param autoscaling.behavior.scaleDown.policies [array] HPA scaling policies when scaling down + ## e.g: + ## Policy to scale one pod in 300s + ## - type: Pods + ## value: 1 + ## periodSeconds: 300 + ## + scaleDown: + stabilizationWindowSeconds: 300 + selectPolicy: Max + policies: + - type: Pods + value: 1 + periodSeconds: 300 +## @section Metrics parameters +## + +## Metrics configuration +## +metrics: + ## @param metrics.enabled Enable exposing Keycloak statistics + ## ref: https://github.com/bitnami/containers/tree/main/bitnami/keycloak#enabling-statistics + ## + enabled: false + ## Keycloak metrics service parameters + ## + service: + ports: + ## @param metrics.service.ports.http Metrics service HTTP port + ## + http: 8080 + ## @param metrics.service.ports.https Metrics service HTTPS port + ## + https: 8443 + ## @param metrics.service.ports.metrics Metrics service Metrics port + ## + metrics: 9000 + ## @param metrics.service.annotations [object] Annotations for enabling prometheus to access the metrics endpoints + ## + annotations: + prometheus.io/scrape: "true" + prometheus.io/port: "{{ .Values.metrics.service.ports.metrics }}" + ## @param metrics.service.extraPorts [array] Add additional ports to the keycloak metrics service (i.e. admin port 9000) + ## + extraPorts: [] + ## Prometheus Operator ServiceMonitor configuration + ## + serviceMonitor: + ## @param metrics.serviceMonitor.enabled Create ServiceMonitor Resource for scraping metrics using PrometheusOperator + ## + enabled: false + ## @param metrics.serviceMonitor.port Metrics service HTTP port + ## + port: metrics + ## @param metrics.serviceMonitor.scheme Metrics service scheme + ## + scheme: http + ## @param metrics.serviceMonitor.tlsConfig Metrics service TLS configuration + ## + tlsConfig: {} + ## @param metrics.serviceMonitor.endpoints [array] The endpoint configuration of the ServiceMonitor. Path is mandatory. Port, scheme, tlsConfig, interval, timeout and labellings can be overwritten. + ## + endpoints: + - path: '{{ include "keycloak.httpPath" . }}metrics' + - path: '{{ include "keycloak.httpPath" . }}realms/{{ .Values.adminRealm }}/metrics' + port: http + ## @param metrics.serviceMonitor.path Metrics service HTTP path. Deprecated: Use @param metrics.serviceMonitor.endpoints instead + ## + path: "" + ## @param metrics.serviceMonitor.namespace Namespace which Prometheus is running in + ## + namespace: "" + ## @param metrics.serviceMonitor.interval Interval at which metrics should be scraped + ## + interval: 30s + ## @param metrics.serviceMonitor.scrapeTimeout Specify the timeout after which the scrape is ended + ## e.g: + ## scrapeTimeout: 30s + ## + scrapeTimeout: "" + ## @param metrics.serviceMonitor.labels Additional labels that can be used so ServiceMonitor will be discovered by Prometheus + ## + labels: {} + ## @param metrics.serviceMonitor.selector Prometheus instance selector labels + ## ref: https://github.com/bitnami/charts/tree/main/bitnami/prometheus-operator#prometheus-configuration + ## + selector: {} + ## @param metrics.serviceMonitor.relabelings RelabelConfigs to apply to samples before scraping + ## + relabelings: [] + ## @param metrics.serviceMonitor.metricRelabelings MetricRelabelConfigs to apply to samples before ingestion + ## + metricRelabelings: [] + ## @param metrics.serviceMonitor.honorLabels honorLabels chooses the metric's labels on collisions with target labels + ## + honorLabels: false + ## @param metrics.serviceMonitor.jobLabel The name of the label on the target service to use as the job name in prometheus. + ## + jobLabel: "" + ## Prometheus Operator alert rules configuration + ## + prometheusRule: + ## @param metrics.prometheusRule.enabled Create PrometheusRule Resource for scraping metrics using PrometheusOperator + ## + enabled: false + ## @param metrics.prometheusRule.namespace Namespace which Prometheus is running in + ## + namespace: "" + ## @param metrics.prometheusRule.labels Additional labels that can be used so PrometheusRule will be discovered by Prometheus + ## + labels: {} + ## @param metrics.prometheusRule.groups Groups, containing the alert rules. + ## Example: + ## groups: + ## - name: Keycloak + ## rules: + ## - alert: KeycloakInstanceNotAvailable + ## annotations: + ## message: "Keycloak instance in namespace {{ `{{` }} $labels.namespace {{ `}}` }} has not been available for the last 5 minutes." + ## expr: | + ## absent(kube_pod_status_ready{namespace="{{ include "common.names.namespace" . }}", condition="true"} * on (pod) kube_pod_labels{pod=~"{{ include "common.names.fullname" . }}-\\d+", namespace="{{ include "common.names.namespace" . }}"}) != 0 + ## for: 5m + ## labels: + ## severity: critical + groups: [] +## @section keycloak-config-cli parameters + +## Configuration for keycloak-config-cli +## ref: https://github.com/adorsys/keycloak-config-cli +## +keycloakConfigCli: + ## @param keycloakConfigCli.enabled Whether to enable keycloak-config-cli job + ## + enabled: false + ## Bitnami keycloak-config-cli image + ## ref: https://hub.docker.com/r/bitnami/keycloak-config-cli/tags/ + ## @param keycloakConfigCli.image.registry [default: REGISTRY_NAME] keycloak-config-cli container image registry + ## @param keycloakConfigCli.image.repository [default: REPOSITORY_NAME/keycloak-config-cli] keycloak-config-cli container image repository + ## @skip keycloakConfigCli.image.tag keycloak-config-cli container image tag + ## @param keycloakConfigCli.image.digest keycloak-config-cli container image digest in the way sha256:aa.... Please note this parameter, if set, will override the tag + ## @param keycloakConfigCli.image.pullPolicy keycloak-config-cli container image pull policy + ## @param keycloakConfigCli.image.pullSecrets keycloak-config-cli container image pull secrets + ## + image: + registry: docker.io + repository: bitnami/keycloak-config-cli + tag: 6.1.6-debian-12-r6 + digest: "" + ## Specify a imagePullPolicy + ## ref: https://kubernetes.io/docs/concepts/containers/images/#pre-pulled-images + ## + pullPolicy: IfNotPresent + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## e.g: + ## pullSecrets: + ## - myRegistryKeySecretName + ## + pullSecrets: [] + ## @param keycloakConfigCli.annotations [object] Annotations for keycloak-config-cli job + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + annotations: + helm.sh/hook: "post-install,post-upgrade,post-rollback" + helm.sh/hook-delete-policy: "hook-succeeded,before-hook-creation" + helm.sh/hook-weight: "5" + ## @param keycloakConfigCli.command Command for running the container (set to default if not set). Use array form + ## + command: [] + ## @param keycloakConfigCli.args Args for running the container (set to default if not set). Use array form + ## + args: [] + ## @param keycloakConfigCli.automountServiceAccountToken Mount Service Account token in pod + ## + automountServiceAccountToken: true + ## @param keycloakConfigCli.hostAliases Job pod host aliases + ## https://kubernetes.io/docs/concepts/services-networking/add-entries-to-pod-etc-hosts-with-host-aliases/ + ## + hostAliases: [] + ## Keycloak config CLI resource requests and limits + ## ref: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/ + ## @param keycloakConfigCli.resourcesPreset Set container resources according to one common preset (allowed values: none, nano, micro, small, medium, large, xlarge, 2xlarge). This is ignored if keycloakConfigCli.resources is set (keycloakConfigCli.resources is recommended for production). + ## More information: https://github.com/bitnami/charts/blob/main/bitnami/common/templates/_resources.tpl#L15 + ## + resourcesPreset: "small" + ## @param keycloakConfigCli.resources Set container requests and limits for different resources like CPU or memory (essential for production workloads) + ## Example: + ## resources: + ## requests: + ## cpu: 2 + ## memory: 512Mi + ## limits: + ## cpu: 3 + ## memory: 1024Mi + ## + resources: {} + ## keycloak-config-cli containers' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container + ## @param keycloakConfigCli.containerSecurityContext.enabled Enabled keycloak-config-cli Security Context + ## @param keycloakConfigCli.containerSecurityContext.seLinuxOptions [object,nullable] Set SELinux options in container + ## @param keycloakConfigCli.containerSecurityContext.runAsUser Set keycloak-config-cli Security Context runAsUser + ## @param keycloakConfigCli.containerSecurityContext.runAsGroup Set keycloak-config-cli Security Context runAsGroup + ## @param keycloakConfigCli.containerSecurityContext.runAsNonRoot Set keycloak-config-cli Security Context runAsNonRoot + ## @param keycloakConfigCli.containerSecurityContext.privileged Set keycloak-config-cli Security Context privileged + ## @param keycloakConfigCli.containerSecurityContext.readOnlyRootFilesystem Set keycloak-config-cli Security Context readOnlyRootFilesystem + ## @param keycloakConfigCli.containerSecurityContext.allowPrivilegeEscalation Set keycloak-config-cli Security Context allowPrivilegeEscalation + ## @param keycloakConfigCli.containerSecurityContext.capabilities.drop List of capabilities to be dropped + ## @param keycloakConfigCli.containerSecurityContext.seccompProfile.type Set keycloak-config-cli Security Context seccomp profile + ## + containerSecurityContext: + enabled: true + seLinuxOptions: {} + runAsUser: 1001 + runAsGroup: 1001 + runAsNonRoot: true + privileged: false + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] + seccompProfile: + type: "RuntimeDefault" + ## keycloak-config-cli pods' Security Context + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod + ## @param keycloakConfigCli.podSecurityContext.enabled Enabled keycloak-config-cli pods' Security Context + ## @param keycloakConfigCli.podSecurityContext.fsGroupChangePolicy Set filesystem group change policy + ## @param keycloakConfigCli.podSecurityContext.sysctls Set kernel settings using the sysctl interface + ## @param keycloakConfigCli.podSecurityContext.supplementalGroups Set filesystem extra groups + ## @param keycloakConfigCli.podSecurityContext.fsGroup Set keycloak-config-cli pod's Security Context fsGroup + ## + podSecurityContext: + enabled: true + fsGroupChangePolicy: Always + sysctls: [] + supplementalGroups: [] + fsGroup: 1001 + ## @param keycloakConfigCli.backoffLimit Number of retries before considering a Job as failed + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/job/#pod-backoff-failure-policy + ## + backoffLimit: 1 + ## @param keycloakConfigCli.podLabels Pod extra labels + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + ## + podLabels: {} + ## @param keycloakConfigCli.podAnnotations Annotations for job pod + ## ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/ + ## + podAnnotations: {} + ## @param keycloakConfigCli.extraEnvVars Additional environment variables to set + ## Example: + ## extraEnvVars: + ## - name: FOO + ## value: "bar" + ## + ## @param keycloakConfigCli.nodeSelector Node labels for pod assignment + ## ref: https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/ + ## + nodeSelector: {} + ## + ## @param keycloakConfigCli.podTolerations Tolerations for job pod assignment + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + podTolerations: [] + extraEnvVars: [] + ## @param keycloakConfigCli.extraEnvVarsCM ConfigMap with extra environment variables + ## + extraEnvVarsCM: "" + ## @param keycloakConfigCli.extraEnvVarsSecret Secret with extra environment variables + ## + extraEnvVarsSecret: "" + ## @param keycloakConfigCli.extraVolumes Extra volumes to add to the job + ## + extraVolumes: [] + ## @param keycloakConfigCli.extraVolumeMounts Extra volume mounts to add to the container + ## + extraVolumeMounts: [] + ## @param keycloakConfigCli.initContainers Add additional init containers to the Keycloak config cli pod + ## Example: + ## initContainers: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + initContainers: [] + ## @param keycloakConfigCli.sidecars Add additional sidecar containers to the Keycloak config cli pod + ## Example: + ## sidecars: + ## - name: your-image-name + ## image: your-image + ## imagePullPolicy: Always + ## ports: + ## - name: portname + ## containerPort: 1234 + ## + sidecars: [] + ## @param keycloakConfigCli.configuration keycloak-config-cli realms configuration + ## NOTE: nil keys will be considered files to import locally + ## Example: + ## configuration: + ## realm1.json: | + ## { + ## "realm": "realm1", + ## "clients": [] + ## } + ## realm2.yaml: | + ## realm: realm2 + ## clients: [] + ## + configuration: {} + ## @param keycloakConfigCli.existingConfigmap ConfigMap with keycloak-config-cli configuration + ## NOTE: This will override keycloakConfigCli.configuration + ## + existingConfigmap: "" + ## Automatic Cleanup for Finished Jobs + ## @param keycloakConfigCli.cleanupAfterFinished.enabled Enables Cleanup for Finished Jobs + ## @param keycloakConfigCli.cleanupAfterFinished.seconds Sets the value of ttlSecondsAfterFinished + ## ref: https://kubernetes.io/docs/concepts/workloads/controllers/ttlafterfinished/ + ## + cleanupAfterFinished: + enabled: false + seconds: 600 +## @section Database parameters + +## PostgreSQL chart configuration +## ref: https://github.com/bitnami/charts/blob/main/bitnami/postgresql/values.yaml +## @param postgresql.enabled Switch to enable or disable the PostgreSQL helm chart +## @param postgresql.auth.postgresPassword Password for the "postgres" admin user. Ignored if `auth.existingSecret` with key `postgres-password` is provided +## @param postgresql.auth.username Name for a custom user to create +## @param postgresql.auth.password Password for the custom user to create +## @param postgresql.auth.database Name for a custom database to create +## @param postgresql.auth.existingSecret Name of existing secret to use for PostgreSQL credentials +## @param postgresql.auth.secretKeys.userPasswordKey Name of key in existing secret to use for PostgreSQL credentials. Only used when `auth.existingSecret` is set. +## @param postgresql.architecture PostgreSQL architecture (`standalone` or `replication`) +## +postgresql: + enabled: true + auth: + postgresPassword: "" + username: bn_keycloak + password: "" + database: bitnami_keycloak + existingSecret: "" + secretKeys: + userPasswordKey: password + architecture: standalone +## External PostgreSQL configuration +## All of these values are only used when postgresql.enabled is set to false +## @param externalDatabase.host Database host +## @param externalDatabase.port Database port number +## @param externalDatabase.user Non-root username for Keycloak +## @param externalDatabase.password Password for the non-root username for Keycloak +## @param externalDatabase.database Keycloak database name +## @param externalDatabase.existingSecret Name of an existing secret resource containing the database credentials +## @param externalDatabase.existingSecretHostKey Name of an existing secret key containing the database host name +## @param externalDatabase.existingSecretPortKey Name of an existing secret key containing the database port +## @param externalDatabase.existingSecretUserKey Name of an existing secret key containing the database user +## @param externalDatabase.existingSecretDatabaseKey Name of an existing secret key containing the database name +## @param externalDatabase.existingSecretPasswordKey Name of an existing secret key containing the database credentials +## @param externalDatabase.annotations Additional custom annotations for external database secret object +## +externalDatabase: + host: "" + port: 5432 + user: bn_keycloak + database: bitnami_keycloak + password: "" + existingSecret: "" + existingSecretHostKey: "" + existingSecretPortKey: "" + existingSecretUserKey: "" + existingSecretDatabaseKey: "" + existingSecretPasswordKey: "" + annotations: {} +## @section Keycloak Cache parameters + +## Keycloak cache configuration +## ref: https://www.keycloak.org/server/caching +## @param cache.enabled Switch to enable or disable the keycloak distributed cache for kubernetes. +## NOTE: Set to false to use 'local' cache (only supported when replicaCount=1). +## @param cache.stackName Set infinispan cache stack to use +## @param cache.stackFile Set infinispan cache stack filename to use +## +cache: + enabled: true + stackName: kubernetes + stackFile: "" +## @section Keycloak Logging parameters + +## Keycloak logging configuration +## ref: https://www.keycloak.org/server/logging +## @param logging.output Alternates between the default log output format or json format +## @param logging.level Allowed values as documented: FATAL, ERROR, WARN, INFO, DEBUG, TRACE, ALL, OFF +## +logging: + output: default + level: INFO diff --git a/charts/keycloak/values-overrides.yaml b/charts/keycloak/values-overrides.yaml new file mode 100644 index 0000000..c80e06c --- /dev/null +++ b/charts/keycloak/values-overrides.yaml @@ -0,0 +1,50 @@ +fullnameOverride: keycloak +namespaceOverride: futureporn + +postgresql: + enabled: false + +externalDatabase: + host: postgresql-primary.futureporn.svc.cluster.local + user: postgres + existingSecret: postgresql + port: 5432 + database: keycloak + + +logging: + level: INFO # INFO is default + +service: + type: LoadBalancer + http: + enabled: true + ports: + http: 8080 + annotations: + external-dns.alpha.kubernetes.io/hostname: keycloak.fp.sbtp.xyz + +global: + defaultStorageClass: standard + +proxy: edge + + # curl -o /emptydir/app-providers-dir/patreon-provider.jar -Ls https://github.com/insanity54/keycloak-patreon-provider/releases/download/$tag/keycloak-patreon-provider-$tag.jar + #curl -H "X-Pinggy-No-Screen: 1" -o /emptydir/app-providers-dir/patreon-provider.jar -Ls http://a.free.pinggy.link/keycloak-patreon-provider-$tag.jar +initContainers: + - name: keycloak-patreon-provider-installer + image: alpine/curl:latest + imagePullPolicy: IfNotPresent + command: + - sh + - -c + - | + set -e + tag=1.3.0-SNAPSHOT + echo "Downloading $tag" + curl --max-time 60 -o /emptydir/app-providers-dir/patreon-provider.jar -Ls https://github.com/insanity54/keycloak-patreon-provider/releases/download/$tag/keycloak-patreon-provider-$tag.jar + chown 1001:1001 /emptydir/app-providers-dir/patreon-provider.jar + echo "Download completed with exit code $?" + volumeMounts: + - name: empty-dir + mountPath: /emptydir diff --git a/dockerfiles/next.dockerfile b/dockerfiles/next.dockerfile index dcacc07..f8a3afb 100644 --- a/dockerfiles/next.dockerfile +++ b/dockerfiles/next.dockerfile @@ -25,8 +25,8 @@ RUN pnpm fetch COPY ./services/next ./services/next COPY ./packages/types ./packages/types COPY ./packages/fetchers ./packages/fetchers +COPY ./packages/utils ./packages/utils # COPY ./packages/strapi ./packages/strapi -# COPY ./packages/utils ./packages/utils RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --recursive --frozen-lockfile --prefer-offline diff --git a/packages/fetchers/src/findOrCreateVtuber.ts b/packages/fetchers/src/findOrCreateVtuber.ts index dbb7586..cda4742 100644 --- a/packages/fetchers/src/findOrCreateVtuber.ts +++ b/packages/fetchers/src/findOrCreateVtuber.ts @@ -23,7 +23,7 @@ const columns = [ 'pornhub' ]; -// greetz https://bobbyhadz.com/blog/javascript-remove-trailing-slash-from-string +// @see https://bobbyhadz.com/blog/javascript-remove-trailing-slash-from-string function removeTrailingSlash(url: string) { return url.replace(/\/+$/, ''); } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5ebe31f..f21fe6f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -252,17 +252,15 @@ export interface SegmentResponse { export interface MuxAssetRecord { id: number; - playbackId: string; - assetId: string; + playback_id: string; + asset_id: string; } export interface IMuxAsset { id: number; - attributes: { - playbackId: string; - assetId: string; - } + playback_id: string; + asset_id: string; } export interface IPagination { @@ -285,13 +283,11 @@ export interface IMeta { export interface IPlatformNotification { id: number; - attributes: { - source: string; - platform: string; - date: string; - date2: string; - vtuber: number; - } + source: string; + platform: string; + date: string; + date_2: string; + vtuber: number; } export interface IPlatformNotificationResponse { @@ -309,80 +305,52 @@ export interface IVod { id: string } -export interface ITweetResponse { - id: string -} export interface IStream { id: number; - attributes: { - date: string; - date2: string; - archiveStatus: ArchiveStatus; - vods: IVodsResponse; - cuid: string; - vtuber: IVtuberResponse; - tweet: ITweetResponse; - isChaturbateStream: boolean; - isFanslyStream: boolean; - platformNotifications: IPlatformNotification[]; - } + date: string; + date_2: string; + archive_status: ArchiveStatus; + vods: IVod[]; + cuid: string; + vtuber: IVtuber; + is_chaturbate_stream: boolean; + is_fansly_stream: boolean; + platform_notifications: IPlatformNotification[]; } -export interface IStreamResponse { - data: IStream; - meta: IMeta; - error?: any; -} - -export interface IStreamsResponse { - data: IStream[]; - meta: IMeta; -} export interface IVtuber { id: number; - attributes: { - slug: string; - displayName: string; - chaturbate?: string; - twitter?: string; - patreon?: string; - twitch?: string; - tiktok?: string; - onlyfans?: string; - youtube?: string; - linktree?: string; - carrd?: string; - fansly?: string; - pornhub?: string; - discord?: string; - reddit?: string; - throne?: string; - instagram?: string; - facebook?: string; - merch?: string; - vods: IVod[]; - description1: string; - description2?: string; - image: string; - imageBlur?: string; - themeColor: string; - fanslyId?: string; - chaturbateId?: string; - twitterId?: string; - } -} - -export interface IVtuberResponse { - data: IVtuber; - meta: IMeta; -} - -export interface IVtubersResponse { - data: IVtuber[]; - meta: IMeta; + slug: string; + display_name: string; + chaturbate?: string; + twitter?: string; + patreon?: string; + twitch?: string; + tiktok?: string; + onlyfans?: string; + youtube?: string; + linktree?: string; + carrd?: string; + fansly?: string; + pornhub?: string; + discord?: string; + reddit?: string; + throne?: string; + instagram?: string; + facebook?: string; + merch?: string; + vods: IVod[]; + description_1: string; + description_2?: string; + image: string; + image_blur?: string; + theme_color: string; + fansly_id?: string; + chaturbate_id?: string; + twitter_id?: string; } export type NotificationData = { diff --git a/packages/utils/src/file.ts b/packages/utils/src/file.ts index 412d26a..8d47ed4 100644 --- a/packages/utils/src/file.ts +++ b/packages/utils/src/file.ts @@ -37,7 +37,6 @@ export function getTmpFile(str: string): string { * @returns {String} filePath * * @see https://stackoverflow.com/a/74722818/1004931 - * greetz chatgpt */ export async function download({ url, @@ -84,7 +83,7 @@ export const tmpFileRegex = /^\/tmp\/.*\.jpg$/; /** * getFileChecksum - * greetz https://stackoverflow.com/a/44643479/1004931 + * @see https://stackoverflow.com/a/44643479/1004931 */ export async function getFileChecksum(filePath: string, algorithm = 'md5') { return new Promise((resolve, reject) => { diff --git a/packages/utils/src/image.spec.ts b/packages/utils/src/image.spec.ts index b8ce774..996ec4f 100644 --- a/packages/utils/src/image.spec.ts +++ b/packages/utils/src/image.spec.ts @@ -30,10 +30,10 @@ describe('image', function () { describe('getStoryboard', function () { this.timeout(1000*60*15) it('should accept a URL and return a path to image on disk', async function () { - const url = 'https://futureporn-b2.b-cdn.net/projektmelody-chaturbate-2024-10-31.mp4' + const url = 'https://futureporn-b2.b-cdn.net/projektmelody-chaturbate-2024-12-10.mp4' const imagePath = await getStoryboard(url) expect(imagePath).to.match(/\.png/) }) }) }) -}) \ No newline at end of file +}) diff --git a/packages/utils/src/patron.ts b/packages/utils/src/patron.ts new file mode 100644 index 0000000..b384fbf --- /dev/null +++ b/packages/utils/src/patron.ts @@ -0,0 +1,180 @@ +export function formatPatronUsername (first_name: string, last_name: string) { + return `${first_name || ''} ${last_name || ''}`.trim() +} + +export interface Reward { + id: string; + type: "reward"; + attributes: { + amount_cents: number; + created_at: string; + description: string; + discord_role_ids: any | null; + edited_at: string; + image_url: string | null; + patron_count: number; + post_count: number | null; + published: boolean; + published_at: string; + remaining: number | null; + requires_shipping: boolean; + title: string; + unpublished_at: string | null; + url: string; + user_limit: number | null; + }; +} + + +export interface Patron { + id: string; + full_name: string; +} + +export type PublicPatron = { + id: string; // User ID of the patron + full_name: string; // Full name of the patron, pulled from included data + link?: string; +}; + + +namespace Patreon { + export interface APIResponse { + data: { + id: string; + relationships: { + currently_entitled_tiers: { + data: { id: string; type: string }[]; + }; + user: { + data: { id: string; type: string }; + }; + }; + type: string; + }[]; + included: { + id: string; + type: string; + attributes: { + full_name?: string; + }; + }[]; + meta: { + count: number; + pagination: { + cursors: { + next?: string; + } + } + }; + } + + export interface Pledge { + id: string; + type: "pledge"; + attributes: { + amount_cents: number; + created_at: string; + declined_since: string | null; + patron_pays_fees: boolean; + pledge_cap_cents: number | null; + }; + relationships: { + patron: { + data: { + id: string; + type: "user"; + }; + links: { + related: string; + }; + }; + reward: { + data: { + id: string; + type: "reward"; + }; + links: { + related: string; + }; + }; + }; + } + + export interface User { + id: string; + type: "user"; + attributes: { + about: string; + created: string; + email: string; + facebook: string | null; + first_name: string; + full_name: string; + gender: number; + image_url: string; + is_email_verified: boolean; + last_name: string; + social_connections: SocialConnections; + thumb_url: string; + twitch: string | null; + twitter: string | null; + url: string; + vanity: string; + youtube: string | null; + }; + } + + export interface SocialConnections { + deviantart: string | null; + discord: string | null; + facebook: string | null; + reddit: string | null; + spotify: string | null; + twitch: string | null; + twitter: string | null; + youtube: string | null; + } + + export interface Reward { + id: string; + type: "reward"; + attributes: { + amount_cents: number; + created_at: string; + description: string; + discord_role_ids: any | null; + edited_at: string; + image_url: string | null; + patron_count: number; + post_count: number | null; + published: boolean; + published_at: string; + remaining: number | null; + requires_shipping: boolean; + title: string; + unpublished_at: string | null; + url: string; + user_limit: number | null; + }; + } +} + + + +export const cjClippyPatreonCampaignId = '8012692' + + +export const tiers = { + everyone: '-1', + free: '10620388', + archiveSupporter: '8154170', + stealthSupporter: '9561793', + tuneItUp: '9184994', + maxQ: '22529959', + archiveCollector: '8154171', + advancedArchiveSupporter: '8686045', + quantumSupporter: '8694826', + sneakyQuantumSupporter: '9560538', + luberPlusPlus: '8686022' +} diff --git a/scripts/data-migrations/.gitignore b/scripts/data-migrations/.gitignore new file mode 100644 index 0000000..92b7aae --- /dev/null +++ b/scripts/data-migrations/.gitignore @@ -0,0 +1 @@ +2024-10-25-from-strapi-to-postgrest-mk2.sql diff --git a/scripts/data-migrations/2024-10-18-drupal.php b/scripts/data-migrations/2024-10-18-drupal.php deleted file mode 100644 index d2cff86..0000000 --- a/scripts/data-migrations/2024-10-18-drupal.php +++ /dev/null @@ -1,79 +0,0 @@ -select('curling_games', 'g') - ->fields('g', [ - 'game_id', - 'title', - 'date', - 'time', - 'place', - ]); - return $query; - } - - /** - * {@inheritdoc} - */ - public function fields() { - $fields = [ - 'game_id' => $this->t('game_id' ), - 'title' => $this->t('title' ), - 'date' => $this->t('date'), - 'time' => $this->t('time'), - 'place' => $this->t('place' ), - ]; - return $fields; - } - - /** - * {@inheritdoc} - */ - public function getIds() { - return [ - 'game_id' => [ - 'type' => 'integer', - 'alias' => 'g', - ], - ]; - } - - /** - * {@inheritdoc} - */ - public function prepareRow(Row $row) { - // This example shows how source properties can be added in - // prepareRow(). The source dates are stored as 2017-12-17 - // and times as 16:00. Drupal 8 saves date and time fields - // in ISO8601 format 2017-01-15T16:00:00 on UTC. - // We concatenate source date and time and add the seconds. - // The same result could also be achieved using the 'concat' - // and 'format_date' process plugins in the migration - // definition. - $date = $row->getSourceProperty('date'); - $time = $row->getSourceProperty('time'); - $datetime = $date . 'T' . $time . ':00'; - $row->setSourceProperty('datetime', $datetime); - return parent::prepareRow($row); - } -} diff --git a/scripts/data-migrations/2024-10-25-from-strapi-to-postgrest-mk2.sql b/scripts/data-migrations/2024-10-25-from-strapi-to-postgrest-mk2.sql index fbf2f05..6d99747 100644 --- a/scripts/data-migrations/2024-10-25-from-strapi-to-postgrest-mk2.sql +++ b/scripts/data-migrations/2024-10-25-from-strapi-to-postgrest-mk2.sql @@ -1,80 +1,988 @@ --- Step 1, fetch data from strapi database and copy it to futureporn database via dblink --- we store the data in futureporn database under public_strapi_old schema --- We did this step manually using pgadmin4 so it's not part of this sql + +BEGIN; + +CREATE EXTENSION IF NOT EXISTS postgres_fdw; + +CREATE SERVER IF NOT EXISTS futureporn_old + FOREIGN DATA WRAPPER postgres_fdw + OPTIONS (dbname 'futureporn_old'); + +CREATE USER MAPPING IF NOT EXISTS FOR postgres + SERVER futureporn_old + OPTIONS (password_required 'true', password ''); -- @todo add password here --- Step 2, transform data from public_strapi_old to match the up-to-date api schema --- There are a lot of deprecated tables in the Strapi db, so what we do is we copy and transform only what we need. -SELECT dblink_disconnect('old_db_conn'); -SELECT dblink_connect( - 'old_db_conn', - 'dbname=futureporn_strapi_old user=postgres passfile=/tmp/.pgpass' -); + +/** + * + * + * + * + * + * + * + * + * + * + * FOREIGN TABLES CREATION + * + * + * + * - [x] external_b2_files + * - [x] external_mux_assets + * - [x] external_streams + * - [x] external_streams_vtuber_links + * - [x] external_tag_vod_relations + * - [x] external_tag_vod_relations_tag_links + * - [x] external_tag_vod_relations_vod_links + * - [x] external_tags + * - [x] external_tags_toy_links + * - [x] external_tags_vods_links + * - [x] external_timestamps + * - [x] external_timestamps_tag_links + * - [x] external_timestamps_vod_links + * - [x] external_toys + * - [x] external_toys_link_tag_links + * - [x] external_vods + * - [x] external_vods_mux_asset_links + * - [x] external_vods_stream_links + * - [x] external_vods_video_src_b_2_links + * - [x] external_vods_vtuber_links + * - [x] external_vods_thumbnail_links + * - [x] external_vods_uploader_links + * - [x] external_vtubers + * - [x] external_vtubers_toy_links + * - [x] external_vtubers_toys_links + * + * + * + */ + +CREATE FOREIGN TABLE external_b2_files +( + id INT, + url CHARACTER VARYING(255), + key CHARACTER VARYING(255), + upload_id CHARACTER VARYING(255), + created_at TIMESTAMP(6) WITHOUT TIME ZONE, + updated_at TIMESTAMP(6) WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT, + cdn_url CHARACTER VARYING(255) +) +SERVER futureporn_old +OPTIONS (table_name 'b2_files'); --- Migrate vods table -INSERT INTO api.vods ( + +CREATE FOREIGN TABLE external_mux_assets +( + id INT, + playback_id CHARACTER VARYING(255), + asset_id CHARACTER VARYING(255), + created_at TIMESTAMP(6) WITHOUT TIME ZONE, + updated_at TIMESTAMP(6) WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'mux_assets'); + + + +CREATE FOREIGN TABLE external_streams +( + id INT, + date_str CHARACTER VARYING(255), + date TIMESTAMP WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT, + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE, + cuid CHARACTER VARYING(255), + date_2 CHARACTER VARYING(255), + archive_status CHARACTER VARYING(255), + is_chaturbate_stream BOOLEAN, + is_fansly_stream BOOLEAN +) +SERVER futureporn_old +OPTIONS (table_name 'streams'); + + + +CREATE FOREIGN TABLE external_streams_vtuber_links +( + id INT, + stream_id INT, + vtuber_id INT, + stream_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'streams_vtuber_links'); + + + +CREATE FOREIGN TABLE external_tag_vod_relations +( + id INT, + votes INT, + creator_id INT, + created_at TIMESTAMP(6) WITHOUT TIME ZONE, + updated_at TIMESTAMP(6) WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'tag_vod_relations'); + + + +CREATE FOREIGN TABLE external_tag_vod_relations_tag_links +( + id INT, + tag_vod_relation_id INT, + tag_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'tag_vod_relations_tag_links'); + + + +CREATE FOREIGN TABLE external_tag_vod_relations_vod_links +( + id INT, + tag_vod_relation_id INT, + vod_id INT, + tag_vod_relation_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'tag_vod_relations_vod_links'); + + + +CREATE FOREIGN TABLE external_tags +( + id INT, + name CHARACTER VARYING(255), + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE, + published_at TIMESTAMP WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'tags'); + + +CREATE FOREIGN TABLE external_tags_toy_links +( + id INT, + tag_id INT, + toy_id INT, + tag_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'tags_toy_links'); + + + +CREATE FOREIGN TABLE external_tags_vods_links +( + id INT, + tag_id INT, + vod_id INT, + tag_order DOUBLE PRECISION, + vod_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'tags_vods_links'); + + + +CREATE FOREIGN TABLE external_timestamps +( + id INT, + time INT, + creator_id INT, + created_at TIMESTAMP(6) WITHOUT TIME ZONE, + updated_at TIMESTAMP(6) WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'timestamps'); + + + +CREATE FOREIGN TABLE external_timestamps_tag_links +( + id INT, + timestamp_id INT, + tag_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'timestamps_tag_links'); + + + +CREATE FOREIGN TABLE external_timestamps_vod_links +( + id INT, + timestamp_id INT, + vod_id INT, + timestamp_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'timestamps_vod_links'); + + +CREATE FOREIGN TABLE external_toys +( + id INT, + make CHARACTER VARYING(255), + model CHARACTER VARYING(255), + created_at TIMESTAMP WITHOUT TIME ZONE, + updated_at TIMESTAMP WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT, + image_2 CHARACTER VARYING(255) +) +SERVER futureporn_old +OPTIONS (table_name 'toys'); + + + +CREATE FOREIGN TABLE external_toys_link_tag_links +( + id INT, + toy_id INT, + tag_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'toys_link_tag_links'); + + + + + + +CREATE FOREIGN TABLE external_vods +( + id integer, + created_at timestamp without time zone, + updated_at timestamp without time zone, + published_at date, + title text, + date date NOT NULL, + mux_asset INT, + thumbnail INT, + vtuber INT, + ipfs_cid text, + video_src_hash CHARACTER VARYING(255), + s3_file INT, + torrent text, + announce_title text, + announce_url text, + note text, + url text, + discord_message_id text, + status text COLLATE pg_catalog."default" DEFAULT 'pending_recording'::text, + recording_id INT, + stream_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'vods'); + + + +CREATE FOREIGN TABLE external_vods_mux_asset_links +( + id INT, + vod_id INT, + mux_asset_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'vods_mux_asset_links'); + + + + + +CREATE FOREIGN TABLE external_vods_stream_links +( + id INT, + vod_id INT, + stream_id INT, + vod_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'vods_stream_links'); + + +CREATE FOREIGN TABLE external_vods_vtuber_links +( + id INT, + vod_id INT, + vtuber_id INT, + vod_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'vods_vtuber_links'); + + + +CREATE FOREIGN TABLE external_vods_thumbnail_links +( + id INT, + vod_id INT, + b_2_file_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'vods_thumbnail_links'); + + +CREATE FOREIGN TABLE external_vods_uploader_links +( + id INT, + vod_id INT, + user_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'vods_uploader_links'); + + + +CREATE FOREIGN TABLE external_vods_video_src_b_2_links +( + id INT, + vod_id INT, + b_2_file_id INT +) +SERVER futureporn_old +OPTIONS (table_name 'vods_video_src_b_2_links'); + + + +CREATE FOREIGN TABLE external_vtubers +( + id INT, + chaturbate CHARACTER VARYING(255), + twitter CHARACTER VARYING(255), + patreon CHARACTER VARYING(255), + twitch CHARACTER VARYING(255), + tiktok CHARACTER VARYING(255), + onlyfans CHARACTER VARYING(255), + youtube CHARACTER VARYING(255), + linktree CHARACTER VARYING(255), + carrd CHARACTER VARYING(255), + fansly CHARACTER VARYING(255), + pornhub CHARACTER VARYING(255), + discord CHARACTER VARYING(255), + reddit CHARACTER VARYING(255), + throne CHARACTER VARYING(255), + instagram CHARACTER VARYING(255), + facebook CHARACTER VARYING(255), + merch CHARACTER VARYING(255), + slug CHARACTER VARYING(255), + image CHARACTER VARYING(255), + description_1 TEXT, + description_2 TEXT, + created_at TIMESTAMP(6) WITHOUT TIME ZONE, + updated_at TIMESTAMP(6) WITHOUT TIME ZONE, + published_at TIMESTAMP(6) WITHOUT TIME ZONE, + created_by_id INT, + updated_by_id INT, + theme_color CHARACTER VARYING(255), + display_name CHARACTER VARYING(255), + image_blur CHARACTER VARYING(255) +) +SERVER futureporn_old +OPTIONS (table_name 'vtubers'); + + +CREATE FOREIGN TABLE external_vtubers_toy_links +( + id INT, + vtuber_id INT, + toy_id INT, + vtuber_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'vtubers_toy_links'); + + + +CREATE FOREIGN TABLE external_vtubers_toys_links +( + id INT, + vtuber_id INT, + toy_id INT, + toy_order DOUBLE PRECISION +) +SERVER futureporn_old +OPTIONS (table_name 'vtubers_toys_links'); + + + +/** + * + * + * + * + * + * + * + * Migrations + * + * + * + * + * The tables we need to copy are as follows. + * Note: this is not alphabetical on purpose, because some tables depend on others. + * + * - [x] vtubers + * - [x] tags + * - [x] b2_files + * - [x] mux_assets + * - [x] streams + * - [x] streams_vtuber_links + * - [x] vods + * - [x] toys + * - [x] tag_vod_relations + * - [x] tag_vod_relations_tag_links + * - [x] tag_vod_relations_vod_links + * - [x] tags_toy_links + * - [x] tags_vods_links + * - [x] timestamps + * - [x] timestamps_tag_links + * - [x] timestamps_vod_links + * - [x] toys_link_tag_links + * - [x] vods_mux_asset_links + * - [x] vods_stream_links + * - [x] vods_thumbnail_links + * - [x] vods_uploader_links + * - [x] vods_video_src_b_2_links + * - [x] vods_vtuber_links + * - [x] vtubers_toy_links + * - [x] vtubers_toys_links + * + * + * + */ + + +INSERT INTO api.vtubers +( id, - id_old, - stream_id, + display_name, + chaturbate, + twitter, + patreon, + twitch, + tiktok, + onlyfans, + youtube, + linktree, + carrd, + fansly, + pornhub, + discord, + reddit, + throne, + instagram, + facebook, + merch, + slug, + description_1, + description_2, + image, + theme_color, + image_blur, + created_at, + updated_at +) +OVERRIDING SYSTEM VALUE +SELECT + vtubers.id, + display_name, + chaturbate, + twitter, + patreon, + twitch, + tiktok, + onlyfans, + youtube, + linktree, + carrd, + fansly, + pornhub, + discord, + reddit, + throne, + instagram, + facebook, + merch, + slug, + description_1, + description_2, + image, + theme_color, + image_blur, + created_at, + updated_at +FROM public.external_vtubers AS vtubers; + + + + + + +INSERT INTO api.b2_files ( + id, + url, + key, + upload_id, + created_at, + updated_at, + created_by_id, + updated_by_id, + cdn_url +) +OVERRIDING SYSTEM VALUE +SELECT + id, + url, + key, + upload_id, + created_at, + updated_at, + created_by_id, + updated_by_id, + cdn_url +FROM public.external_b2_files; + + +INSERT INTO api.mux_assets ( + id, + asset_id, + playback_id, + created_at, + updated_at +) +OVERRIDING SYSTEM VALUE +SELECT + id, + asset_id, + playback_id, + created_at, + updated_at +FROM public.external_mux_assets; + + + +INSERT INTO api.streams ( + id, + platform_notification_type, + date, created_at, updated_at, + vtuber_num, + archive_status, + is_chaturbate_stream, + is_fansly_stream +) +OVERRIDING SYSTEM VALUE +SELECT + streams.id, + NULL AS platform_notification_type, -- Modify if necessary + streams.date_2::TIMESTAMP WITHOUT TIME ZONE AS date, + streams.created_at, + streams.updated_at, + links.vtuber_id AS vtuber_num, + streams.archive_status, + streams.is_chaturbate_stream, + streams.is_fansly_stream +FROM public.external_streams AS streams +LEFT JOIN public.external_streams_vtuber_links AS links + ON streams.id = links.stream_id; + + + +INSERT INTO api.streams_vtuber_links ( + id, + stream_id, + vtuber_id, + stream_order +) +OVERRIDING SYSTEM VALUE +SELECT + id, + stream_id, + vtuber_id, + stream_order +FROM public.external_streams_vtuber_links; + + + +INSERT INTO api.vods ( + id, + created_at, + updated_at, + published_at, title, date, note, ipfs_cid, - s3_file, announce_title, announce_url, status ) +OVERRIDING SYSTEM VALUE SELECT - gen_random_uuid(), vods.id, - NULL, vods.created_at, vods.updated_at, + vods.published_at, vods.title, vods.date::date, vods.note, - vods.video_src_hash, - NULL, -- old vods doesn't contain this info-- the join table is needed + vods.video_src_hash AS ipfs_cid, vods.announce_title, vods.announce_url, 'pending_recording' -FROM - dblink('old_db_conn', 'SELECT * FROM public.vods') AS vods ( - id integer, - video_src_hash character varying, - video_720_hash character varying, - video_480_hash character varying, - video_360_hash character varying, - video_240_hash character varying, - thin_hash character varying, - thicc_hash character varying, - announce_title character varying, - announce_url character varying, - note text, - date timestamp(6) without time zone, - spoilers text, - created_at timestamp(6) without time zone, - updated_at timestamp(6) without time zone, - published_at timestamp(6) without time zone, - created_by_id integer, - updated_by_id integer, - title character varying, - chat_log text, - date_2 character varying, - cuid character varying, - archive_status character varying - ) - LEFT JOIN ( - -- Fetching vods_vtuber_links from the old database - SELECT * - FROM dblink('old_db_conn', 'SELECT vod_id, vtuber_id FROM public.vods_vtuber_links') AS links ( - vod_id integer, - vtuber_id integer - ) - ) AS links ON vods.id = links.vod_id - LEFT JOIN api.vtubers AS vtubers - ON links.vtuber_id = vtubers.id_old; -- Map the old `vtuber_id` to the new `uuid` in `vtubers` +FROM public.external_vods AS vods; + + + +INSERT INTO api.toys ( + id, + make, + model, + image, + created_at, + updated_at +) +OVERRIDING SYSTEM VALUE +SELECT + toys.id, + toys.make, + toys.model, + toys.image_2 AS image, + toys.created_at, + toys.updated_at +FROM public.external_toys AS toys; + + + +INSERT INTO api.tags ( + id, + name, + created_at, + updated_at, + created_by_id, + updated_by_id +) +OVERRIDING SYSTEM VALUE +SELECT + id, + name, + created_at, + updated_at, + created_by_id, + updated_by_id +FROM public.external_tags; + + + +INSERT INTO api.tag_vod_relations ( + id, + votes, + creator_id, + created_at, + updated_at, + created_by_id, + updated_by_id +) +SELECT + id, + votes, + creator_id, + created_at, + updated_at, + created_by_id, + updated_by_id +FROM public.external_tag_vod_relations; + + + +INSERT INTO api.tag_vod_relations_tag_links ( + id, + tag_vod_relation_id, + tag_id +) +SELECT + id, + tag_vod_relation_id, + tag_id +FROM public.external_tag_vod_relations_tag_links; + + + +INSERT INTO api.tag_vod_relations_vod_links +( + id, + tag_vod_relation_id, + vod_id, + tag_vod_relation_order +) +SELECT + id, + tag_vod_relation_id, + vod_id, + tag_vod_relation_order +FROM public.external_tag_vod_relations_vod_links; + + + +INSERT INTO api.tags_toy_links +( + id, + tag_id, + toy_id, + tag_order +) +SELECT + id, + tag_id, + toy_id, + tag_order +FROM public.external_tags_toy_links; + + + +INSERT INTO api.tags_vods_links +( + id, + tag_id, + vod_id, + tag_order, + vod_order +) +SELECT + id, + tag_id, + vod_id, + tag_order, + vod_order +FROM public.external_tags_vods_links; + + + +INSERT INTO api.timestamps +( + id, + time, + creator_id, + created_at, + updated_at, + created_by_id, + updated_by_id +) +SELECT + id, + time, + creator_id, + created_at, + updated_at, + created_by_id, + updated_by_id +FROM public.external_timestamps; + + + +INSERT INTO api.timestamps_tag_links +( + id, + timestamp_id, + tag_id +) +SELECT + id, + timestamp_id, + tag_id +FROM public.external_timestamps_tag_links; + + + +INSERT INTO api.timestamps_vod_links +( + id, + timestamp_id, + vod_id, + timestamp_order +) +SELECT + id, + timestamp_id, + vod_id, + timestamp_order +FROM public.external_timestamps_vod_links; + + + +INSERT INTO api.toys_link_tag_links +( + id, + toy_id, + tag_id +) +SELECT + id, + toy_id, + tag_id +FROM public.external_toys_link_tag_links; + + + +INSERT INTO api.vods_mux_asset_links ( + id, + vod_id, + mux_asset_id +) +SELECT + vmal.id, + vmal.vod_id, + vmal.mux_asset_id +FROM public.external_vods_mux_asset_links AS vmal; + + + +INSERT INTO api.vods_stream_links ( + id, + vod_id, + stream_id, + vod_order +) +SELECT + vsl.id, + vsl.vod_id, + vsl.stream_id, + vsl.vod_order +FROM public.external_vods_stream_links AS vsl; + + + +INSERT INTO api.vods_thumbnail_links ( + id, + vod_id, + b_2_file_id +) +SELECT + vtl.id, + vtl.vod_id, + vtl.b_2_file_id +FROM public.external_vods_thumbnail_links AS vtl; + + + +INSERT INTO api.vods_uploader_links ( + id, + vod_id, + user_id +) +SELECT + vul.id, + vul.vod_id, + vul.user_id +FROM public.external_vods_uploader_links AS vul; + + + +INSERT INTO api.vods_video_src_b_2_links ( + id, + vod_id, + b_2_file_id +) +SELECT + v.id, + v.vod_id, + v.b_2_file_id +FROM public.external_vods_video_src_b_2_links AS v; + + + +INSERT INTO api.vods_vtuber_links ( + id, + vod_id, + vtuber_id, + vod_order +) +SELECT + v.id, + v.vod_id, + v.vtuber_id, + v.vod_order +FROM public.external_vods_vtuber_links AS v; + + + +INSERT INTO api.vtubers_toy_links +( + id, + vtuber_id, + toy_id, + vtuber_order +) +SELECT + id, + vtuber_id, + toy_id, + vtuber_order +FROM public.external_vtubers_toy_links; + + +INSERT INTO api.vtubers_toys_links +( + id, + vtuber_id, + toy_id, + toy_order +) +SELECT + id, + vtuber_id, + toy_id, + toy_order +FROM public.external_vtubers_toys_links; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +COMMIT; \ No newline at end of file diff --git a/scripts/data-migrations/2024-11-21-relate-vtubers-to-vods.sql b/scripts/data-migrations/2024-11-21-relate-vtubers-to-vods.sql new file mode 100644 index 0000000..95d5ff2 --- /dev/null +++ b/scripts/data-migrations/2024-11-21-relate-vtubers-to-vods.sql @@ -0,0 +1,19 @@ + +BEGIN; + +-- SELECT * FROM api.vods AS vods +-- INNER JOIN api.vods_vtuber_links AS links +-- ON vods.id = links.vtuber_id + +-- INSERT INTO api.vods (vtuber) +-- SELECT vtuber +-- FROM api.vods_vtuber_links +-- WHERE api.vods.id = api.vods_vtuber_links.vtuber_id; + +UPDATE api.vods +SET vtuber_id = links.vtuber_id +FROM api.vods_vtuber_links links +WHERE api.vods.id = links.vod_id; + + +COMMIT; \ No newline at end of file diff --git a/scripts/data-migrations/2024-11-22-relate-mux_asset_id-to-vods.sql b/scripts/data-migrations/2024-11-22-relate-mux_asset_id-to-vods.sql new file mode 100644 index 0000000..54b2365 --- /dev/null +++ b/scripts/data-migrations/2024-11-22-relate-mux_asset_id-to-vods.sql @@ -0,0 +1,11 @@ + +BEGIN; + +UPDATE api.vods +SET mux_asset_id = links.mux_asset_id +FROM api.vods_mux_asset_links links +WHERE api.vods.id = links.vod_id; + +COMMIT; + + diff --git a/scripts/data-migrations/2024-11-22-relate-thumbnails-to-vods.sql b/scripts/data-migrations/2024-11-22-relate-thumbnails-to-vods.sql new file mode 100644 index 0000000..587c372 --- /dev/null +++ b/scripts/data-migrations/2024-11-22-relate-thumbnails-to-vods.sql @@ -0,0 +1,11 @@ + +BEGIN; + +UPDATE api.vods +SET thumbnail_id = links.b_2_file_id +FROM api.vods_thumbnail_links links +WHERE api.vods.id = links.vod_id; + +COMMIT; + + diff --git a/scripts/data-migrations/2024-11-22-rename-vtuber_num-to-vtuber_id.sql b/scripts/data-migrations/2024-11-22-rename-vtuber_num-to-vtuber_id.sql new file mode 100644 index 0000000..f4e2019 --- /dev/null +++ b/scripts/data-migrations/2024-11-22-rename-vtuber_num-to-vtuber_id.sql @@ -0,0 +1,11 @@ + +BEGIN; + +UPDATE api.streams +SET vtuber_id = vtuber_num +WHERE vtuber_num IS NOT NULL; + +COMMIT; + + +-- @TODO api.streams.vtuber_num is deprecated in favor of api.streams.vtuber_Id \ No newline at end of file diff --git a/scripts/data-migrations/README.md b/scripts/data-migrations/README.md index d1c6722..af966ab 100644 --- a/scripts/data-migrations/README.md +++ b/scripts/data-migrations/README.md @@ -1,7 +1,7 @@ # Futureporn data migrations This directory is for data migrations ONLY. -For schema migrations, see ./services/migrations +For schema migrations, see ../services/migrations node package ## Usage diff --git a/scripts/k8s-secrets.sh b/scripts/k8s-secrets.sh index a0b3ac8..4eadd70 100755 --- a/scripts/k8s-secrets.sh +++ b/scripts/k8s-secrets.sh @@ -42,6 +42,16 @@ EOF # --from-literal=b2Key=${UPPY_B2_KEY} \ # --from-literal=b2Secret=${UPPY_B2_SECRET}\ +kubectl --namespace futureporn delete secret next --ignore-not-found +kubectl --namespace futureporn create secret generic next \ +--from-literal=nextAuthSecret=${NEXTAUTH_SECRET} + +kubectl --namespace futureporn delete secret keycloak --ignore-not-found +kubectl --namespace futureporn create secret generic keycloak \ +--from-literal=adminPassword=${KEYCLOAK_ADMIN_PASSWORD} \ +--from-literal=clientId=${KEYCLOAK_CLIENT_ID} \ +--from-literal=clientSecret=${KEYCLOAK_CLIENT_SECRET} + kubectl --namespace futureporn delete secret traefik-dashboard-auth --ignore-not-found kubectl --namespace futureporn create secret generic traefik-dashboard-auth \ --type=kubernetes.io/basic-auth \ @@ -58,6 +68,7 @@ kubectl --namespace futureporn create secret generic logto \ kubectl --namespace futureporn delete secret supertokens --ignore-not-found kubectl --namespace futureporn create secret generic supertokens \ --from-literal=apiKeys=${SUPERTOKENS_API_KEYS} \ +--from-literal=apiKey=${SUPERTOKENS_API_KEY} \ --from-literal=postgresqlUri=${SUPERTOKENS_POSTGRESQL_URI} kubectl --namespace futureporn delete secret patreon --ignore-not-found @@ -188,7 +199,8 @@ kubectl --namespace futureporn delete secret postgresql --ignore-not-found kubectl --namespace futureporn create secret generic postgresql \ --from-literal=replication-password=${POSTGRES_PASSWORD} \ --from-literal=postgres-password=${POSTGRES_PASSWORD} \ ---from-literal=password=${POSTGRES_PASSWORD} +--from-literal=password=${POSTGRES_PASSWORD} \ +--from-literal=db-password=${POSTGRES_PASSWORD} kubectl --namespace futureporn delete secret pgadmin --ignore-not-found kubectl --namespace futureporn create secret generic pgadmin \ diff --git a/scripts/keycloak-seed.sh b/scripts/keycloak-seed.sh new file mode 100755 index 0000000..02268b7 --- /dev/null +++ b/scripts/keycloak-seed.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +kubectl -n futureporn exec postgresql-primary-0 -- env PGPASSWORD=${POSTGRES_PASSWORD} psql -U postgres --command "CREATE DATABASE keycloak;" +echo "Done." \ No newline at end of file diff --git a/services/bot/src/utils/loader.ts b/services/bot/src/utils/loader.ts index 98b5532..3926b45 100644 --- a/services/bot/src/utils/loader.ts +++ b/services/bot/src/utils/loader.ts @@ -1,4 +1,4 @@ -// greetz https://github.com/discordeno/discordeno/blob/main/examples/advanced/src/utils/loader.ts +// @see https://github.com/discordeno/discordeno/blob/main/examples/advanced/src/utils/loader.ts import { readdir } from 'node:fs/promises' import { logger } from '@discordeno/bot' diff --git a/services/factory/README.md b/services/factory/README.md index de66563..a0f4bc7 100644 --- a/services/factory/README.md +++ b/services/factory/README.md @@ -9,3 +9,9 @@ factory has a big disk and lots of RAM in order to do transcoding tasks ## 240p encodes ffmpeg -i ./pmel-cb-2023-03-04.mp4 -vf scale=-2:240 -b:v 368k -b:a 45k ./projektmelody-chaturbatep2023-03-04_240p.mp4 + + +## potential helpful tools + +* https://github.com/superstreamerapp/superstreamer +* https://github.com/StreamPot/StreamPot diff --git a/services/factory/crontab b/services/factory/crontab index 1fc4461..79439f5 100644 --- a/services/factory/crontab +++ b/services/factory/crontab @@ -13,5 +13,5 @@ # * * * * * task ?opts {payload} -## every 12 hours, we update the patrons table, synchronizing it with Patreon API. -* */12 * * * synchronize_patrons_list ?max=1 \ No newline at end of file +## every n minutes, we update the patrons table to contain patron names who have opted-in to the Website Shoutout perk +*/1 * * * * synchronize_patrons_list ?max=1 \ No newline at end of file diff --git a/services/factory/pnpm-lock.yaml b/services/factory/pnpm-lock.yaml index 7b4657b..4117d98 100644 --- a/services/factory/pnpm-lock.yaml +++ b/services/factory/pnpm-lock.yaml @@ -67,34 +67,6 @@ importers: specifier: ^4.19.0 version: 4.19.0 - ../..: {} - - ../../packages/fetchers: {} - - ../../packages/infra: {} - - ../../packages/storage: {} - - ../../packages/types: {} - - ../../packages/utils: {} - - ../bot: {} - - ../capture: {} - - ../mailbox: {} - - ../migrations: {} - - ../next: {} - - ../scout: {} - - ../strapi: {} - - ../uppy: {} - packages: '@aws-crypto/crc32@5.2.0': diff --git a/services/factory/src/config.ts b/services/factory/src/config.ts index 513ff14..a454d6c 100644 --- a/services/factory/src/config.ts +++ b/services/factory/src/config.ts @@ -17,6 +17,8 @@ if (!process.env.S3_USC_BUCKET) throw new Error('Missing S3_USC_BUCKET env var') if (!process.env.CACHE_DIR) throw new Error('Missing CACHE_DIR env var'); if (!process.env.PATREON_CREATOR_ACCESS_TOKEN) throw new Error('Missing PATREON_CREATOR_ACCESS_TOKEN env var'); if (!process.env.PATREON_CREATOR_REFRESH_TOKEN) throw new Error('Missing PATREON_CREATOR_REFRESH_TOKEN env var'); +if (!process.env.SUPERTOKENS_URL) throw new Error('Missing SUPERTOKENS_URL env var'); +if (!process.env.SUPERTOKENS_API_KEY) throw new Error('Missing SUPERTOKENS_API_KEY env var'); const postgrestUrl = process.env.POSTGREST_URL! const automationUserJwt = process.env.AUTOMATION_USER_JWT! const connectionString = process.env.WORKER_CONNECTION_STRING! @@ -29,6 +31,8 @@ const s3UscBucket = process.env.S3_USC_BUCKET! const cacheDir = process.env.CACHE_DIR! const patreonCreatorAccessToken = process.env.PATREON_CREATOR_ACCESS_TOKEN! const patreonCreatorRefreshToken = process.env.PATREON_CREATOR_REFRESH_TOKEN! +const supertokensUrl = process.env.SUPERTOKENS_URL! +const supertokensApiKey = process.env.SUPERTOKENS_API_KEY! export interface Config { postgrestUrl: string; @@ -43,6 +47,8 @@ export interface Config { cacheDir: string; patreonCreatorAccessToken: string; patreonCreatorRefreshToken: string; + supertokensUrl: string; + supertokensApiKey: string; } @@ -59,4 +65,6 @@ export const configs: Config = { cacheDir, patreonCreatorAccessToken, patreonCreatorRefreshToken, + supertokensUrl, + supertokensApiKey, } diff --git a/services/factory/src/tasks/generate_thumbnail.ts b/services/factory/src/tasks/generate_thumbnail.ts index 3f77ed0..469dba1 100644 --- a/services/factory/src/tasks/generate_thumbnail.ts +++ b/services/factory/src/tasks/generate_thumbnail.ts @@ -25,9 +25,19 @@ async function doIntegratedRequest(vodId: string): Promise { // we need to get a CDN url to the vod so we can download chunks of the file in order for Prevvy to create the storyboard image. const cdnUrl = getCdnUrl(configs.s3MainBucket, s3_file.s3_key) - const tmpImagePath = await getStoryboard(cdnUrl) + const thumbnailCdnUrl = await getThumbnailCdnUrl(cdnUrl) + + await patchVodInDatabase(vodId, { thumbnail: thumbnailCdnUrl }) + +} + + +async function getThumbnailCdnUrl(videoUrl: string): Promise { + + const tmpImagePath = await getStoryboard(videoUrl) + // we need to upload the image to S3 const uploadArgs: S3FileArgs = { filePath: tmpImagePath, @@ -44,15 +54,13 @@ async function doIntegratedRequest(vodId: string): Promise { // we need to create a S3 file in the db const thumbnail = getCdnUrl(configs.s3MainBucket, upload.Key) - await patchVodInDatabase(vodId, { thumbnail }) - - + return thumbnail } async function doSoloRequest(videoUrl: string): Promise { - return await getThumbnailUrl(videoUrl) + await getThumbnailCdnUrl(videoUrl) } @@ -74,7 +82,7 @@ export const generate_thumbnail: Task = async function (payload: unknown, helper if (integratedRequest) { await doIntegratedRequest(vod_id) } else if (soloRequest) { - await getThumbnailUrl(video_url) + await getThumbnailCdnUrl(video_url) } else { throw new Error(`unsupported ambiguous request!`) } diff --git a/services/factory/src/tasks/synchronize_patrons_list.ts b/services/factory/src/tasks/synchronize_patrons_list.ts index 68cc31b..a6f6ee2 100644 --- a/services/factory/src/tasks/synchronize_patrons_list.ts +++ b/services/factory/src/tasks/synchronize_patrons_list.ts @@ -1,244 +1,132 @@ import type { Task, Helpers } from "graphile-worker"; -import type { IPatron } from '@futureporn/types' import { configs } from '../config.ts' - -interface Patron { - id: string; - full_name: string; -} - -type SimplePatron = { - id: string; // User ID of the patron - full_name: string; // Full name of the patron, pulled from included data - entitled_tier_ids: string[]; // List of tier IDs the patron is currently entitled to -}; +import { formatPatronUsername } from "@futureporn/utils/patron.ts"; +import { PublicPatron } from "@futureporn/utils/patron.ts"; -namespace Patreon { - export interface APIResponse { - data: { - id: string; - relationships: { - currently_entitled_tiers: { - data: { id: string; type: string }[]; - }; - user: { - data: { id: string; type: string }; - }; - }; - type: string; - }[]; - included: { - id: string; - type: string; - attributes: { - full_name?: string; - }; - }[]; - meta: { - count: number; - pagination: { - cursors: { - next?: string; - } + +// async function fetchAllPages(url: string, headers: HeadersInit = {}): Promise { +// const responses: Patreon.APIResponse[] = []; +// let cursor: string | null = null; + +// do { +// const fetchUrl = cursor ? `${url}&page[cursor]=${cursor}` : url; +// const response = await fetch(fetchUrl, { headers }); +// const jsonResponse: Patreon.APIResponse = await response.json(); + +// responses.push(jsonResponse); +// cursor = jsonResponse.meta?.pagination?.cursors?.next || null; +// } while (cursor); + +// return responses; +// } + + +/** + * getOptInPatrons + * + * Get a list of users who have the 'patron' role and have opted-in to having their username displayed on the site + * + * @see https://app.swaggerhub.com/apis/supertokens/CDI/5.1.1#/User%20Roles%20Recipe/getRoleUsers + * + * @todo this rapid-fires 600+ fetch() requests to supertokens. Do we need to throttle this? + */ +async function getPublicPatrons(): Promise { + const allPatrons = await getPatronIds() + let publicPatrons = [] + for (const patronId of allPatrons) { + const res = await fetch(`${configs.supertokensUrl}/recipe/user/metadata?userId=${patronId}`, { + method: 'GET', + headers: { + 'rid': 'usermetadata', + 'Authorization': configs.supertokensApiKey, + 'cdi-version': '5.1', } - }; + }) + if (!res.ok) { + throw new Error(`failed to fetch usermetadata from supertokens. res.status=${res.status}, res.statusText=${res.statusText}`) + } + const data = await res.json() + const metadata = data.metadata + const isUsernamePublic = metadata?.isUsernamePublic + if (isUsernamePublic) { + const patron: PublicPatron = { + full_name: formatPatronUsername(metadata.first_name, metadata.last_name), + id: patronId, + link: metadata?.vanityLink + } + publicPatrons.push(patron) + } } + return publicPatrons +} - export interface Pledge { - id: string; - type: "pledge"; - attributes: { - amount_cents: number; - created_at: string; - declined_since: string | null; - patron_pays_fees: boolean; - pledge_cap_cents: number | null; - }; - relationships: { - patron: { - data: { - id: string; - type: "user"; - }; - links: { - related: string; - }; - }; - reward: { - data: { - id: string; - type: "reward"; - }; - links: { - related: string; - }; - }; - }; - } - - export interface User { - id: string; - type: "user"; - attributes: { - about: string; - created: string; - email: string; - facebook: string | null; - first_name: string; - full_name: string; - gender: number; - image_url: string; - is_email_verified: boolean; - last_name: string; - social_connections: SocialConnections; - thumb_url: string; - twitch: string | null; - twitter: string | null; - url: string; - vanity: string; - youtube: string | null; - }; - } - - export interface SocialConnections { - deviantart: string | null; - discord: string | null; - facebook: string | null; - reddit: string | null; - spotify: string | null; - twitch: string | null; - twitter: string | null; - youtube: string | null; - } - - export interface Reward { - id: string; - type: "reward"; - attributes: { - amount_cents: number; - created_at: string; - description: string; - discord_role_ids: any | null; - edited_at: string; - image_url: string | null; - patron_count: number; - post_count: number | null; - published: boolean; - published_at: string; - remaining: number | null; - requires_shipping: boolean; - title: string; - unpublished_at: string | null; - url: string; - user_limit: number | null; - }; + +/** + * getPatronIds + * + * get the UUIDs of all patrons + */ +async function getPatronIds(): Promise { + const res = await fetch(`${configs.supertokensUrl}/recipe/role/users?role=patron`, { + method: 'GET', + headers: { + 'rid': 'userroles', + 'Authorization': configs.supertokensApiKey, + 'cdi-version': '5.1' + } + }) + if (!res.ok) { + throw new Error(`failed to getPublicPatrons() with res.status=${res.status} res.statusText=${res.statusText}`) } + const data = await res.json() + const patrons = data?.users + return patrons } -const tiers = { - free: '10620388', - archiveSupporter: '8154170', - stealthSupporter: '9561793', - tuneItUp: '9184994', - maxQ: '22529959', - archiveCollector: '8154171', - advancedArchiveSupporter: '8686045', - quantumSupporter: '8694826', - sneakyQuantumSupporter: '9560538', - luberPlusPlus: '8686022' -} - -async function fetchAllPages(url: string, headers: HeadersInit = {}): Promise { - const responses: Patreon.APIResponse[] = []; - let cursor: string | null = null; - - do { - const fetchUrl = cursor ? `${url}&page[cursor]=${cursor}` : url; - const response = await fetch(fetchUrl, { headers }); - const jsonResponse: Patreon.APIResponse = await response.json(); - - responses.push(jsonResponse); - cursor = jsonResponse.meta?.pagination?.cursors?.next || null; - } while (cursor); - - return responses; -} - - -function transformApiResponse(responses: Patreon.APIResponse[]): SimplePatron[] { - const usersMap: Record = {}; - - // Build a map of user ID -> full_name from the included data - responses.forEach(response => { - response.included - .filter(item => item.type === 'user') - .forEach(user => { - usersMap[user.id] = user.attributes.full_name || ''; - }); - }); - - // Map through `data` to create a unified array of SimplePatron - return responses.flatMap(response => - response.data.map(datum => ({ - id: datum.relationships.user.data.id, - full_name: usersMap[datum.relationships.user.data.id] || '', - entitled_tier_ids: datum.relationships.currently_entitled_tiers.data.map(tier => tier.id), - })) - ); -} - - -function filterActivePatrons(patrons: SimplePatron[]): SimplePatron[] { - const acceptedTiers = new Set(Object.values(tiers)) - acceptedTiers.delete(tiers.free); - return patrons.filter(patron => - patron.entitled_tier_ids.some(tierId => acceptedTiers.has(tierId)) - ); -} - - -async function getActivePatrons(url: string, headers: HeadersInit = {}): Promise { - // Step 1: Fetch all pages of raw data - const rawResponses = await fetchAllPages(url, headers); - - // Step 2: Transform raw responses into a unified data format - const allPatrons = transformApiResponse(rawResponses); - - // Step 3: Filter patrons by accepted tiers - return filterActivePatrons(allPatrons); -} - - - - -const updatePatronsTable = async function (patronsList: SimplePatron[]): Promise { - // @todo - console.log(`@todo Syncronizing ${patronsList.length} patrons to the db`) +const updatePatronsTable = async function (patronsList: PublicPatron[]): Promise { + console.log(`Syncronizing ${patronsList.length} patron name to the public db`) + // console.log(patronsList) const res = await fetch(`${configs.postgrestUrl}/patrons`, { method: 'POST', headers: { 'Authorization': `Bearer ${configs.automationUserJwt}`, - 'Prefer': 'return=representation', + 'Prefer': 'resolution=merge-duplicates,return=representation', 'Content-Type': 'application/json', - } + }, + body: JSON.stringify(patronsList) }) - const data = await res.json() + if (!res.ok) { + const body = await res.text() + throw new Error(`failed to update patrons. res.status=${res.status} res.statusText=${res.statusText} body=${body}`); + } } +const deleteExpiredPatrons = async function (activePatronIds: string[]): Promise { + const res = await fetch(`${configs.postgrestUrl}/patrons?id=not.in.(${activePatronIds.join(',')})`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${configs.automationUserJwt}`, + 'Prefer': 'return=representation', + } + }); + + if (!res.ok) { + const body = await res.text(); + throw new Error(`Failed to delete expired patrons. res.status=${res.status} res.statusText=${res.statusText} body=${body}`); + } +}; + const synchronize_patrons_list: Task = async function (payload: unknown, helpers: Helpers) { try { - const patrons = await getActivePatrons( - 'https://api.patreon.com/api/oauth2/v2/campaigns/8012692/members?include=currently_entitled_tiers,user&fields[user]=full_name,vanity', - { 'Authorization': `Bearer ${configs.patreonCreatorAccessToken}` } - ) - + const patrons = await getPublicPatrons() await updatePatronsTable(patrons) + await deleteExpiredPatrons(patrons.map((p) => p.id)) } catch (e) { helpers.logger.error('failed to synchronize_patrons_list') diff --git a/services/factory/src/utils/importDirectory.ts b/services/factory/src/utils/importDirectory.ts index eba2690..a6dc622 100644 --- a/services/factory/src/utils/importDirectory.ts +++ b/services/factory/src/utils/importDirectory.ts @@ -1,4 +1,4 @@ -// greetz https://github.com/discordeno/discordeno/blob/main/examples/advanced/src/utils/loader.ts +// @see https://github.com/discordeno/discordeno/blob/main/examples/advanced/src/utils/loader.ts import { readdir } from 'node:fs/promises' import { join } from 'node:path' diff --git a/services/htmx/README.md b/services/htmx/README.md new file mode 100644 index 0000000..227b042 --- /dev/null +++ b/services/htmx/README.md @@ -0,0 +1,15 @@ +# bright.futureporn.net + +An htmx experiment. The goal here is to see if htmx can improve Futureporn performance. + +## Design requirements + +* [ ] performant +* [ ] serve FP video +* [ ] optimize for humans +* [ ] [HATEOAS](https://intercoolerjs.org/2016/05/08/hatoeas-is-for-humans.html) +* [ ] auth via supertokens + +## Based on + +[htmx-ts-starter-kit](https://github.com/claudioc/fastify-htmx-ts-starter-kit/tree/main) \ No newline at end of file diff --git a/services/htmx/app/index.ts b/services/htmx/app/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/services/htmx/app/vods/index.ts b/services/htmx/app/vods/index.ts new file mode 100644 index 0000000..9c4a39a --- /dev/null +++ b/services/htmx/app/vods/index.ts @@ -0,0 +1,26 @@ + + +fastify.route({ + method: 'GET', + url: '/', + schema: { + querystring: { + type: 'object', + properties: { + name: { type: 'string' }, + excitement: { type: 'integer' } + } + }, + response: { + 200: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + }, + handler: function (request, reply) { + reply.send({ hello: 'world' }) + } +}) \ No newline at end of file diff --git a/services/htmx/app/vods/view.njk b/services/htmx/app/vods/view.njk new file mode 100644 index 0000000..e69de29 diff --git a/services/htmx/config.ts b/services/htmx/config.ts new file mode 100644 index 0000000..5c0a0af --- /dev/null +++ b/services/htmx/config.ts @@ -0,0 +1,20 @@ +const requiredEnvVars = [ + 'PORT' +] as const; + +const getEnvVar = (key: typeof requiredEnvVars[number]): string => { + const value = process.env[key]; + if (!value) { + throw new Error(`Missing ${key} env var`); + } + return value; +}; + +export interface Config { + port: number; +} + +export const configs: Config = { + port: parseInt(getEnvVar('PORT')) +} + diff --git a/services/htmx/index.ts b/services/htmx/index.ts new file mode 100644 index 0000000..f149f89 --- /dev/null +++ b/services/htmx/index.ts @@ -0,0 +1,21 @@ +import { configs } from './config.ts' +import Fastify from 'fastify' + +const fastify = Fastify({ + logger: true +}) + + +// Declare a route +fastify.get('/', function (request, reply) { + reply.send({ hello: 'world' }) +}) + +// Run the server! +fastify.listen({ port: configs.port }, function (err, address) { + if (err) { + fastify.log.error(err) + process.exit(1) + } + // Server is now listening on ${address} +}) \ No newline at end of file diff --git a/services/htmx/package.json b/services/htmx/package.json new file mode 100644 index 0000000..45536a5 --- /dev/null +++ b/services/htmx/package.json @@ -0,0 +1,23 @@ +{ + "name": "htmx", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.ts", + "dev": "pnpm run dev.nodemon # yes this is crazy to have nodemon execute tsx, but it's the only way I have found to get live reloading in TS/ESM/docker with Graphile Worker's way of loading tasks", + "dev.nodemon": "nodemon --ext ts --exec \"pnpm run dev.tsx\"", + "dev.tsx": "tsx ./app/index.ts" + }, + "keywords": [], + "author": "", + "license": "Unlicense", + "dependencies": { + "fastify": "^4.28.1" + }, + "devDependencies": { + "nodemon": "^3.1.4", + "tsx": "^4.19.0" + } +} diff --git a/services/htmx/pnpm-lock.yaml b/services/htmx/pnpm-lock.yaml new file mode 100644 index 0000000..7629935 --- /dev/null +++ b/services/htmx/pnpm-lock.yaml @@ -0,0 +1,876 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + fastify: + specifier: ^4.28.1 + version: 4.28.1 + devDependencies: + nodemon: + specifier: ^3.1.4 + version: 3.1.7 + tsx: + specifier: ^4.19.0 + version: 4.19.2 + +packages: + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fastify/ajv-compiler@3.6.0': + resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==} + + '@fastify/error@3.4.1': + resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==} + + '@fastify/fast-json-stringify-compiler@4.3.0': + resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==} + + '@fastify/merge-json-schemas@0.1.1': + resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + + abstract-logging@2.0.1: + resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + avvio@8.4.0: + resolution: {integrity: sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + + fast-content-type-parse@1.1.0: + resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} + + fast-decode-uri-component@1.0.1: + resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stringify@5.16.1: + resolution: {integrity: sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==} + + fast-querystring@1.1.2: + resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-uri@2.4.0: + resolution: {integrity: sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==} + + fast-uri@3.0.3: + resolution: {integrity: sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==} + + fastify@4.28.1: + resolution: {integrity: sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-my-way@8.2.2: + resolution: {integrity: sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==} + engines: {node: '>=14'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + ignore-by-default@1.0.1: + resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + json-schema-ref-resolver@1.0.1: + resolution: {integrity: sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + light-my-request@5.14.0: + resolution: {integrity: sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nodemon@3.1.7: + resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==} + engines: {node: '>=10'} + hasBin: true + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + pino-abstract-transport@2.0.0: + resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + + pino-std-serializers@7.0.0: + resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + + pino@9.5.0: + resolution: {integrity: sha512-xSEmD4pLnV54t0NOUN16yCl7RIB1c5UUOse5HSyEXtBp+FgFQyPeDutc+Q2ZO7/22vImV7VfEjH/1zV2QuqvYw==} + hasBin: true + + process-warning@3.0.0: + resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} + + process-warning@4.0.0: + resolution: {integrity: sha512-/MyYDxttz7DfGMMHiysAsFE4qF+pQYAA8ziO/3NcRVrQ5fSk+Mns4QZA/oRPFzvcqNoVJXQNWNAsdwBXLUkQKw==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pstree.remy@1.1.8: + resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + ret@0.4.3: + resolution: {integrity: sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==} + engines: {node: '>=10'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + safe-regex2@3.1.0: + resolution: {integrity: sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + sonic-boom@4.2.0: + resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + thread-stream@3.1.0: + resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toad-cache@3.7.0: + resolution: {integrity: sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==} + engines: {node: '>=12'} + + touch@3.1.1: + resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==} + hasBin: true + + tsx@4.19.2: + resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} + engines: {node: '>=18.0.0'} + hasBin: true + + undefsafe@2.0.5: + resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} + +snapshots: + + '@esbuild/aix-ppc64@0.23.1': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + + '@fastify/ajv-compiler@3.6.0': + dependencies: + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + fast-uri: 2.4.0 + + '@fastify/error@3.4.1': {} + + '@fastify/fast-json-stringify-compiler@4.3.0': + dependencies: + fast-json-stringify: 5.16.1 + + '@fastify/merge-json-schemas@0.1.1': + dependencies: + fast-deep-equal: 3.1.3 + + abstract-logging@2.0.1: {} + + ajv-formats@2.1.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + atomic-sleep@1.0.0: {} + + avvio@8.4.0: + dependencies: + '@fastify/error': 3.4.1 + fastq: 1.17.1 + + balanced-match@1.0.2: {} + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + concat-map@0.0.1: {} + + cookie@0.7.2: {} + + debug@4.3.7(supports-color@5.5.0): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 5.5.0 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + + fast-content-type-parse@1.1.0: {} + + fast-decode-uri-component@1.0.1: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stringify@5.16.1: + dependencies: + '@fastify/merge-json-schemas': 0.1.1 + ajv: 8.17.1 + ajv-formats: 3.0.1(ajv@8.17.1) + fast-deep-equal: 3.1.3 + fast-uri: 2.4.0 + json-schema-ref-resolver: 1.0.1 + rfdc: 1.4.1 + + fast-querystring@1.1.2: + dependencies: + fast-decode-uri-component: 1.0.1 + + fast-redact@3.5.0: {} + + fast-uri@2.4.0: {} + + fast-uri@3.0.3: {} + + fastify@4.28.1: + dependencies: + '@fastify/ajv-compiler': 3.6.0 + '@fastify/error': 3.4.1 + '@fastify/fast-json-stringify-compiler': 4.3.0 + abstract-logging: 2.0.1 + avvio: 8.4.0 + fast-content-type-parse: 1.1.0 + fast-json-stringify: 5.16.1 + find-my-way: 8.2.2 + light-my-request: 5.14.0 + pino: 9.5.0 + process-warning: 3.0.0 + proxy-addr: 2.0.7 + rfdc: 1.4.1 + secure-json-parse: 2.7.0 + semver: 7.6.3 + toad-cache: 3.7.0 + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-my-way@8.2.2: + dependencies: + fast-deep-equal: 3.1.3 + fast-querystring: 1.1.2 + safe-regex2: 3.1.0 + + forwarded@0.2.0: {} + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.8.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + has-flag@3.0.0: {} + + ignore-by-default@1.0.1: {} + + ipaddr.js@1.9.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + json-schema-ref-resolver@1.0.1: + dependencies: + fast-deep-equal: 3.1.3 + + json-schema-traverse@1.0.0: {} + + light-my-request@5.14.0: + dependencies: + cookie: 0.7.2 + process-warning: 3.0.0 + set-cookie-parser: 2.7.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + ms@2.1.3: {} + + nodemon@3.1.7: + dependencies: + chokidar: 3.6.0 + debug: 4.3.7(supports-color@5.5.0) + ignore-by-default: 1.0.1 + minimatch: 3.1.2 + pstree.remy: 1.1.8 + semver: 7.6.3 + simple-update-notifier: 2.0.0 + supports-color: 5.5.0 + touch: 3.1.1 + undefsafe: 2.0.5 + + normalize-path@3.0.0: {} + + on-exit-leak-free@2.1.2: {} + + picomatch@2.3.1: {} + + pino-abstract-transport@2.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.0.0: {} + + pino@9.5.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 2.0.0 + pino-std-serializers: 7.0.0 + process-warning: 4.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.0 + thread-stream: 3.1.0 + + process-warning@3.0.0: {} + + process-warning@4.0.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pstree.remy@1.1.8: {} + + quick-format-unescaped@4.0.4: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + real-require@0.2.0: {} + + require-from-string@2.0.2: {} + + resolve-pkg-maps@1.0.0: {} + + ret@0.4.3: {} + + reusify@1.0.4: {} + + rfdc@1.4.1: {} + + safe-regex2@3.1.0: + dependencies: + ret: 0.4.3 + + safe-stable-stringify@2.5.0: {} + + secure-json-parse@2.7.0: {} + + semver@7.6.3: {} + + set-cookie-parser@2.7.1: {} + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.6.3 + + sonic-boom@4.2.0: + dependencies: + atomic-sleep: 1.0.0 + + split2@4.2.0: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + thread-stream@3.1.0: + dependencies: + real-require: 0.2.0 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toad-cache@3.7.0: {} + + touch@3.1.1: {} + + tsx@4.19.2: + dependencies: + esbuild: 0.23.1 + get-tsconfig: 4.8.1 + optionalDependencies: + fsevents: 2.3.3 + + undefsafe@2.0.5: {} diff --git a/services/htmx/src/routes.ts b/services/htmx/src/routes.ts new file mode 100644 index 0000000..9c4a39a --- /dev/null +++ b/services/htmx/src/routes.ts @@ -0,0 +1,26 @@ + + +fastify.route({ + method: 'GET', + url: '/', + schema: { + querystring: { + type: 'object', + properties: { + name: { type: 'string' }, + excitement: { type: 'integer' } + } + }, + response: { + 200: { + type: 'object', + properties: { + hello: { type: 'string' } + } + } + } + }, + handler: function (request, reply) { + reply.send({ hello: 'world' }) + } +}) \ No newline at end of file diff --git a/services/migrations/migrations/00096_add-id_num-to-s3_files.sql b/services/migrations/migrations/00096_add-id_num-to-s3_files.sql new file mode 100644 index 0000000..17dec9c --- /dev/null +++ b/services/migrations/migrations/00096_add-id_num-to-s3_files.sql @@ -0,0 +1,4 @@ +-- for strapi to postgrest migration. this col is meant to be deleted later + +ALTER TABLE IF EXISTS api.s3_files + ADD COLUMN IF NOT EXISTS id_num INT; diff --git a/services/migrations/migrations/00097_add-id_num-to-mux-assets.sql b/services/migrations/migrations/00097_add-id_num-to-mux-assets.sql new file mode 100644 index 0000000..0a41bc8 --- /dev/null +++ b/services/migrations/migrations/00097_add-id_num-to-mux-assets.sql @@ -0,0 +1,4 @@ +-- for strapi to postgrest migration. this col is meant to be deleted later + +ALTER TABLE IF EXISTS api.mux_assets + ADD COLUMN IF NOT EXISTS id_num INT; diff --git a/services/migrations/migrations/00098_add-id_num-to-streams.sql b/services/migrations/migrations/00098_add-id_num-to-streams.sql new file mode 100644 index 0000000..226ae11 --- /dev/null +++ b/services/migrations/migrations/00098_add-id_num-to-streams.sql @@ -0,0 +1,6 @@ +-- for strapi to postgrest migration. this col is meant to be deleted later +ALTER TABLE IF EXISTS api.streams + ADD COLUMN IF NOT EXISTS id_num INT; + +ALTER TABLE IF EXISTS api.vtubers + ADD COLUMN IF NOT EXISTS id_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00099_add-vtuber_num.sql b/services/migrations/migrations/00099_add-vtuber_num.sql new file mode 100644 index 0000000..d2e3ee4 --- /dev/null +++ b/services/migrations/migrations/00099_add-vtuber_num.sql @@ -0,0 +1,6 @@ +-- adding an INT col to make it compatible with the strapi migration +-- this is meant to be deleted after migration to UUID + + +ALTER TABLE IF EXISTS api.streams + ADD COLUMN IF NOT EXISTS vtuber_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00100_add-id_num-to-tags.sql b/services/migrations/migrations/00100_add-id_num-to-tags.sql new file mode 100644 index 0000000..226a049 --- /dev/null +++ b/services/migrations/migrations/00100_add-id_num-to-tags.sql @@ -0,0 +1,2 @@ +ALTER TABLE IF EXISTS api.tags + ADD COLUMN IF NOT EXISTS id_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00101_add-missing-cols-to-tags.sql b/services/migrations/migrations/00101_add-missing-cols-to-tags.sql new file mode 100644 index 0000000..78d8103 --- /dev/null +++ b/services/migrations/migrations/00101_add-missing-cols-to-tags.sql @@ -0,0 +1,11 @@ +ALTER TABLE IF EXISTS api.tags + ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE IF EXISTS api.tags + ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE IF EXISTS api.tags + ADD COLUMN IF NOT EXISTS created_by_id INT; + +ALTER TABLE IF EXISTS api.tags + ADD COLUMN IF NOT EXISTS updated_by_id INT; \ No newline at end of file diff --git a/services/migrations/migrations/00102_add-toy_id_num-to-tags.sql b/services/migrations/migrations/00102_add-toy_id_num-to-tags.sql new file mode 100644 index 0000000..6314af2 --- /dev/null +++ b/services/migrations/migrations/00102_add-toy_id_num-to-tags.sql @@ -0,0 +1,3 @@ + +ALTER TABLE IF EXISTS api.tags + ADD COLUMN IF NOT EXISTS toy_id_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00103_add-toy_num.sql b/services/migrations/migrations/00103_add-toy_num.sql new file mode 100644 index 0000000..e0b7524 --- /dev/null +++ b/services/migrations/migrations/00103_add-toy_num.sql @@ -0,0 +1,21 @@ + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS toy_id_num INT; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS created_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITHOUT TIME ZONE; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS created_by_id_num INT; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS updated_by_id_num INT; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS created_by_id UUID; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS updated_by_id UUID; diff --git a/services/migrations/migrations/00104_add-toy_num-fix.sql b/services/migrations/migrations/00104_add-toy_num-fix.sql new file mode 100644 index 0000000..cb461e0 --- /dev/null +++ b/services/migrations/migrations/00104_add-toy_num-fix.sql @@ -0,0 +1,7 @@ + + +ALTER TABLE IF EXISTS api.toys + DROP COLUMN IF EXISTS toy_id_num; + +ALTER TABLE IF EXISTS api.toys + ADD COLUMN IF NOT EXISTS id_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00105_add-id_num-to-vods_links.sql b/services/migrations/migrations/00105_add-id_num-to-vods_links.sql new file mode 100644 index 0000000..4797c5a --- /dev/null +++ b/services/migrations/migrations/00105_add-id_num-to-vods_links.sql @@ -0,0 +1,18 @@ + +ALTER TABLE IF EXISTS api.vods_mux_asset_links + ADD COLUMN IF NOT EXISTS id_num INT; + +ALTER TABLE IF EXISTS api.vods_stream_links + ADD COLUMN IF NOT EXISTS id_num INT; + +ALTER TABLE IF EXISTS api.vods_thumbnail_links + ADD COLUMN IF NOT EXISTS id_num INT; + +ALTER TABLE IF EXISTS api.vods_uploader_links + ADD COLUMN IF NOT EXISTS id_num INT; + +ALTER TABLE IF EXISTS api.vods_video_src_b_2_links + ADD COLUMN IF NOT EXISTS id_num INT; + +ALTER TABLE IF EXISTS api.vods_vtuber_links + ADD COLUMN IF NOT EXISTS id_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00106_add-id_num-to-vods.sql b/services/migrations/migrations/00106_add-id_num-to-vods.sql new file mode 100644 index 0000000..61bacf8 --- /dev/null +++ b/services/migrations/migrations/00106_add-id_num-to-vods.sql @@ -0,0 +1,2 @@ +ALTER TABLE IF EXISTS api.vods + ADD COLUMN IF NOT EXISTS id_num INT; \ No newline at end of file diff --git a/services/migrations/migrations/00107_remove-uuid.sql b/services/migrations/migrations/00107_remove-uuid.sql new file mode 100644 index 0000000..002fee2 --- /dev/null +++ b/services/migrations/migrations/00107_remove-uuid.sql @@ -0,0 +1,125 @@ +-- I was planning on migrating strapi database to postgrest and also migrating from INT to UUID. +-- Doing both at once proved far too complex, so I'm reverting the schmea to use INT ids in order to migrate. +-- Once that is done we can switch to uuids, but the two steps should be separate + +DO $$ +BEGIN + -- api.builds + ALTER TABLE api.builds DROP COLUMN id CASCADE; + ALTER TABLE api.builds ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.builds DROP COLUMN vod_id CASCADE; + ALTER TABLE api.builds ADD COLUMN vod_id INT; + + -- api.contributors + ALTER TABLE api.contributors DROP COLUMN id CASCADE; + ALTER TABLE api.contributors ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + + -- api.discord_interactions + ALTER TABLE api.discord_interactions DROP COLUMN id CASCADE; + ALTER TABLE api.discord_interactions ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + + -- api.mux_assets + ALTER TABLE api.mux_assets DROP COLUMN id CASCADE; + ALTER TABLE api.mux_assets ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + + -- api.patrons + ALTER TABLE api.patrons DROP COLUMN id CASCADE; + ALTER TABLE api.patrons ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + + -- api.recordings + ALTER TABLE api.recordings DROP COLUMN id CASCADE; + ALTER TABLE api.recordings ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.recordings DROP COLUMN discord_interaction_id CASCADE; + ALTER TABLE api.recordings ADD COLUMN discord_interaction_id INT; + + -- api.s3_files + ALTER TABLE api.s3_files DROP COLUMN id CASCADE; + ALTER TABLE api.s3_files ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + + -- api.segments + ALTER TABLE api.segments DROP COLUMN id CASCADE; + ALTER TABLE api.segments ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.segments DROP COLUMN vod_id CASCADE; + ALTER TABLE api.segments ADD COLUMN vod_id INT; + + -- api.segments_vod_links + ALTER TABLE api.segments_vod_links DROP COLUMN id CASCADE; + ALTER TABLE api.segments_vod_links ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.segments_vod_links DROP COLUMN segment_id CASCADE; + ALTER TABLE api.segments_vod_links ADD COLUMN segment_id INT; + ALTER TABLE api.segments_vod_links DROP COLUMN vod_id CASCADE; + ALTER TABLE api.segments_vod_links ADD COLUMN vod_id INT; + + -- api.streams + ALTER TABLE api.streams DROP COLUMN id CASCADE; + ALTER TABLE api.streams ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.streams DROP COLUMN vtuber CASCADE; + ALTER TABLE api.streams ADD COLUMN vtuber INT; + + -- api.tags + ALTER TABLE api.tags DROP COLUMN id CASCADE; + ALTER TABLE api.tags ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.tags DROP COLUMN toy_id CASCADE; + ALTER TABLE api.tags ADD COLUMN toy_id INT; + + -- api.tags_vods + ALTER TABLE api.tags_vods DROP COLUMN id CASCADE; + ALTER TABLE api.tags_vods ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.tags_vods DROP COLUMN tag_id CASCADE; + ALTER TABLE api.tags_vods ADD COLUMN tag_id INT; + ALTER TABLE api.tags_vods DROP COLUMN vod_id CASCADE; + ALTER TABLE api.tags_vods ADD COLUMN vod_id INT; + + -- api.toys + ALTER TABLE api.toys DROP COLUMN id CASCADE; + ALTER TABLE api.toys ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.toys DROP COLUMN created_by_id CASCADE; + ALTER TABLE api.toys ADD COLUMN created_by_id INT; + ALTER TABLE api.toys DROP COLUMN updated_by_id CASCADE; + ALTER TABLE api.toys ADD COLUMN updated_by_id INT; + + -- api.toys_tags + ALTER TABLE api.toys_tags DROP COLUMN id CASCADE; + ALTER TABLE api.toys_tags ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.toys_tags DROP COLUMN tag_id CASCADE; + ALTER TABLE api.toys_tags ADD COLUMN tag_id INT; + ALTER TABLE api.toys_tags DROP COLUMN toy_id CASCADE; + ALTER TABLE api.toys_tags ADD COLUMN toy_id INT; + + -- api.toys_vtubers + ALTER TABLE api.toys_vtubers DROP COLUMN id CASCADE; + ALTER TABLE api.toys_vtubers ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.toys_vtubers DROP COLUMN toy_id CASCADE; + ALTER TABLE api.toys_vtubers ADD COLUMN toy_id INT; + ALTER TABLE api.toys_vtubers DROP COLUMN vtuber_id CASCADE; + ALTER TABLE api.toys_vtubers ADD COLUMN vtuber_id INT; + + -- api.vods + ALTER TABLE api.vods DROP COLUMN id CASCADE; + ALTER TABLE api.vods ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.vods DROP COLUMN mux_asset CASCADE; + ALTER TABLE api.vods ADD COLUMN mux_asset INT; + ALTER TABLE api.vods DROP COLUMN recording_id CASCADE; + ALTER TABLE api.vods ADD COLUMN recording_id INT; + ALTER TABLE api.vods DROP COLUMN s3_file CASCADE; + ALTER TABLE api.vods ADD COLUMN s3_file INT; + ALTER TABLE api.vods DROP COLUMN stream_id CASCADE; + ALTER TABLE api.vods ADD COLUMN stream_id INT; + ALTER TABLE api.vods DROP COLUMN thumbnail CASCADE; + ALTER TABLE api.vods ADD COLUMN thumbnail INT; + ALTER TABLE api.vods DROP COLUMN vtuber CASCADE; + ALTER TABLE api.vods ADD COLUMN vtuber INT; + + -- api.vods_s3_files_joins + ALTER TABLE api.vods_s3_files_joins DROP COLUMN id CASCADE; + ALTER TABLE api.vods_s3_files_joins ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + ALTER TABLE api.vods_s3_files_joins DROP COLUMN s3_file_id CASCADE; + ALTER TABLE api.vods_s3_files_joins ADD COLUMN s3_file_id INT; + ALTER TABLE api.vods_s3_files_joins DROP COLUMN vod_id CASCADE; + ALTER TABLE api.vods_s3_files_joins ADD COLUMN vod_id INT; + + -- api.vtubers + ALTER TABLE api.vtubers DROP COLUMN id CASCADE; + ALTER TABLE api.vtubers ADD COLUMN id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY; + +END $$; diff --git a/services/migrations/migrations/00108_add-strapi-tables.sql b/services/migrations/migrations/00108_add-strapi-tables.sql new file mode 100644 index 0000000..33c24b5 --- /dev/null +++ b/services/migrations/migrations/00108_add-strapi-tables.sql @@ -0,0 +1,47 @@ + +CREATE TABLE IF NOT EXISTS api.streams_vtuber_links +( + id integer NOT NULL, + stream_id integer, + vtuber_id integer, + stream_order double precision, + CONSTRAINT streams_vtuber_links_pkey PRIMARY KEY (id), + CONSTRAINT streams_vtuber_links_unique UNIQUE (stream_id, vtuber_id), + CONSTRAINT streams_vtuber_links_fk FOREIGN KEY (stream_id) + REFERENCES api.streams (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT streams_vtuber_links_inv_fk FOREIGN KEY (vtuber_id) + REFERENCES api.vtubers (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.streams_vtuber_links + OWNER to postgres; +-- Index: streams_vtuber_links_fk + +-- DROP INDEX IF EXISTS api.streams_vtuber_links_fk; + +CREATE INDEX IF NOT EXISTS streams_vtuber_links_fk + ON api.streams_vtuber_links USING btree + (stream_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: streams_vtuber_links_inv_fk + +-- DROP INDEX IF EXISTS api.streams_vtuber_links_inv_fk; + +CREATE INDEX IF NOT EXISTS streams_vtuber_links_inv_fk + ON api.streams_vtuber_links USING btree + (vtuber_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: streams_vtuber_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.streams_vtuber_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS streams_vtuber_links_order_inv_fk + ON api.streams_vtuber_links USING btree + (stream_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00109_use-consistent-description_1.sql b/services/migrations/migrations/00109_use-consistent-description_1.sql new file mode 100644 index 0000000..3f7109d --- /dev/null +++ b/services/migrations/migrations/00109_use-consistent-description_1.sql @@ -0,0 +1,5 @@ +ALTER TABLE api.vtubers DROP COLUMN description1; +ALTER TABLE api.vtubers DROP COLUMN description2; + +ALTER TABLE api.vtubers ADD COLUMN description_1 TEXT; +ALTER TABLE api.vtubers ADD COLUMN description_2 TEXT; \ No newline at end of file diff --git a/services/migrations/migrations/00110_unseed-vtubers.sql b/services/migrations/migrations/00110_unseed-vtubers.sql new file mode 100644 index 0000000..7b73e3e --- /dev/null +++ b/services/migrations/migrations/00110_unseed-vtubers.sql @@ -0,0 +1,5 @@ +-- we are removing vtuber rows because vtubers are data. +-- data is not supposed to be part of schema migrations +-- @see https://github.com/thomwright/postgres-migrations?tab=readme-ov-file#schema-migrations-vs-data-migrations + +DELETE FROM api.vtubers; \ No newline at end of file diff --git a/services/migrations/migrations/00111_create-strapi-tables-tvr.sql b/services/migrations/migrations/00111_create-strapi-tables-tvr.sql new file mode 100644 index 0000000..8bca59b --- /dev/null +++ b/services/migrations/migrations/00111_create-strapi-tables-tvr.sql @@ -0,0 +1,33 @@ + +CREATE TABLE IF NOT EXISTS api.tag_vod_relations +( + id integer NOT NULL, + votes integer, + creator_id integer, + created_at timestamp(6) without time zone, + updated_at timestamp(6) without time zone, + created_by_id integer, + updated_by_id integer, + CONSTRAINT tag_vod_relations_pkey PRIMARY KEY (id) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.tag_vod_relations + OWNER to postgres; +-- Index: tag_vod_relations_created_by_id_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_created_by_id_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_created_by_id_fk + ON api.tag_vod_relations USING btree + (created_by_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tag_vod_relations_updated_by_id_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_updated_by_id_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_updated_by_id_fk + ON api.tag_vod_relations USING btree + (updated_by_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00112_create-tvr_tag_links.sql b/services/migrations/migrations/00112_create-tvr_tag_links.sql new file mode 100644 index 0000000..48fdcb8 --- /dev/null +++ b/services/migrations/migrations/00112_create-tvr_tag_links.sql @@ -0,0 +1,38 @@ + +CREATE TABLE IF NOT EXISTS api.tag_vod_relations_tag_links +( + id integer NOT NULL, + tag_vod_relation_id integer, + tag_id integer, + CONSTRAINT tag_vod_relations_tag_links_pkey PRIMARY KEY (id), + CONSTRAINT tag_vod_relations_tag_links_unique UNIQUE (tag_vod_relation_id, tag_id), + CONSTRAINT tag_vod_relations_tag_links_fk FOREIGN KEY (tag_vod_relation_id) + REFERENCES api.tag_vod_relations (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT tag_vod_relations_tag_links_inv_fk FOREIGN KEY (tag_id) + REFERENCES api.tags (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.tag_vod_relations_tag_links + OWNER to postgres; +-- Index: tag_vod_relations_tag_links_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_tag_links_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_tag_links_fk + ON api.tag_vod_relations_tag_links USING btree + (tag_vod_relation_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tag_vod_relations_tag_links_inv_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_tag_links_inv_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_tag_links_inv_fk + ON api.tag_vod_relations_tag_links USING btree + (tag_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00113_create-tvr_vod_links.sql b/services/migrations/migrations/00113_create-tvr_vod_links.sql new file mode 100644 index 0000000..76abe0e --- /dev/null +++ b/services/migrations/migrations/00113_create-tvr_vod_links.sql @@ -0,0 +1,47 @@ + +CREATE TABLE IF NOT EXISTS api.tag_vod_relations_vod_links +( + id integer NOT NULL, + tag_vod_relation_id integer, + vod_id integer, + tag_vod_relation_order double precision, + CONSTRAINT tag_vod_relations_vod_links_pkey PRIMARY KEY (id), + CONSTRAINT tag_vod_relations_vod_links_unique UNIQUE (tag_vod_relation_id, vod_id), + CONSTRAINT tag_vod_relations_vod_links_fk FOREIGN KEY (tag_vod_relation_id) + REFERENCES api.tag_vod_relations (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT tag_vod_relations_vod_links_inv_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.tag_vod_relations_vod_links + OWNER to postgres; +-- Index: tag_vod_relations_vod_links_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_vod_links_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_vod_links_fk + ON api.tag_vod_relations_vod_links USING btree + (tag_vod_relation_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tag_vod_relations_vod_links_inv_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_vod_links_inv_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_vod_links_inv_fk + ON api.tag_vod_relations_vod_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tag_vod_relations_vod_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.tag_vod_relations_vod_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS tag_vod_relations_vod_links_order_inv_fk + ON api.tag_vod_relations_vod_links USING btree + (tag_vod_relation_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00114_create-tags_toy_links.sql b/services/migrations/migrations/00114_create-tags_toy_links.sql new file mode 100644 index 0000000..70cbe39 --- /dev/null +++ b/services/migrations/migrations/00114_create-tags_toy_links.sql @@ -0,0 +1,47 @@ + +CREATE TABLE IF NOT EXISTS api.tags_toy_links +( + id integer NOT NULL, + tag_id integer, + toy_id integer, + tag_order double precision, + CONSTRAINT tags_toy_links_pkey PRIMARY KEY (id), + CONSTRAINT tags_toy_links_unique UNIQUE (tag_id, toy_id), + CONSTRAINT tags_toy_links_fk FOREIGN KEY (tag_id) + REFERENCES api.tags (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT tags_toy_links_inv_fk FOREIGN KEY (toy_id) + REFERENCES api.toys (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.tags_toy_links + OWNER to postgres; +-- Index: tags_toy_links_fk + +-- DROP INDEX IF EXISTS api.tags_toy_links_fk; + +CREATE INDEX IF NOT EXISTS tags_toy_links_fk + ON api.tags_toy_links USING btree + (tag_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tags_toy_links_inv_fk + +-- DROP INDEX IF EXISTS api.tags_toy_links_inv_fk; + +CREATE INDEX IF NOT EXISTS tags_toy_links_inv_fk + ON api.tags_toy_links USING btree + (toy_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tags_toy_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.tags_toy_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS tags_toy_links_order_inv_fk + ON api.tags_toy_links USING btree + (tag_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00115_create-tags_vods_links.sql b/services/migrations/migrations/00115_create-tags_vods_links.sql new file mode 100644 index 0000000..7d7f78e --- /dev/null +++ b/services/migrations/migrations/00115_create-tags_vods_links.sql @@ -0,0 +1,55 @@ +CREATE TABLE IF NOT EXISTS api.tags_vods_links +( + id integer NOT NULL, + tag_id integer, + vod_id integer, + vod_order double precision, + tag_order double precision, + CONSTRAINT tags_vods_links_pkey PRIMARY KEY (id), + CONSTRAINT tags_vods_links_unique UNIQUE (tag_id, vod_id), + CONSTRAINT tags_vods_links_fk FOREIGN KEY (tag_id) + REFERENCES api.tags (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT tags_vods_links_inv_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.tags_vods_links + OWNER to postgres; +-- Index: tags_vods_links_fk + +-- DROP INDEX IF EXISTS api.tags_vods_links_fk; + +CREATE INDEX IF NOT EXISTS tags_vods_links_fk + ON api.tags_vods_links USING btree + (tag_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tags_vods_links_inv_fk + +-- DROP INDEX IF EXISTS api.tags_vods_links_inv_fk; + +CREATE INDEX IF NOT EXISTS tags_vods_links_inv_fk + ON api.tags_vods_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tags_vods_links_order_fk + +-- DROP INDEX IF EXISTS api.tags_vods_links_order_fk; + +CREATE INDEX IF NOT EXISTS tags_vods_links_order_fk + ON api.tags_vods_links USING btree + (vod_order ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: tags_vods_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.tags_vods_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS tags_vods_links_order_inv_fk + ON api.tags_vods_links USING btree + (tag_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00116_create-timestamps.sql b/services/migrations/migrations/00116_create-timestamps.sql new file mode 100644 index 0000000..f8d1168 --- /dev/null +++ b/services/migrations/migrations/00116_create-timestamps.sql @@ -0,0 +1,210 @@ +-- Table: api.timestamps + +-- DROP TABLE IF EXISTS api.timestamps; + +CREATE TABLE IF NOT EXISTS api.timestamps +( + id integer NOT NULL, + "time" integer, + creator_id integer, + created_at timestamp(6) without time zone, + updated_at timestamp(6) without time zone, + created_by_id integer, + updated_by_id integer, + CONSTRAINT timestamps_pkey PRIMARY KEY (id) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps + OWNER to postgres; +-- Index: timestamps_created_by_id_fk + +-- DROP INDEX IF EXISTS api.timestamps_created_by_id_fk; + +CREATE INDEX IF NOT EXISTS timestamps_created_by_id_fk + ON api.timestamps USING btree + (created_by_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_updated_by_id_fk + +-- DROP INDEX IF EXISTS api.timestamps_updated_by_id_fk; + +CREATE INDEX IF NOT EXISTS timestamps_updated_by_id_fk + ON api.timestamps USING btree + (updated_by_id ASC NULLS LAST) + TABLESPACE pg_default; + + +-- Table: api.timestamps + +-- DROP TABLE IF EXISTS api.timestamps; + +CREATE TABLE IF NOT EXISTS api.timestamps +( + id integer NOT NULL, + "time" integer, + creator_id integer, + created_at timestamp(6) without time zone, + updated_at timestamp(6) without time zone, + created_by_id integer, + updated_by_id integer, + CONSTRAINT timestamps_pkey PRIMARY KEY (id) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps + OWNER to postgres; +-- Index: timestamps_created_by_id_fk + +-- DROP INDEX IF EXISTS api.timestamps_created_by_id_fk; + +CREATE INDEX IF NOT EXISTS timestamps_created_by_id_fk + ON api.timestamps USING btree + (created_by_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_updated_by_id_fk + +-- DROP INDEX IF EXISTS api.timestamps_updated_by_id_fk; + +CREATE INDEX IF NOT EXISTS timestamps_updated_by_id_fk + ON api.timestamps USING btree + (updated_by_id ASC NULLS LAST) + TABLESPACE pg_default; + + +-- Table: api.timestamps + +-- DROP TABLE IF EXISTS api.timestamps; + +CREATE TABLE IF NOT EXISTS api.timestamps +( + id integer NOT NULL, + "time" integer, + creator_id integer, + created_at timestamp(6) without time zone, + updated_at timestamp(6) without time zone, + created_by_id integer, + updated_by_id integer, + CONSTRAINT timestamps_pkey PRIMARY KEY (id) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps + OWNER to postgres; +-- Index: timestamps_created_by_id_fk + +-- DROP INDEX IF EXISTS api.timestamps_created_by_id_fk; + +CREATE INDEX IF NOT EXISTS timestamps_created_by_id_fk + ON api.timestamps USING btree + (created_by_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_updated_by_id_fk + +-- DROP INDEX IF EXISTS api.timestamps_updated_by_id_fk; + +CREATE INDEX IF NOT EXISTS timestamps_updated_by_id_fk + ON api.timestamps USING btree + (updated_by_id ASC NULLS LAST) + TABLESPACE pg_default; + + + +-- Table: api.timestamps_upvoters_links + +-- DROP TABLE IF EXISTS api.timestamps_upvoters_links; + +CREATE TABLE IF NOT EXISTS api.timestamps_upvoters_links +( + id integer NOT NULL, + timestamp_id integer, + user_id integer, + user_order double precision, + CONSTRAINT timestamps_upvoters_links_pkey PRIMARY KEY (id), + CONSTRAINT timestamps_upvoters_links_unique UNIQUE (timestamp_id, user_id), + CONSTRAINT timestamps_upvoters_links_fk FOREIGN KEY (timestamp_id) + REFERENCES api.timestamps (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps_upvoters_links + OWNER to postgres; +-- Index: timestamps_upvoters_links_fk + +-- DROP INDEX IF EXISTS api.timestamps_upvoters_links_fk; + +CREATE INDEX IF NOT EXISTS timestamps_upvoters_links_fk + ON api.timestamps_upvoters_links USING btree + (timestamp_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_upvoters_links_inv_fk + +-- DROP INDEX IF EXISTS api.timestamps_upvoters_links_inv_fk; + +CREATE INDEX IF NOT EXISTS timestamps_upvoters_links_inv_fk + ON api.timestamps_upvoters_links USING btree + (user_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_upvoters_links_order_fk + +-- DROP INDEX IF EXISTS api.timestamps_upvoters_links_order_fk; + +CREATE INDEX IF NOT EXISTS timestamps_upvoters_links_order_fk + ON api.timestamps_upvoters_links USING btree + (user_order ASC NULLS LAST) + TABLESPACE pg_default; + + + +-- Table: api.timestamps_upvoters_links + +-- DROP TABLE IF EXISTS api.timestamps_upvoters_links; + +CREATE TABLE IF NOT EXISTS api.timestamps_upvoters_links +( + id integer NOT NULL, + timestamp_id integer, + user_id integer, + user_order double precision, + CONSTRAINT timestamps_upvoters_links_pkey PRIMARY KEY (id), + CONSTRAINT timestamps_upvoters_links_unique UNIQUE (timestamp_id, user_id), + CONSTRAINT timestamps_upvoters_links_fk FOREIGN KEY (timestamp_id) + REFERENCES api.timestamps (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps_upvoters_links + OWNER to postgres; +-- Index: timestamps_upvoters_links_fk + +-- DROP INDEX IF EXISTS api.timestamps_upvoters_links_fk; + +CREATE INDEX IF NOT EXISTS timestamps_upvoters_links_fk + ON api.timestamps_upvoters_links USING btree + (timestamp_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_upvoters_links_inv_fk + +-- DROP INDEX IF EXISTS api.timestamps_upvoters_links_inv_fk; + +CREATE INDEX IF NOT EXISTS timestamps_upvoters_links_inv_fk + ON api.timestamps_upvoters_links USING btree + (user_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_upvoters_links_order_fk + +-- DROP INDEX IF EXISTS api.timestamps_upvoters_links_order_fk; + +CREATE INDEX IF NOT EXISTS timestamps_upvoters_links_order_fk + ON api.timestamps_upvoters_links USING btree + (user_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00117_create-timestamps_tag_links.sql b/services/migrations/migrations/00117_create-timestamps_tag_links.sql new file mode 100644 index 0000000..5c16aae --- /dev/null +++ b/services/migrations/migrations/00117_create-timestamps_tag_links.sql @@ -0,0 +1,41 @@ +-- Table: api.timestamps_tag_links + +-- DROP TABLE IF EXISTS api.timestamps_tag_links; + +CREATE TABLE IF NOT EXISTS api.timestamps_tag_links +( + id integer NOT NULL, + timestamp_id integer, + tag_id integer, + CONSTRAINT timestamps_tag_links_pkey PRIMARY KEY (id), + CONSTRAINT timestamps_tag_links_unique UNIQUE (timestamp_id, tag_id), + CONSTRAINT timestamps_tag_links_fk FOREIGN KEY (timestamp_id) + REFERENCES api.timestamps (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT timestamps_tag_links_inv_fk FOREIGN KEY (tag_id) + REFERENCES api.tags (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps_tag_links + OWNER to postgres; +-- Index: timestamps_tag_links_fk + +-- DROP INDEX IF EXISTS api.timestamps_tag_links_fk; + +CREATE INDEX IF NOT EXISTS timestamps_tag_links_fk + ON api.timestamps_tag_links USING btree + (timestamp_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_tag_links_inv_fk + +-- DROP INDEX IF EXISTS api.timestamps_tag_links_inv_fk; + +CREATE INDEX IF NOT EXISTS timestamps_tag_links_inv_fk + ON api.timestamps_tag_links USING btree + (tag_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00118_create-timestamps_vod_links.sql b/services/migrations/migrations/00118_create-timestamps_vod_links.sql new file mode 100644 index 0000000..9f4d198 --- /dev/null +++ b/services/migrations/migrations/00118_create-timestamps_vod_links.sql @@ -0,0 +1,50 @@ +-- Table: api.timestamps_vod_links + +-- DROP TABLE IF EXISTS api.timestamps_vod_links; + +CREATE TABLE IF NOT EXISTS api.timestamps_vod_links +( + id integer NOT NULL, + timestamp_id integer, + vod_id integer, + timestamp_order double precision, + CONSTRAINT timestamps_vod_links_pkey PRIMARY KEY (id), + CONSTRAINT timestamps_vod_links_unique UNIQUE (timestamp_id, vod_id), + CONSTRAINT timestamps_vod_links_fk FOREIGN KEY (timestamp_id) + REFERENCES api.timestamps (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT timestamps_vod_links_inv_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.timestamps_vod_links + OWNER to postgres; +-- Index: timestamps_vod_links_fk + +-- DROP INDEX IF EXISTS api.timestamps_vod_links_fk; + +CREATE INDEX IF NOT EXISTS timestamps_vod_links_fk + ON api.timestamps_vod_links USING btree + (timestamp_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_vod_links_inv_fk + +-- DROP INDEX IF EXISTS api.timestamps_vod_links_inv_fk; + +CREATE INDEX IF NOT EXISTS timestamps_vod_links_inv_fk + ON api.timestamps_vod_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: timestamps_vod_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.timestamps_vod_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS timestamps_vod_links_order_inv_fk + ON api.timestamps_vod_links USING btree + (timestamp_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00119_create-toys_link_tag_links.sql b/services/migrations/migrations/00119_create-toys_link_tag_links.sql new file mode 100644 index 0000000..3b06a90 --- /dev/null +++ b/services/migrations/migrations/00119_create-toys_link_tag_links.sql @@ -0,0 +1,41 @@ +-- Table: api.toys_link_tag_links + +-- DROP TABLE IF EXISTS api.toys_link_tag_links; + +CREATE TABLE IF NOT EXISTS api.toys_link_tag_links +( + id integer NOT NULL, + toy_id integer, + tag_id integer, + CONSTRAINT toys_link_tag_links_pkey PRIMARY KEY (id), + CONSTRAINT toys_link_tag_links_unique UNIQUE (toy_id, tag_id), + CONSTRAINT toys_link_tag_links_fk FOREIGN KEY (toy_id) + REFERENCES api.toys (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT toys_link_tag_links_inv_fk FOREIGN KEY (tag_id) + REFERENCES api.tags (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.toys_link_tag_links + OWNER to postgres; +-- Index: toys_link_tag_links_fk + +-- DROP INDEX IF EXISTS api.toys_link_tag_links_fk; + +CREATE INDEX IF NOT EXISTS toys_link_tag_links_fk + ON api.toys_link_tag_links USING btree + (toy_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: toys_link_tag_links_inv_fk + +-- DROP INDEX IF EXISTS api.toys_link_tag_links_inv_fk; + +CREATE INDEX IF NOT EXISTS toys_link_tag_links_inv_fk + ON api.toys_link_tag_links USING btree + (tag_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00120_create-vods_mux_asset_links.sql b/services/migrations/migrations/00120_create-vods_mux_asset_links.sql new file mode 100644 index 0000000..2b29cd5 --- /dev/null +++ b/services/migrations/migrations/00120_create-vods_mux_asset_links.sql @@ -0,0 +1,41 @@ +-- Table: api.vods_mux_asset_links + +-- DROP TABLE IF EXISTS api.vods_mux_asset_links; + +CREATE TABLE IF NOT EXISTS api.vods_mux_asset_links +( + id integer NOT NULL, + vod_id integer, + mux_asset_id integer, + CONSTRAINT vods_mux_asset_links_pkey PRIMARY KEY (id), + CONSTRAINT vods_mux_asset_links_unique UNIQUE (vod_id, mux_asset_id), + CONSTRAINT vods_mux_asset_links_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vods_mux_asset_links_inv_fk FOREIGN KEY (mux_asset_id) + REFERENCES api.mux_assets (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vods_mux_asset_links + OWNER to postgres; +-- Index: vods_mux_asset_links_fk + +-- DROP INDEX IF EXISTS api.vods_mux_asset_links_fk; + +CREATE INDEX IF NOT EXISTS vods_mux_asset_links_fk + ON api.vods_mux_asset_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_mux_asset_links_inv_fk + +-- DROP INDEX IF EXISTS api.vods_mux_asset_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_mux_asset_links_inv_fk + ON api.vods_mux_asset_links USING btree + (mux_asset_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00121_create-vods_stream_links.sql b/services/migrations/migrations/00121_create-vods_stream_links.sql new file mode 100644 index 0000000..57b37b9 --- /dev/null +++ b/services/migrations/migrations/00121_create-vods_stream_links.sql @@ -0,0 +1,50 @@ +-- Table: api.vods_stream_links + +-- DROP TABLE IF EXISTS api.vods_stream_links; + +CREATE TABLE IF NOT EXISTS api.vods_stream_links +( + id integer NOT NULL, + vod_id integer, + stream_id integer, + vod_order double precision, + CONSTRAINT vods_stream_links_pkey PRIMARY KEY (id), + CONSTRAINT vods_stream_links_unique UNIQUE (vod_id, stream_id), + CONSTRAINT vods_stream_links_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vods_stream_links_inv_fk FOREIGN KEY (stream_id) + REFERENCES api.streams (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vods_stream_links + OWNER to postgres; +-- Index: vods_stream_links_fk + +-- DROP INDEX IF EXISTS api.vods_stream_links_fk; + +CREATE INDEX IF NOT EXISTS vods_stream_links_fk + ON api.vods_stream_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_stream_links_inv_fk + +-- DROP INDEX IF EXISTS api.vods_stream_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_stream_links_inv_fk + ON api.vods_stream_links USING btree + (stream_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_stream_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.vods_stream_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_stream_links_order_inv_fk + ON api.vods_stream_links USING btree + (vod_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00122_create-vods_thumbnail_links.sql b/services/migrations/migrations/00122_create-vods_thumbnail_links.sql new file mode 100644 index 0000000..269838c --- /dev/null +++ b/services/migrations/migrations/00122_create-vods_thumbnail_links.sql @@ -0,0 +1,87 @@ + +-- Table: api.b2_files + +-- DROP TABLE IF EXISTS api.b2_files; + +CREATE TABLE IF NOT EXISTS api.b2_files +( + id integer NOT NULL, + url character varying(255) COLLATE pg_catalog."default", + key character varying(255) COLLATE pg_catalog."default", + upload_id character varying(255) COLLATE pg_catalog."default", + created_at timestamp(6) without time zone, + updated_at timestamp(6) without time zone, + created_by_id integer, + updated_by_id integer, + cdn_url character varying(255) COLLATE pg_catalog."default", + CONSTRAINT b2_files_pkey PRIMARY KEY (id) +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.b2_files + OWNER to postgres; +-- Index: b2_files_created_by_id_fk + +-- DROP INDEX IF EXISTS api.b2_files_created_by_id_fk; + +CREATE INDEX IF NOT EXISTS b2_files_created_by_id_fk + ON api.b2_files USING btree + (created_by_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: b2_files_updated_by_id_fk + +-- DROP INDEX IF EXISTS api.b2_files_updated_by_id_fk; + +CREATE INDEX IF NOT EXISTS b2_files_updated_by_id_fk + ON api.b2_files USING btree + (updated_by_id ASC NULLS LAST) + TABLESPACE pg_default; + + + + +-- Table: api.vods_thumbnail_links + +-- DROP TABLE IF EXISTS api.vods_thumbnail_links; + +CREATE TABLE IF NOT EXISTS api.vods_thumbnail_links +( + id integer NOT NULL, + vod_id integer, + b_2_file_id integer, + CONSTRAINT vods_thumbnail_links_pkey PRIMARY KEY (id), + CONSTRAINT vods_thumbnail_links_unique UNIQUE (vod_id, b_2_file_id), + CONSTRAINT vods_thumbnail_links_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vods_thumbnail_links_inv_fk FOREIGN KEY (b_2_file_id) + REFERENCES api.b2_files (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vods_thumbnail_links + OWNER to postgres; +-- Index: vods_thumbnail_links_fk + +-- DROP INDEX IF EXISTS api.vods_thumbnail_links_fk; + +CREATE INDEX IF NOT EXISTS vods_thumbnail_links_fk + ON api.vods_thumbnail_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_thumbnail_links_inv_fk + +-- DROP INDEX IF EXISTS api.vods_thumbnail_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_thumbnail_links_inv_fk + ON api.vods_thumbnail_links USING btree + (b_2_file_id ASC NULLS LAST) + TABLESPACE pg_default; + + + diff --git a/services/migrations/migrations/00123_create-vods_uploader_links.sql b/services/migrations/migrations/00123_create-vods_uploader_links.sql new file mode 100644 index 0000000..43f4e5f --- /dev/null +++ b/services/migrations/migrations/00123_create-vods_uploader_links.sql @@ -0,0 +1,37 @@ +-- Table: api.vods_uploader_links + +-- DROP TABLE IF EXISTS api.vods_uploader_links; + +CREATE TABLE IF NOT EXISTS api.vods_uploader_links +( + id integer NOT NULL, + vod_id integer, + user_id integer, + CONSTRAINT vods_uploader_links_pkey PRIMARY KEY (id), + CONSTRAINT vods_uploader_links_unique UNIQUE (vod_id, user_id), + CONSTRAINT vods_uploader_links_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vods_uploader_links + OWNER to postgres; +-- Index: vods_uploader_links_fk + +-- DROP INDEX IF EXISTS api.vods_uploader_links_fk; + +CREATE INDEX IF NOT EXISTS vods_uploader_links_fk + ON api.vods_uploader_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_uploader_links_inv_fk + +-- DROP INDEX IF EXISTS api.vods_uploader_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_uploader_links_inv_fk + ON api.vods_uploader_links USING btree + (user_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00124_create-vods_video_src_b_2_links.sql b/services/migrations/migrations/00124_create-vods_video_src_b_2_links.sql new file mode 100644 index 0000000..1f37371 --- /dev/null +++ b/services/migrations/migrations/00124_create-vods_video_src_b_2_links.sql @@ -0,0 +1,41 @@ +-- Table: api.vods_video_src_b_2_links + +-- DROP TABLE IF EXISTS api.vods_video_src_b_2_links; + +CREATE TABLE IF NOT EXISTS api.vods_video_src_b_2_links +( + id integer NOT NULL, + vod_id integer, + b_2_file_id integer, + CONSTRAINT vods_video_src_b_2_links_pkey PRIMARY KEY (id), + CONSTRAINT vods_video_src_b_2_links_unique UNIQUE (vod_id, b_2_file_id), + CONSTRAINT vods_video_src_b_2_links_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vods_video_src_b_2_links_inv_fk FOREIGN KEY (b_2_file_id) + REFERENCES api.b2_files (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vods_video_src_b_2_links + OWNER to postgres; +-- Index: vods_video_src_b_2_links_fk + +-- DROP INDEX IF EXISTS api.vods_video_src_b_2_links_fk; + +CREATE INDEX IF NOT EXISTS vods_video_src_b_2_links_fk + ON api.vods_video_src_b_2_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_video_src_b_2_links_inv_fk + +-- DROP INDEX IF EXISTS api.vods_video_src_b_2_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_video_src_b_2_links_inv_fk + ON api.vods_video_src_b_2_links USING btree + (b_2_file_id ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00125_create-vods_vtuber_links.sql b/services/migrations/migrations/00125_create-vods_vtuber_links.sql new file mode 100644 index 0000000..a0f280f --- /dev/null +++ b/services/migrations/migrations/00125_create-vods_vtuber_links.sql @@ -0,0 +1,50 @@ +-- Table: api.vods_vtuber_links + +-- DROP TABLE IF EXISTS api.vods_vtuber_links; + +CREATE TABLE IF NOT EXISTS api.vods_vtuber_links +( + id integer NOT NULL, + vod_id integer, + vtuber_id integer, + vod_order double precision, + CONSTRAINT vods_vtuber_links_pkey PRIMARY KEY (id), + CONSTRAINT vods_vtuber_links_unique UNIQUE (vod_id, vtuber_id), + CONSTRAINT vods_vtuber_links_fk FOREIGN KEY (vod_id) + REFERENCES api.vods (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vods_vtuber_links_inv_fk FOREIGN KEY (vtuber_id) + REFERENCES api.vtubers (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vods_vtuber_links + OWNER to postgres; +-- Index: vods_vtuber_links_fk + +-- DROP INDEX IF EXISTS api.vods_vtuber_links_fk; + +CREATE INDEX IF NOT EXISTS vods_vtuber_links_fk + ON api.vods_vtuber_links USING btree + (vod_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_vtuber_links_inv_fk + +-- DROP INDEX IF EXISTS api.vods_vtuber_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_vtuber_links_inv_fk + ON api.vods_vtuber_links USING btree + (vtuber_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vods_vtuber_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.vods_vtuber_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS vods_vtuber_links_order_inv_fk + ON api.vods_vtuber_links USING btree + (vod_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00126_create-vtubers_toy_links.sql b/services/migrations/migrations/00126_create-vtubers_toy_links.sql new file mode 100644 index 0000000..57c816f --- /dev/null +++ b/services/migrations/migrations/00126_create-vtubers_toy_links.sql @@ -0,0 +1,105 @@ +-- Table: api.vtubers_toy_links + +-- DROP TABLE IF EXISTS api.vtubers_toy_links; + +CREATE TABLE IF NOT EXISTS api.vtubers_toy_links +( + id integer NOT NULL, + vtuber_id integer, + toy_id integer, + vtuber_order double precision, + CONSTRAINT vtubers_toy_links_pkey PRIMARY KEY (id), + CONSTRAINT vtubers_toy_links_unique UNIQUE (vtuber_id, toy_id), + CONSTRAINT vtubers_toy_links_fk FOREIGN KEY (vtuber_id) + REFERENCES api.vtubers (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vtubers_toy_links_inv_fk FOREIGN KEY (toy_id) + REFERENCES api.toys (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vtubers_toy_links + OWNER to postgres; +-- Index: vtubers_toy_links_fk + +-- DROP INDEX IF EXISTS api.vtubers_toy_links_fk; + +CREATE INDEX IF NOT EXISTS vtubers_toy_links_fk + ON api.vtubers_toy_links USING btree + (vtuber_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vtubers_toy_links_inv_fk + +-- DROP INDEX IF EXISTS api.vtubers_toy_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vtubers_toy_links_inv_fk + ON api.vtubers_toy_links USING btree + (toy_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vtubers_toy_links_order_inv_fk + +-- DROP INDEX IF EXISTS api.vtubers_toy_links_order_inv_fk; + +CREATE INDEX IF NOT EXISTS vtubers_toy_links_order_inv_fk + ON api.vtubers_toy_links USING btree + (vtuber_order ASC NULLS LAST) + TABLESPACE pg_default; + + + +-- also adding vtubers_toys_links + +-- Table: api.vtubers_toys_links + +-- DROP TABLE IF EXISTS api.vtubers_toys_links; + +CREATE TABLE IF NOT EXISTS api.vtubers_toys_links +( + id integer NOT NULL, + vtuber_id integer, + toy_id integer, + toy_order double precision, + CONSTRAINT vtubers_toys_links_pkey PRIMARY KEY (id), + CONSTRAINT vtubers_toys_links_unique UNIQUE (vtuber_id, toy_id), + CONSTRAINT vtubers_toys_links_fk FOREIGN KEY (vtuber_id) + REFERENCES api.vtubers (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE, + CONSTRAINT vtubers_toys_links_inv_fk FOREIGN KEY (toy_id) + REFERENCES api.toys (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE +) + +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS api.vtubers_toys_links + OWNER to postgres; +-- Index: vtubers_toys_links_fk + +-- DROP INDEX IF EXISTS api.vtubers_toys_links_fk; + +CREATE INDEX IF NOT EXISTS vtubers_toys_links_fk + ON api.vtubers_toys_links USING btree + (vtuber_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vtubers_toys_links_inv_fk + +-- DROP INDEX IF EXISTS api.vtubers_toys_links_inv_fk; + +CREATE INDEX IF NOT EXISTS vtubers_toys_links_inv_fk + ON api.vtubers_toys_links USING btree + (toy_id ASC NULLS LAST) + TABLESPACE pg_default; +-- Index: vtubers_toys_links_order_fk + +-- DROP INDEX IF EXISTS api.vtubers_toys_links_order_fk; + +CREATE INDEX IF NOT EXISTS vtubers_toys_links_order_fk + ON api.vtubers_toys_links USING btree + (toy_order ASC NULLS LAST) + TABLESPACE pg_default; \ No newline at end of file diff --git a/services/migrations/migrations/00127_grant-permissions-for-vods_vtuber_links.sql b/services/migrations/migrations/00127_grant-permissions-for-vods_vtuber_links.sql new file mode 100644 index 0000000..1f7228b --- /dev/null +++ b/services/migrations/migrations/00127_grant-permissions-for-vods_vtuber_links.sql @@ -0,0 +1,2 @@ +GRANT all ON api.vods_vtuber_links TO automation; +GRANT SELECT ON api.vods_vtuber_links TO web_anon; diff --git a/services/migrations/migrations/00128_add-vtuber-fk-to-vods.sql b/services/migrations/migrations/00128_add-vtuber-fk-to-vods.sql new file mode 100644 index 0000000..c6e14cb --- /dev/null +++ b/services/migrations/migrations/00128_add-vtuber-fk-to-vods.sql @@ -0,0 +1,6 @@ + +ALTER TABLE IF EXISTS api.vods + ADD CONSTRAINT vods_vtuber_fk FOREIGN KEY (vtuber) + REFERENCES api.vtubers (id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE CASCADE \ No newline at end of file diff --git a/services/migrations/migrations/00129_add-vtuber_id-to-vods.sql b/services/migrations/migrations/00129_add-vtuber_id-to-vods.sql new file mode 100644 index 0000000..096f800 --- /dev/null +++ b/services/migrations/migrations/00129_add-vtuber_id-to-vods.sql @@ -0,0 +1,9 @@ +ALTER TABLE api.vods ADD COLUMN vtuber_id INTEGER; + +ALTER TABLE api.vods + DROP CONSTRAINT vods_vtuber_fk; + +ALTER TABLE api.vods + ADD CONSTRAINT vods_vtuber_fk FOREIGN KEY (vtuber_id) + REFERENCES api.vtubers (id) + ON DELETE CASCADE; \ No newline at end of file diff --git a/services/migrations/migrations/00130_add-s3_files-fk-to-vods.sql b/services/migrations/migrations/00130_add-s3_files-fk-to-vods.sql new file mode 100644 index 0000000..4c6fcdc --- /dev/null +++ b/services/migrations/migrations/00130_add-s3_files-fk-to-vods.sql @@ -0,0 +1,6 @@ +ALTER TABLE api.vods ADD COLUMN s3_file_id INTEGER; + +ALTER TABLE api.vods + ADD CONSTRAINT vods_s3_file_fk FOREIGN KEY (s3_file_id) + REFERENCES api.s3_files (id) + ON DELETE CASCADE; \ No newline at end of file diff --git a/services/migrations/migrations/00131_grant-perms-on-s3_files.sql b/services/migrations/migrations/00131_grant-perms-on-s3_files.sql new file mode 100644 index 0000000..6593f67 --- /dev/null +++ b/services/migrations/migrations/00131_grant-perms-on-s3_files.sql @@ -0,0 +1,7 @@ +GRANT all ON api.s3_files TO automation; +-- GRANT SELECT ON api.s3_files TO web_anon; + +-- we only want to grant access to this resource to patrons. how do we do that? @TODO + + + diff --git a/services/migrations/migrations/00132_rename-vtuber_num-to-vtuber_id.sql b/services/migrations/migrations/00132_rename-vtuber_num-to-vtuber_id.sql new file mode 100644 index 0000000..4c05ef0 --- /dev/null +++ b/services/migrations/migrations/00132_rename-vtuber_num-to-vtuber_id.sql @@ -0,0 +1,7 @@ +ALTER TABLE IF EXISTS api.streams + ADD COLUMN IF NOT EXISTS vtuber_id INT; + +ALTER TABLE api.streams + ADD CONSTRAINT streams_vtuber_fk FOREIGN KEY (vtuber_id) + REFERENCES api.vtubers (id) + ON DELETE CASCADE; \ No newline at end of file diff --git a/services/migrations/migrations/00133_add-mux_assets-fk-to-vods.sql b/services/migrations/migrations/00133_add-mux_assets-fk-to-vods.sql new file mode 100644 index 0000000..04e7d0a --- /dev/null +++ b/services/migrations/migrations/00133_add-mux_assets-fk-to-vods.sql @@ -0,0 +1,6 @@ +ALTER TABLE api.vods ADD COLUMN mux_asset_id INTEGER; + +ALTER TABLE api.vods + ADD CONSTRAINT vods_mux_asset_fk FOREIGN KEY (mux_asset_id) + REFERENCES api.mux_assets (id) + ON DELETE CASCADE; diff --git a/services/migrations/migrations/00134_add-thumbnail-fk-to-vods.sql b/services/migrations/migrations/00134_add-thumbnail-fk-to-vods.sql new file mode 100644 index 0000000..6dbafc0 --- /dev/null +++ b/services/migrations/migrations/00134_add-thumbnail-fk-to-vods.sql @@ -0,0 +1,6 @@ +ALTER TABLE api.vods ADD COLUMN thumbnail_id INTEGER; + +ALTER TABLE api.vods + ADD CONSTRAINT vods_thumbnail_fk FOREIGN KEY (thumbnail_id) + REFERENCES api.b2_files (id) + ON DELETE CASCADE; diff --git a/services/next/app/api/auth/[...nextauth]/route.ts b/services/next/app/api/auth/[...nextauth]/route.ts new file mode 100644 index 0000000..98f6330 --- /dev/null +++ b/services/next/app/api/auth/[...nextauth]/route.ts @@ -0,0 +1,17 @@ +/** + * api/auth/[...nextauth]/route.ts + */ + +import { authOptions } from "@/app/lib/auth" +import NextAuth from "next-auth" + + +// v4 +const handler = NextAuth(authOptions) +export { handler as GET, handler as POST } + + + + +// v5 +// export { GET, POST } from "@/app/lib/auth" diff --git a/services/next/app/api/auth/[[...path]]/route.ts b/services/next/app/api/auth/[[...path]]/route.ts deleted file mode 100644 index 04b7e7d..0000000 --- a/services/next/app/api/auth/[[...path]]/route.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { getAppDirRequestHandler } from 'supertokens-node/nextjs'; -import { NextRequest, NextResponse } from 'next/server'; -import { ensureSuperTokensInit } from '../../../config/backend'; - -ensureSuperTokensInit(); - -const handleCall = getAppDirRequestHandler(); - -export async function GET(request: NextRequest) { - const res = await handleCall(request); - if (!res.headers.has('Cache-Control')) { - // This is needed for production deployments with Vercel - res.headers.set( - 'Cache-Control', - 'no-cache, no-store, max-age=0, must-revalidate' - ) - } - return res; -} - -export async function POST(request: NextRequest) { - return handleCall(request); -} - -export async function DELETE(request: NextRequest) { - return handleCall(request); -} - -export async function PUT(request: NextRequest) { - return handleCall(request); -} - -export async function PATCH(request: NextRequest) { - return handleCall(request); -} - -export async function HEAD(request: NextRequest) { - return handleCall(request); -} \ No newline at end of file diff --git a/services/next/app/api/page.tsx b/services/next/app/api/page.tsx index f1a47d8..ace3230 100644 --- a/services/next/app/api/page.tsx +++ b/services/next/app/api/page.tsx @@ -2,47 +2,14 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; +import { postgrestUrl } from '@/app/lib/constants' import Link from 'next/link' -import { Highlight, themes } from "prism-react-renderer"; +import { useSession } from "next-auth/react"; +import { signIn } from "next-auth/react" -const bootstrapScript = `#!/bin/bash - -## bootstrap.sh -## tested on Ubuntu 22.04 - -## install dependencies -cd -apt install -y screen - -## Open necessary firewall ports -ufw allow 9096/tcp -ufw allow 9094/tcp -ufw allow 4001/tcp -ufw allow 4001/udp - -## Download kubo -wget 'https://dist.ipfs.tech/kubo/v0.24.0/kubo_v0.24.0_linux-amd64.tar.gz' -tar xvzf ./kubo_v0.24.0_linux-amd64.tar.gz -chmod +x ./kubo/install.sh -./kubo/install.sh - -## Download ipfs-cluster-follow -wget 'https://dist.ipfs.tech/ipfs-cluster-follow/v1.0.7/ipfs-cluster-follow_v1.0.7_linux-amd64.tar.gz' -tar xvzf ./ipfs-cluster-follow_v1.0.7_linux-amd64.tar.gz -chmod +x ./ipfs-cluster-follow/ipfs-cluster-follow -mv ./ipfs-cluster-follow/ipfs-cluster-follow /usr/local/bin/ - -## initialize ipfs -ipfs init - -## run ipfs in a screen session -screen -d -m ipfs daemon - -## run ipfs-cluster-follow -CLUSTER_PEERNAME="my-cluster-peer-name" ipfs-cluster-follow futureporn.net run --init https://futureporn.net/api/service.json -` export default function Page() { + const { data: session } = useSession(); return (
@@ -68,50 +35,23 @@ export default function Page() {

Data API

-

The Data API contains all the data served by this website in JSON format, including IPFS Content IDs (CID), VOD titles, dates, and stream announcement links.

-

Futureporn API Version 1

+

The Data API contains all the data served by this website in JSON format. This API is unstable, meaning it's subject to changes that may break your integrations.

+
Futureporn API Version 1
+
Futureporn API Version 2
-

IPFS Cluster Template

-

The IPFS Cluster Template allows other IPFS cluster instances to join the Futureporn.net IPFS cluster as a follower peer . Cluster peers automatically pin (replicate) the IPFS content listed on this website.

+

Client Session (test)

+
{JSON.stringify(session)}
-

Basic instructions are as follows

-

1. Download & install both kubo and ipfs-cluster-follow onto your server.

-

2. Initialize your ipfs repo & start the ipfs daemon

-

3. Join the cluster using ipfs-cluster-follow

+ -

Below is an example bash script to get everything you need to run an IPFS follower peer. This is only an example and may need tweaks to run in your environment.

- - - {({ className, style, tokens, getLineProps, getTokenProps }) => ( -
-                                        {tokens.map((line, i) => (
-                                            
- {line.map((token, key) => ( - - ))} -
- ))} -
- )} -
- - - - - -

Futureporn IPFS Cluster Template (service.json)

-
+
-
) diff --git a/services/next/app/api/patreon/currently-entitled-tiers/route.ts b/services/next/app/api/patreon/currently-entitled-tiers/route.ts new file mode 100644 index 0000000..89fe62f --- /dev/null +++ b/services/next/app/api/patreon/currently-entitled-tiers/route.ts @@ -0,0 +1,53 @@ +'use server'; + +import { NextResponse } from "next/server"; +import { getServerSession } from "next-auth"; +import { getKeycloakIdpToken, getPatreonMemberships } from "@/app/lib/patreon"; +import { syncronizeKeycloakRoles } from '@/app/lib/keycloak'; +import { authOptions } from "@/app/lib/auth"; + +export async function GET(req: Request, res: Response) { + + console.log('lets run getServerSession()') + + + // @TODO @TODO @TODO @todo @todo @todo THE PROBLEM IS WITH getServerSession()!!!! + // or rather, the JWE we generated in middleware.ts is invalid and the error is manifesting within the getServerSession() invocation. + const session = await getServerSession(authOptions); + // console.log('session as follows') + // console.log(session) + + if (session) { + + let keycloakIdpToken, patreonTiersList + + if (!session.token?.access_token) { + console.error('session.token.access_token was missing') + return NextResponse.json({ error: `Failed to get access token from Session`}, { status: 500 }) + } + + try { + keycloakIdpToken = await getKeycloakIdpToken(session.token.access_token) + } catch (e) { + return NextResponse.json({ error: `Failed to get Patreon token (Keycloak IDP). e=${e}`}, { status: 401 }) + } + + try { + patreonTiersList = await getPatreonMemberships(keycloakIdpToken) + } catch (e) { + return NextResponse.json({ error: `Failed to get patreon memberships. e=${e}`}, { status: 401 }) + } + + + // side effect which grants the appropriate keycloak roles to the user + try { + await syncronizeKeycloakRoles(session.user.id, patreonTiersList) + } catch (e) { + return NextResponse.json({ error: `Failed to syncronize roles` }, { status: 500 }) + } + + + return NextResponse.json(patreonTiersList); + } + return NextResponse.json({ error: "You must be logged in." }, { status: 401 }); +} diff --git a/services/next/app/api/patreon/session/route.ts b/services/next/app/api/patreon/session/route.ts new file mode 100644 index 0000000..6a2b155 --- /dev/null +++ b/services/next/app/api/patreon/session/route.ts @@ -0,0 +1,18 @@ +'use server'; + +import { NextResponse } from "next/server"; +import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/lib/auth"; + + +export async function GET() { + + + const session = await getServerSession(authOptions); + + if (session) { + + return NextResponse.json(session) + } + return NextResponse.json({ error: "You must be logged in." }, { status: 401 }); +} diff --git a/services/next/app/api/session/route.ts b/services/next/app/api/session/route.ts new file mode 100644 index 0000000..7cff2d7 --- /dev/null +++ b/services/next/app/api/session/route.ts @@ -0,0 +1,12 @@ +import { getServerSession } from "next-auth"; +import { authOptions } from "@/app/lib/auth"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + const session = await getServerSession(authOptions); + + return NextResponse.json({ + authenticated: !!session, + session, + }); +} diff --git a/services/next/app/api/user/route.ts b/services/next/app/api/user/route.ts new file mode 100644 index 0000000..48abd02 --- /dev/null +++ b/services/next/app/api/user/route.ts @@ -0,0 +1,3 @@ +import { getServerSession } from "next-auth"; +import { NextResponse, NextRequest } from "next/server"; + diff --git a/services/next/app/archive/page.tsx b/services/next/app/archive/page.tsx index ae76ed2..f9c17bd 100644 --- a/services/next/app/archive/page.tsx +++ b/services/next/app/archive/page.tsx @@ -84,6 +84,7 @@ export default async function Page() {

Stream Archive

+ {/* */} diff --git a/services/next/app/auth/[[...path]]/page.tsx b/services/next/app/auth/[[...path]]/page.tsx deleted file mode 100644 index 269d945..0000000 --- a/services/next/app/auth/[[...path]]/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { redirectToAuth } from 'supertokens-auth-react'; -import SuperTokens from 'supertokens-auth-react/ui'; -import { ThirdPartyPreBuiltUI } from 'supertokens-auth-react/recipe/thirdparty/prebuiltui'; -// import { PasswordlessPreBuiltUI } from 'supertokens-auth-react/recipe/passwordless/prebuiltui'; - -export default function Auth() { - // if the user visits a page that is not handled by us (like /auth/random), then we redirect them back to the auth page. - const [loaded, setLoaded] = useState(false); - useEffect(() => { - if ( - SuperTokens.canHandleRoute([ThirdPartyPreBuiltUI]) === false - ) { - redirectToAuth({ redirectBack: false }); - } else { - setLoaded(true); - } - }, []); - - if (loaded) { - return SuperTokens.getRoutingComponent([ThirdPartyPreBuiltUI]); - } - - return null; -} \ No newline at end of file diff --git a/services/next/app/callback/route.ts b/services/next/app/callback/route.ts deleted file mode 100644 index d927bfe..0000000 --- a/services/next/app/callback/route.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { handleSignIn } from '@logto/next/server-actions'; -import { redirect } from 'next/navigation'; -import { NextRequest } from 'next/server'; -import { logtoConfig } from '../logto'; - -export async function GET(request: NextRequest) { - const searchParams = request.nextUrl.searchParams; - await handleSignIn(logtoConfig, searchParams); - - redirect('/'); -} \ No newline at end of file diff --git a/services/next/app/components/access-denied-screen.tsx b/services/next/app/components/access-denied-screen.tsx new file mode 100644 index 0000000..e1f692d --- /dev/null +++ b/services/next/app/components/access-denied-screen.tsx @@ -0,0 +1,24 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faCircleXmark, faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons" +import Link from "next/link" + +export default function AccessDeniedScreen(requiredUserRole: string, featureName: string = 'This feature') { + return <> +
+
+

Access denied

+
+
+

{featureName} is only available to {requiredUserRole}s

+

To unlock this feature, make a pledge at + patreon.com/CJ_Clippy + + +

+
+
+ +} \ No newline at end of file diff --git a/services/next/app/components/auth-buttons.tsx b/services/next/app/components/auth-buttons.tsx new file mode 100644 index 0000000..0ac89fa --- /dev/null +++ b/services/next/app/components/auth-buttons.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { signIn, signOut } from "next-auth/react"; +import Link from "next/link"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faUser } from "@fortawesome/free-solid-svg-icons"; + +export const LoginButton = () => { + return ( + + ); +}; + +export const RegisterButton = () => { + return ( + + Register + + ); +}; + +export const LogoutButton = () => { + return ( + + ); +}; + +export const ProfileButton = () => { + return ( + + Profile + + ) +}; diff --git a/services/next/app/components/auth-provider.tsx b/services/next/app/components/auth-provider.tsx new file mode 100644 index 0000000..04e162a --- /dev/null +++ b/services/next/app/components/auth-provider.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { SessionProvider } from "next-auth/react"; + +type Props = { + children: React.ReactNode; +} + +export default function AuthProvider({ children }: Props) { + return {children}; +} \ No newline at end of file diff --git a/services/next/app/components/auth.tsx b/services/next/app/components/auth.tsx deleted file mode 100644 index 105e704..0000000 --- a/services/next/app/components/auth.tsx +++ /dev/null @@ -1,133 +0,0 @@ -'use client'; - -import { createContext, useContext, ReactNode } from 'react'; -import { useRouter } from 'next/navigation'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faPatreon } from '@fortawesome/free-brands-svg-icons'; -import { useLocalStorageValue } from '@react-hookz/web'; -import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons'; -import Skeleton from 'react-loading-skeleton'; -import { strapiUrl } from '@/app/lib/constants'; -// import NextAuth from 'next-auth'; // this is (pipedream) wishlist -// import Providers from 'next-auth/providers'; - -export interface IJWT { - jwt: string; - user: IUser; -} - -export interface IUser { - id: number; - username: string; - email: string; - provider: string; - confirmed: boolean; - blocked: boolean; - createdAt: string; - updatedAt: string; - isNamePublic: boolean; - avatar: string | null; - isLinkPublic: boolean; - vanityLink: string | null; - patreonBenefits: string; -} - -export interface IAuthData { - accessToken: string | null; - user: IUser | null; -} - -export interface IUseAuth { - authData: IAuthData | null | undefined; - setAuthData: (data: IAuthData | null) => void; - lastVisitedPath: string | undefined; - login: () => void; - logout: () => void; -} - -export const AuthContext = createContext(null); - -interface IAuthContextProps { - children: ReactNode; -} -export function AuthProvider({ children }: IAuthContextProps): React.JSX.Element { - const { value: authData, set: setAuthData } = useLocalStorageValue('authData', { - defaultValue: null, - }); - - const { value: lastVisitedPath, set: setLastVisitedPath } = useLocalStorageValue('lastVisitedPath', { - defaultValue: '/profile', - initializeWithValue: false, - }); - const router = useRouter(); - - const login = async () => { - const currentPath = window.location.pathname; - setLastVisitedPath(currentPath); - router.push(`${strapiUrl}/api/connect/patreon`); - }; - - const logout = () => { - setAuthData({ accessToken: null, user: null }); - }; - - return ( - - {children} - - ); -} - -export function LoginButton() { - const context = useContext(AuthContext); - if (!context) return ; - const { login } = context; - return ( - - ); -} - -export function LogoutButton() { - const context = useContext(AuthContext); - if (!context) return <>; - const { logout } = context; - return ( - - ); -} - -// export function useAuth(): IUseAuth { -// const context = useContext(AuthContext); -// if (!context) { -// throw new Error('useAuth must be used within an AuthProvider'); -// } -// return context; -// } diff --git a/services/next/app/components/auth.tsx.old b/services/next/app/components/auth.tsx.old new file mode 100644 index 0000000..8ce7801 --- /dev/null +++ b/services/next/app/components/auth.tsx.old @@ -0,0 +1,116 @@ +'use client'; + +import { createContext, useContext, ReactNode } from 'react'; +import { useRouter } from 'next/navigation'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faPatreon } from '@fortawesome/free-brands-svg-icons'; +import { useLocalStorageValue } from '@react-hookz/web'; +import { faRightFromBracket } from '@fortawesome/free-solid-svg-icons'; +import Skeleton from 'react-loading-skeleton'; +import { strapiUrl } from '@/app/lib/constants'; +import NextAuth from 'next-auth'; +import Providers from 'next-auth/providers'; + +// export interface IJWT { +// jwt: string; +// user: IUser; +// } + +// export interface IUser { +// id: number; +// username: string; +// email: string; +// provider: string; +// confirmed: boolean; +// blocked: boolean; +// createdAt: string; +// updatedAt: string; +// isNamePublic: boolean; +// avatar: string | null; +// isLinkPublic: boolean; +// vanityLink: string | null; +// patreonBenefits: string; +// } + +// export interface IAuthData { +// accessToken: string | null; +// user: IUser | null; +// } + +// export interface IUseAuth { +// authData: IAuthData | null | undefined; +// setAuthData: (data: IAuthData | null) => void; +// lastVisitedPath: string | undefined; +// login: () => void; +// logout: () => void; +// } + +// export const AuthContext = createContext(null); + +// interface IAuthContextProps { +// children: ReactNode; +// } +// export function AuthProvider({ children }: IAuthContextProps): React.JSX.Element { +// const { value: authData, set: setAuthData } = useLocalStorageValue('authData', { +// defaultValue: null, +// }); + +// const { value: lastVisitedPath, set: setLastVisitedPath } = useLocalStorageValue('lastVisitedPath', { +// defaultValue: '/profile', +// initializeWithValue: false, +// }); +// const router = useRouter(); + +// const login = async () => { +// const currentPath = window.location.pathname; +// setLastVisitedPath(currentPath); +// router.push(`${strapiUrl}/api/connect/patreon`); +// }; + +// const logout = () => { +// setAuthData({ accessToken: null, user: null }); +// }; + +// return ( +// +// {children} +// +// ); +// } + + + +// export function LogoutButton() { +// const context = useContext(AuthContext); +// if (!context) return <>; +// const { logout } = context; +// return ( +// +// ); +// } + +export function useAuth(): IUseAuth { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within an AuthProvider'); + } + return context; +} diff --git a/services/next/app/components/error-card.tsx b/services/next/app/components/error-card.tsx new file mode 100644 index 0000000..d16beec --- /dev/null +++ b/services/next/app/components/error-card.tsx @@ -0,0 +1,9 @@ +export function ErrorCard(props: { message: string }) { + const { message } = props + return ( +
+ + {message} +
+ ) +} \ No newline at end of file diff --git a/services/next/app/components/navbar.tsx b/services/next/app/components/navbar.tsx index 86d7a90..c2b6dfd 100644 --- a/services/next/app/components/navbar.tsx +++ b/services/next/app/components/navbar.tsx @@ -1,28 +1,23 @@ 'use client' -import { useEffect, useState } from 'react' +import { useState } from 'react' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faExternalLinkAlt } from "@fortawesome/free-solid-svg-icons"; -import { faUser, faUpload } from "@fortawesome/free-solid-svg-icons"; +import { faUpload } from "@fortawesome/free-solid-svg-icons"; import Link from 'next/link' -import { LoginButton } from '@/app/components/auth' - +import { LoginButton, ProfileButton } from './auth-buttons'; +import { Spinner } from './spinner'; +import ProtectedRoute from "../components/protected-route"; export default function Navbar() { const [isExpanded, setExpanded] = useState(false); - const [isProfileButton, setIsProfileButton] = useState(false); + + const handleBurgerClick = () => { setExpanded(!isExpanded); }; - // const { authData } = useAuth() - - // useEffect(() => { - // if (!!authData?.accessToken && !!authData?.user?.username) setIsProfileButton(true) - // else setIsProfileButton(false) - // }, [authData]) - return ( <> diff --git a/services/next/app/components/patron-perks.tsx b/services/next/app/components/patron-perks.tsx new file mode 100644 index 0000000..b6f796e --- /dev/null +++ b/services/next/app/components/patron-perks.tsx @@ -0,0 +1,51 @@ +import PatronRoute from "../components/protected-route"; +import { useMutateMetadata, useMetadata } from "../profile/hooks/useMetadata"; +import { faCheckCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import VibrateTest from "./vibrate-test"; +import { useState, useEffect } from "react"; +import { faSpinner, faXmarkCircle, faSave } from "@fortawesome/free-solid-svg-icons"; +import Skeleton, { SkeletonTheme } from "react-loading-skeleton" +import 'react-loading-skeleton/dist/skeleton.css'; + +export default function PatronPerks() { + + + + + const { status, data, error, isFetching, isPending } = useMetadata() + const mutateMetadata = useMutateMetadata() + + + + const username = `${data?.metadata?.first_name || ''} ${data?.metadata?.last_name || ''}`.trim(); + const { metadata } = data || {}; + + const [ isUsernamePublic, setIsUsernamePublic ] = useState(metadata?.isUsernamePublic || false) + useEffect(() => { + mutateMetadata.mutate({ isUsernamePublic }) + }, [ isUsernamePublic ]) + + + + return ( + <> +

Patron Perks

+

Website Shoutout

+ +
+ Display {(!!username) ? username : } publicly? + setIsUsernamePublic(evt.target.checked)}/> + {(mutateMetadata.status === 'success') && } + {(mutateMetadata.status === 'pending') && } + {(mutateMetadata.status === 'error') && } +
+
+ + +

Vibrate (test)

+ + + + ) +} \ No newline at end of file diff --git a/services/next/app/components/patrons-list.tsx b/services/next/app/components/patrons-list.tsx index cd4c127..0990914 100644 --- a/services/next/app/components/patrons-list.tsx +++ b/services/next/app/components/patrons-list.tsx @@ -3,17 +3,19 @@ import 'react-loading-skeleton/dist/skeleton.css'; import { getPatrons } from '../lib/patreon'; import Link from 'next/link' + interface PatronsListProps { displayStyle: string; } export default async function PatronsList({ displayStyle }: PatronsListProps) { const patrons = await getPatrons() - + console.log('patrons list as follows') + console.log(patrons) if (!patrons || patrons.length === 0) return ( - + ); @@ -21,24 +23,17 @@ export default async function PatronsList({ displayStyle }: PatronsListProps) { return (
{patrons.map((patron) => ( -
+ +
- {patron.username && ( + {patron.full_name && ( - {patron.username} + {patron.full_name} )} - {patron.vanityLink && ( - - {patron.vanityLink} - - - - - )}
@@ -48,7 +43,7 @@ export default async function PatronsList({ displayStyle }: PatronsListProps) {
); } else if (displayStyle === 'list') { - const patronNames = patrons.map((patron) => patron.username.trim()).join(', '); + const patronNames = patrons.map((patron) => patron.full_name.trim()).join(', '); return {patronNames}; } else { return ; // Handle unsupported display styles or provide a default display style diff --git a/services/next/app/components/permissions-table.tsx b/services/next/app/components/permissions-table.tsx new file mode 100644 index 0000000..f162268 --- /dev/null +++ b/services/next/app/components/permissions-table.tsx @@ -0,0 +1,8 @@ + +// @see https://gitea.futureporn.net/futureporn/pm/issues/106 + +export default function PermissionsTable() { + return <> +

@todo permissions table

+ +} \ No newline at end of file diff --git a/services/next/app/components/protected-route.tsx b/services/next/app/components/protected-route.tsx new file mode 100644 index 0000000..13eb863 --- /dev/null +++ b/services/next/app/components/protected-route.tsx @@ -0,0 +1,53 @@ +'use client' + +import { useSession } from "next-auth/react" +import { Spinner } from "./spinner"; + +// type AuthContentProps = { +// loadingSlot?: React.ReactNode; +// authedSlot: React.ReactNode; +// unauthSlot: React.ReactNode; +// }; + +// export default function AuthContent({ +// loadingSlot, +// authedSlot, +// unauthSlot, +// }: AuthContentProps) { +// const { data: session, status } = useSession(); + +// if (status === 'loading') { +// return <>{!!loadingSlot ? loadingSlot : } +// } + +// if (status === 'authenticated') { +// return <>{authedSlot}; +// } + +// return <>{unauthSlot}; +// } + + +import React from "react"; +import AccessDeniedScreen from './access-denied-screen'; + +interface ProtectedRouteProps extends React.PropsWithChildren { + requiredUserRole: string; + featureName?: string; + loading?: React.ReactNode; + accessDenied?: React.ReactNode; +} + +const ProtectedRoute = (props: ProtectedRouteProps) => { + const { data: session, status } = useSession(); + + if (status === 'loading') return (props.loading) ? props.loading : ; + if (status !== 'authenticated' || !session.roles || !session.roles.includes(props.requiredUserRole)) { + return (props?.accessDenied) ? props.accessDenied : AccessDeniedScreen(props.requiredUserRole, props.featureName); + } else { + return props.children; + } + +} + +export default ProtectedRoute \ No newline at end of file diff --git a/services/next/app/components/query-provider.tsx b/services/next/app/components/query-provider.tsx new file mode 100644 index 0000000..99ea319 --- /dev/null +++ b/services/next/app/components/query-provider.tsx @@ -0,0 +1,27 @@ +// @see https://github.com/TanStack/query/issues/4933#issuecomment-1416892904 + +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { useState } from 'react'; + + + +export default function QueryProvider({ + children, +}: { + children: React.ReactNode +}) { + const [queryClient] = useState(() => new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000 + } + } + })) + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/services/next/app/components/spinner.tsx b/services/next/app/components/spinner.tsx new file mode 100644 index 0000000..23a2ba3 --- /dev/null +++ b/services/next/app/components/spinner.tsx @@ -0,0 +1,6 @@ +import { faSpinner } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +export function Spinner() { + return +} \ No newline at end of file diff --git a/services/next/app/components/streams-table.tsx b/services/next/app/components/streams-table.tsx index 773a640..316f6b3 100644 --- a/services/next/app/components/streams-table.tsx +++ b/services/next/app/components/streams-table.tsx @@ -2,6 +2,7 @@ import React from 'react' import ReactDOM from 'react-dom/client' import Link from 'next/link' +import { defaultImageBlur } from '../lib/constants' import { keepPreviousData, QueryClient, @@ -40,7 +41,6 @@ function getStatusClass(value: string) { export default function StreamsTable() { const rerender = React.useReducer(() => ({}), {})[1] - // image & name // title // platform @@ -51,9 +51,9 @@ export default function StreamsTable() { { header: 'VTuber', accessorFn: d => ({ - displayName: d.attributes.vtuber.data?.attributes?.displayName, - image: d.attributes.vtuber.data?.attributes.image, - imageBlur: d.attributes.vtuber.data?.attributes.imageBlur + displayName: d.vtuber.display_name, + image: d.vtuber.image, + imageBlur: d.vtuber.image_blur }), cell: info => { const { displayName, image, imageBlur } = info.getValue<{ displayName: string, image: string, imageBlur: string }>(); @@ -68,7 +68,7 @@ export default function StreamsTable() { alt={displayName} placeholder="blur" objectFit='cover' - blurDataURL={imageBlur} + blurDataURL={imageBlur || defaultImageBlur} width={32} height={32} /> @@ -84,24 +84,23 @@ export default function StreamsTable() { }, { header: 'Date', - accessorFn: d => format(new Date(d.attributes.date2), 'yyyy-MM-dd HH:mm'), - // accessorFn: d => new Date(d.attributes.date2), + accessorFn: d => format(new Date(d.date), 'yyyy-MM-dd HH:mm'), sortingFn: 'datetime', sortDescFirst: true, - cell: info => ({info.getValue() as string}) + cell: info => ({info.getValue() as string}) }, { header: 'Platform', accessorFn: d => [ - (d.attributes.isChaturbateStream && 'CB'), - (d.attributes.isFanslyStream && 'Fansly') + (d.is_chaturbate_stream && 'CB'), + (d.is_fansly_stream && 'Fansly') ].filter(Boolean).join(', ') || '???' }, { header: 'Status', accessorFn: d => { - if (!d.attributes.archiveStatus) return 'missing'; - return d.attributes.archiveStatus + if (!d.archive_status) return 'missing'; + return d.archive_status } }, // { diff --git a/services/next/app/components/supertokensProvider.tsx b/services/next/app/components/supertokensProvider.tsx deleted file mode 100644 index d5b58aa..0000000 --- a/services/next/app/components/supertokensProvider.tsx +++ /dev/null @@ -1,18 +0,0 @@ -'use client'; -import React from 'react'; -import { SuperTokensWrapper } from 'supertokens-auth-react'; -import SuperTokensReact from 'supertokens-auth-react'; -import { frontendConfig, setRouter } from '../config/frontend'; -import { usePathname, useRouter } from 'next/navigation'; - -if (typeof window !== 'undefined') { - // we only want to call this init function on the frontend, so we check typeof window !== 'undefined' - SuperTokensReact.init(frontendConfig()); -} - -export const SuperTokensProvider: React.FC> = ({ - children, -}) => { - setRouter(useRouter(), usePathname() || window.location.pathname); - return {children}; -}; \ No newline at end of file diff --git a/services/next/app/components/tag.tsx b/services/next/app/components/tag.tsx index 5b05678..4131417 100644 --- a/services/next/app/components/tag.tsx +++ b/services/next/app/components/tag.tsx @@ -4,7 +4,6 @@ import { ITagVodRelation, ITagVodRelationsResponse } from "@/app/lib/tag-vod-rel import { isWithinInterval, subHours } from "date-fns"; import { faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { AuthContext, IUseAuth } from "./auth"; import { useContext, useEffect, useState } from "react"; import { useRouter } from 'next/navigation'; import { strapiUrl } from "@/app/lib/constants"; diff --git a/services/next/app/components/tagger.tsx b/services/next/app/components/tagger.tsx index e919a78..429a17e 100644 --- a/services/next/app/components/tagger.tsx +++ b/services/next/app/components/tagger.tsx @@ -7,7 +7,6 @@ import { faPlus, faX, faTags } from "@fortawesome/free-solid-svg-icons"; import { formatTimestamp } from '@/app/lib/dates'; import { readOrCreateTagVodRelation } from '@/app/lib/tag-vod-relations'; import { readOrCreateTag } from '@/app/lib/tags'; -import { useAuth } from './auth'; import { debounce } from 'lodash'; import { strapiUrl } from '@/app/lib/constants'; import { VideoContext } from './video-context'; diff --git a/services/next/app/components/timestamps-list.tsx b/services/next/app/components/timestamps-list.tsx index d281967..7f3af72 100644 --- a/services/next/app/components/timestamps-list.tsx +++ b/services/next/app/components/timestamps-list.tsx @@ -11,7 +11,6 @@ import { import Link from 'next/link'; import { faClock, faLink, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { AuthContext, IAuthData } from "./auth"; import { isWithinInterval, subHours, Interval } from 'date-fns'; import { useRouter } from 'next/navigation'; diff --git a/services/next/app/components/upload-form.tsx b/services/next/app/components/upload-form.tsx index a750773..d9b428b 100644 --- a/services/next/app/components/upload-form.tsx +++ b/services/next/app/components/upload-form.tsx @@ -6,7 +6,6 @@ import { useSearchParams } from 'next/navigation'; import React from 'react'; import AwsS3 from '@uppy/aws-s3'; import RemoteSources from '@uppy/remote-sources'; -import { LoginButton, useAuth } from '@/app/components/auth'; import { Dashboard } from '@uppy/react'; import styles from '@/assets/styles/fp.module.css' import { projektMelodyEpoch } from "@/app/lib/constants"; diff --git a/services/next/app/components/user-controls.tsx b/services/next/app/components/user-controls.tsx index fb32c4c..8c1df02 100644 --- a/services/next/app/components/user-controls.tsx +++ b/services/next/app/components/user-controls.tsx @@ -1,7 +1,6 @@ 'use client'; import React, { useState } from 'react'; -import { LogoutButton, useAuth } from "./auth" import { patreonQuantumSupporterId, strapiUrl } from '../lib/constants'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSave, faTimes, faCheck } from "@fortawesome/free-solid-svg-icons"; diff --git a/services/next/app/components/user-metadata.tsx b/services/next/app/components/user-metadata.tsx new file mode 100644 index 0000000..60ead00 --- /dev/null +++ b/services/next/app/components/user-metadata.tsx @@ -0,0 +1,37 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" +import { faCheckCircle, faSpinner, faXmarkCircle } from "@fortawesome/free-solid-svg-icons" +import { useMetadata, useMutateMetadata } from "../profile/hooks/useMetadata" +import { useState, useEffect } from 'react' + +export default function UserMetadata() { + + + const { status, data, error, isFetching, isPending } = useMetadata() + const mutateMetadata = useMutateMetadata() + + + const username = `${data?.metadata?.first_name || ''} ${data?.metadata?.last_name || ''}`.trim(); + const { metadata } = data || {}; + + const [ isUsernamePublic, setIsUsernamePublic ] = useState(metadata?.isUsernamePublic || false) + useEffect(() => { + mutateMetadata.mutate({ isUsernamePublic }) + }, [ isUsernamePublic ]) + + return ( + <> + + {isPending && } + +
+ Display {username} publicly? + setIsUsernamePublic(evt.target.checked)}/> + {(mutateMetadata.status === 'success') && } + {(mutateMetadata.status === 'pending') && } + {(mutateMetadata.status === 'error') && } +
+ + ) +} + + diff --git a/services/next/app/components/vibrate-test.tsx b/services/next/app/components/vibrate-test.tsx new file mode 100644 index 0000000..2eccfa8 --- /dev/null +++ b/services/next/app/components/vibrate-test.tsx @@ -0,0 +1,29 @@ +'use client'; + +import { useVibrate } from '@react-hookz/web'; +import { useState } from 'react'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faStop, faPlay } from '@fortawesome/free-solid-svg-icons'; + +export default function VibrateTest() { + const [doVibrate, setDoVibrate] = useState(false) + + useVibrate( + doVibrate, + [100, 30, 100, 30, 100, 30, 200, 30, 200, 30, 200, 30, 100, 30, 100, 30, 100], + true + ); + return ( + <> +

This experimental test is meant to activate your device's vibration.

+ + + ) +} \ No newline at end of file diff --git a/services/next/app/components/video-player.tsx b/services/next/app/components/video-player.tsx index 3d00326..4f62671 100644 --- a/services/next/app/components/video-player.tsx +++ b/services/next/app/components/video-player.tsx @@ -2,7 +2,6 @@ import { useEffect, useState, forwardRef, useContext, Ref } from 'react'; import { IVod } from '@/app/lib/vods'; -import { useAuth } from '@/app/components/auth'; import { getVodTitle } from './vod-page'; import { VideoSourceSelector } from '@/app/components/video-source-selector' import { buildIpfsUrl } from '@/app/lib/ipfs'; diff --git a/services/next/app/components/vod-card.tsx b/services/next/app/components/vod-card.tsx index 7a3c03f..1d8ba2b 100644 --- a/services/next/app/components/vod-card.tsx +++ b/services/next/app/components/vod-card.tsx @@ -16,28 +16,30 @@ interface IVodCardProps { export default function VodCard({id, title, date, muxAsset, thumbnail = 'https://futureporn-b2.b-cdn.net/default-thumbnail.webp', vtuber}: IVodCardProps) { - if (!vtuber?.attributes?.slug) return

VOD {id} is missing VTuber

+ if (!vtuber?.slug) return

VOD {id} is missing VTuber.slug~

+ return (
- +
-
+ {/* @todo restore thumbnail image */} + {/*
{title} -
+
*/}
-

{vtuber.attributes.displayName}

+

{vtuber.display_name}

{title}

diff --git a/services/next/app/components/vods-list.tsx b/services/next/app/components/vods-list.tsx index ad0910c..1b326a4 100644 --- a/services/next/app/components/vods-list.tsx +++ b/services/next/app/components/vods-list.tsx @@ -39,7 +39,7 @@ export default function VodsList({ vods, page = 1, pageSize = 24 }: IVodsListPro // @todo [x] sortability return ( <> - {/*

VodsList on page {page}, pageSize {pageSize}, with {vods.data.length} vods

*/} +

VodsList on page {page}, pageSize {pageSize}, with {vods.length} vods

{/*
                 
@@ -54,10 +54,10 @@ export default function VodsList({ vods, page = 1, pageSize = 24 }: IVodsListPro
                         key={vod.id}
                         id={vod.id}
                         title={getVodTitle(vod)}
-                        date={vod.attributes.date2}
-                        muxAsset={vod.attributes.muxAsset?.data?.attributes.playbackId}
-                        vtuber={vod.attributes.vtuber.data}
-                        thumbnail={vod.attributes.thumbnail?.data?.attributes?.cdnUrl}
+                        date={vod.date}
+                        muxAsset={vod.mux_asset?.playback_id}
+                        vtuber={vod.vtuber}
+                        thumbnail={vod.thumbnail?.cdn_url}
                     />
                 ))}
             
diff --git a/services/next/app/components/vtuber-card.tsx b/services/next/app/components/vtuber-card.tsx index 0413bd5..190cd0a 100644 --- a/services/next/app/components/vtuber-card.tsx +++ b/services/next/app/components/vtuber-card.tsx @@ -6,8 +6,8 @@ import NotFound from "@/app/vt/[slug]/not-found"; import ArchiveProgress from "./archive-progress"; export default async function VTuberCard(vtuber: IVtuber) { - const { id, attributes: { slug, displayName, imageBlur, image }} = vtuber; - if (!imageBlur) return

this is a vtubercard with an invalid imageBlur={imageBlur}

+ const { id, slug, display_name, image_blur, image } = vtuber; + if (!image_blur) return

This VTuberCard is missing image_blur

const vods = await getVodsForVtuber(id) if (!vods) return return ( @@ -23,17 +23,17 @@ export default async function VTuberCard(vtuber: IVtuber) { {displayName}
-

{displayName}

+

{display_name}

diff --git a/services/next/app/config/appInfo.ts b/services/next/app/config/appInfo.ts deleted file mode 100644 index b4e152c..0000000 --- a/services/next/app/config/appInfo.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const appInfo = { - // learn more about this on https://supertokens.com/docs/thirdpartyemailpassword/appinfo - appName: "futureporn", - apiDomain: "https://next.fp.sbtp.xyz", - websiteDomain: "https://next.fp.sbtp.xyz", - apiBasePath: "/api/auth", - websiteBasePath: "/auth" -} \ No newline at end of file diff --git a/services/next/app/config/backend.ts b/services/next/app/config/backend.ts deleted file mode 100644 index ae97019..0000000 --- a/services/next/app/config/backend.ts +++ /dev/null @@ -1,75 +0,0 @@ -import SuperTokens from "supertokens-node"; -import ThirdPartyNode from "supertokens-node/recipe/thirdparty" -import SessionNode from 'supertokens-node/recipe/session' -import Dashboard from "supertokens-node/recipe/dashboard"; -import UserRoles from "supertokens-node/recipe/userroles" -import { appInfo } from './appInfo' -import { TypeInput } from "supertokens-node/types"; -import { configs } from './configs' - -const apiKey = configs.supertokensApiKeys.split(',').at(0) - -export const backendConfig = (): TypeInput => { - return { - framework: "custom", - supertokens: { - apiKey, - connectionURI: configs.supertokensUrl - }, - appInfo, - recipeList: [ - Dashboard.init({ - admins: [ - "cj@futureporn.net", - ], - }), - UserRoles.init(), - ThirdPartyNode.init({ - // We have provided you with development keys which you can use for testing. - // IMPORTANT: Please replace them with your own OAuth keys for production use. - signInAndUpFeature: { - providers: [{ - config: { - requireEmail: false, - authorizationEndpoint: 'https://www.patreon.com/oauth2/authorize', - tokenEndpoint: 'https://www.patreon.com/api/oauth2/token', - userInfoEndpoint: 'https://www.patreon.com/api/oauth2/v2/identity', - name: 'Patreon', - thirdPartyId: 'patreon', - clients: [{ - scope: ['identity', 'identity.memberships'], - clientId: configs.patreonClientId, - clientSecret: configs.patreonClientSecret - }], - userInfoEndpointQueryParams: { - // 'include': 'memberships,memberships.currently_entitled_tiers,memberships.currently_entitled_tiers.benefits,memberships.campaign', - 'fields[user]': 'full_name,email', - // 'fields[member]': 'full_name,is_follower,patron_status,currently_entitled_amount_cents,campaign_lifetime_support_cents', - // 'fields[tier]': 'title', - // 'fields[benefit]': 'title', - }, - userInfoMap: { - fromUserInfoAPI: { - userId: "data.id", // Maps to the "id" field under "data" - email: "data.attributes.email", // Maps to the "email" field in "data.attributes" - } - } - } - }, - ], - } - }), - SessionNode.init(), - ], - isInServerlessEnv: true, - } -} - -let initialized = false; -// This function is used in your APIs to make sure SuperTokens is initialised -export function ensureSuperTokensInit() { - if (!initialized) { - SuperTokens.init(backendConfig()); - initialized = true; - } -} \ No newline at end of file diff --git a/services/next/app/config/clientConfigs.ts b/services/next/app/config/clientConfigs.ts new file mode 100644 index 0000000..d5723a2 --- /dev/null +++ b/services/next/app/config/clientConfigs.ts @@ -0,0 +1,16 @@ + +if (!process.env.NEXT_PUBLIC_API_DOMAIN) throw new Error('Missing NEXT_PUBLIC_API_DOMAIN env var'); +if (!process.env.NEXT_PUBLIC_WEBSITE_DOMAIN) throw new Error('Missing NEXT_PUBLIC_WEBSITE_DOMAIN env var'); + +// const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN! +// const websiteDomain = process.env.NEXT_PUBLIC_WEBSITE_DOMAIN! + +// export interface ClientConfig { +// apiDomain: string; +// websiteDomain: string; +// } + +// export const configs: ClientConfig = { +// apiDomain, +// websiteDomain, +// } diff --git a/services/next/app/config/configs.ts b/services/next/app/config/configs.ts index d2660ac..653d059 100644 --- a/services/next/app/config/configs.ts +++ b/services/next/app/config/configs.ts @@ -1,43 +1,38 @@ -// if (!process.env.PATREON_CLIENT_ID) throw new Error("PATREON_CLIENT_ID was missing from env"); -// if (!process.env.PATREON_CLIENT_SECRET) throw new Error('Missing PATREON_CLIENT_SECRET env var'); -if (!process.env.NEXT_PUBLIC_API_DOMAIN) throw new Error('Missing NEXT_PUBLIC_API_DOMAIN env var'); -if (!process.env.NEXT_PUBLIC_WEBSITE_DOMAIN) throw new Error('Missing NEXT_PUBLIC_WEBSITE_DOMAIN env var'); -if (!process.env.SUPERTOKENS_API_KEYS) throw new Error('SUPERTOKENS_API_KEYS was undefined in env'); -if (!process.env.SUPERTOKENS_URL) throw new Error('SUPERTOKENS_URL missing in env'); -if (!process.env.PATREON_CLIENT_ID) throw new Error('PATREON_CLIENT_ID missing in env'); -if (!process.env.PATREON_CLIENT_SECRET) throw new Error('PATREON_CLIENT_SECRET missing in env'); + +if (!process.env.PATREON_CLIENT_ID) throw new Error("PATREON_CLIENT_ID was missing from env"); +if (!process.env.PATREON_CLIENT_SECRET) throw new Error('Missing PATREON_CLIENT_SECRET env var'); +if (!process.env.KEYCLOAK_CLIENT_ID) throw new Error(`KEYCLOAK_CLIENT_ID missing in env!`); +if (!process.env.KEYCLOAK_CLIENT_SECRET) throw new Error(`KEYCLOAK_CLIENT_SECRET missing in env!`); +if (!process.env.KEYCLOAK_ISSUER) throw new Error(`KEYCLOAK_ISSUER missing in env!`); +if (!process.env.NEXTAUTH_SECRET) throw new Error(`NEXTAUTH_SECRET missing in env!`); +if (!process.env.NEXTAUTH_URL) throw new Error(`NEXTAUTH_URL missing in env!`); + const patreonClientId = process.env.PATREON_CLIENT_ID! const patreonClientSecret = process.env.PATREON_CLIENT_SECRET! -const apiDomain = process.env.NEXT_PUBLIC_API_DOMAIN! -const websiteDomain = process.env.NEXT_PUBLIC_WEBSITE_DOMAIN! -const logtoCookieSecret = process.env.LOGTO_COOKIE_SECRET! -const logtoAppId = process.env.LOGTO_APP_ID! -const logtoAppSecret = process.env.LOGTO_APP_SECRET! -const supertokensApiKeys = process.env.SUPERTOKENS_API_KEYS! -const supertokensUrl = process.env.SUPERTOKENS_URL! +const keycloakClientId = process.env.KEYCLOAK_CLIENT_ID! +const keycloakClientSecret = process.env.KEYCLOAK_CLIENT_SECRET! +const keycloakIssuer = process.env.KEYCLOAK_ISSUER! +const nextAuthSecret = process.env.NEXTAUTH_SECRET! +const nextAuthUrl = process.env.NEXTAUTH_URL! -export interface Config { +export interface ServerConfig { patreonClientId: string; patreonClientSecret: string; - apiDomain: string; - websiteDomain: string; - logtoCookieSecret: string; - logtoAppSecret: string; - logtoAppId: string; - supertokensApiKeys: string; - supertokensUrl: string; + keycloakClientId: string; + keycloakClientSecret: string; + keycloakIssuer: string; + nextAuthSecret: string; + nextAuthUrl: string; } -export const configs: Config = { +export const configs: ServerConfig = { patreonClientId, patreonClientSecret, - apiDomain, - websiteDomain, - logtoCookieSecret, - logtoAppSecret, - logtoAppId, - supertokensApiKeys, - supertokensUrl, -} + keycloakClientId, + keycloakClientSecret, + keycloakIssuer, + nextAuthSecret, + nextAuthUrl, +} \ No newline at end of file diff --git a/services/next/app/config/frontend.tsx b/services/next/app/config/frontend.tsx deleted file mode 100644 index bd199f0..0000000 --- a/services/next/app/config/frontend.tsx +++ /dev/null @@ -1,48 +0,0 @@ -// import PasswordlessReact from 'supertokens-auth-react/recipe/passwordless' -import ThirdPartyReact from 'supertokens-auth-react/recipe/thirdparty' -import SessionReact from 'supertokens-auth-react/recipe/session' -import { appInfo } from './appInfo' -import { SuperTokensConfig } from 'supertokens-auth-react/lib/build/types' -import { useRouter } from "next/navigation"; -import { faPatreon } from '@fortawesome/free-brands-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -const routerInfo: { router?: ReturnType; pathName?: string } = {}; - -export function setRouter( - router: ReturnType, - pathName: string, -) { - routerInfo.router = router; - routerInfo.pathName = pathName; -} - -export const frontendConfig = (): SuperTokensConfig => { - return { - appInfo, - recipeList: [ - ThirdPartyReact.init({ - signInAndUpFeature: { - providers: [ - ThirdPartyReact.Github.init(), - { - id: 'patreon', - name: 'Patreon', - logo: , - } - ], - }, - }), - SessionReact.init(), - ], - windowHandler: (original) => ({ - ...original, - location: { - ...original.location, - getPathName: () => routerInfo.pathName!, - assign: (url) => routerInfo.router!.push(url.toString()), - setHref: (url) => routerInfo.router!.push(url.toString()), - }, - }), - } -} \ No newline at end of file diff --git a/services/next/app/connect/patreon/redirect/page.tsx b/services/next/app/connect/patreon/redirect/page.tsx index 293f7c8..8c00f41 100644 --- a/services/next/app/connect/patreon/redirect/page.tsx +++ b/services/next/app/connect/patreon/redirect/page.tsx @@ -4,7 +4,6 @@ import { useSearchParams, useRouter } from 'next/navigation' import Link from 'next/link' import { useEffect, useState } from 'react' import { strapiUrl } from '@/app/lib/constants' -import { useAuth, IAuthData, IUser, IJWT } from '@/app/components/auth' import { DangerNotification } from '@/app/components/notifications' export type AccessToken = string | null; diff --git a/services/next/app/faq/page.tsx b/services/next/app/faq/page.tsx index 1fa9eea..fad05ac 100644 --- a/services/next/app/faq/page.tsx +++ b/services/next/app/faq/page.tsx @@ -25,15 +25,6 @@ export default async function Page() {

Lewdtubers are sexually explicit vtubers. ProjektMelody was the first Vtuber to livestream on Chaturbate on {projektMelodyEpoch.toDateString()}. Many more followed after her.

-
- -

Interplanetary File System (IPFS) is a new-ish technology which gives a unique address to every file. This address is called a Content ID, or CID for short. A CID can be used to request the file from the IPFS network.

-

IPFS is a distributed, decentralized protocol with no central point of failure. IPFS provider nodes can come and go, providing file serving capacity to the network. As long as there is at least one node pinning the content you want, you can download it.

-

There are a few ways to use IPFS, each with their own tradeoffs. Firstly, you can use a public gateway. IPFS public gateways can be overloaded and unreliable at times, but it's simple to use. All you have to do is visit a gateway URL containing the CID. One such example is https://ipfs.io/ipfs/bafkreigaknpexyvxt76zgkitavbwx6ejgfheup5oybpm77f3pxzrvwpfdi

-

The next way to use IPFS consists of running IPFS Desktop on your computer. A local IPFS node runs for as long as IPFS Desktop is active, and you can query this node for the content you want. This setup works best with IPFS Companion, or a web browser that natively supports IPFS, such as Brave browser.

-
- -
@@ -52,34 +43,6 @@ export default async function Page() {
-
-
-
- -
- -
-

Bandwidth is prohibitively expensive, so that's the free-to-play experience at the moment. (Patrons get access to CDN which is much faster.)

-

If the video isn't loading fast enough to stream, you can download the entire video then playback later on your device.

-
-
-
- -
-
-
- - -

Yes! The recommended way is to use either IPFS Desktop or ipget.

-

ipget example is as follows.

-
-                  
-                    ipget --progress -o projektmelody-chaturbate-2023-12-03.mp4 bafybeiejms45zzonfe7ndr3mp4vmrqrg3btgmuche3xkeq5b77uauuaxkm
-                  
-                
-
-
-
diff --git a/services/next/app/latest-vods/page.tsx b/services/next/app/latest-vods/page.tsx index ea33eaf..b7d0fe1 100644 --- a/services/next/app/latest-vods/page.tsx +++ b/services/next/app/latest-vods/page.tsx @@ -12,14 +12,14 @@ interface IPageParams { } export default async function Page({ params }: IPageParams) { - const vods: IVodsResponse = await getVods(1, 24); - + const pageSize = 24 + const vods = await getVods(1, pageSize); return ( <>

Latest VODs

page 1

- - + + ) } \ No newline at end of file diff --git a/services/next/app/layout.tsx b/services/next/app/layout.tsx index ea68f3f..3d448ca 100644 --- a/services/next/app/layout.tsx +++ b/services/next/app/layout.tsx @@ -2,22 +2,12 @@ import { ReactNode } from 'react' import Footer from "./components/footer" import Navbar from "./components/navbar" import "bulma" -import "../assets/styles/global.css"; -import "@fortawesome/fontawesome-svg-core/styles.css"; -import { AuthProvider } from './components/auth'; -import type { Metadata } from 'next'; -import NotificationCenter from './components/notification-center'; -import { SuperTokensProvider } from "./components/supertokensProvider"; -// import { -// QueryClientProvider, -// QueryClient -// } from '@tanstack/react-query' -// import NextTopLoader from 'nextjs-toploader'; -// import Ipfs from './components/ipfs'; // slows down the page too much - - - -// const queryClient = new QueryClient() +import "../assets/styles/global.css" +import "@fortawesome/fontawesome-svg-core/styles.css" +import type { Metadata } from 'next' +import NotificationCenter from './components/notification-center' +import QueryProvider from './components/query-provider' +import AuthProvider from './components/auth-provider' export const metadata: Metadata = { title: 'Futureporn.net', @@ -47,34 +37,22 @@ type Props = { export default function RootLayout({ children, }: Props) { + + return ( - - {/* */} - {/* */} - - {/* */} - {/* */} - - -
- {children} - {/*
*/} -
- {/*
*/} - {/*
*/} - - + + + + + +
+ {children} + {/*
*/} +
+
+
+ ) } diff --git a/services/next/app/lib/auth.ts b/services/next/app/lib/auth.ts new file mode 100644 index 0000000..fff8f94 --- /dev/null +++ b/services/next/app/lib/auth.ts @@ -0,0 +1,135 @@ +import KeycloakProvider, { type KeycloakProfile } from "next-auth/providers/keycloak"; +import { NextAuthOptions } from "next-auth"; +import { jwtDecode } from "jwt-decode"; +import { type JWT } from "next-auth/jwt"; +import { User, Profile } from "next-auth"; +import { configs } from "../config/configs"; +import { getPatreonMemberships, getKeycloakIdpToken, updateKeycloakUserPatreonEntitlements } from "./patreon"; + + +declare module "next-auth" { + interface Session { + user: User; + token: JWT | undefined; + profile: Profile; + } + + interface Profile { + realm_access: any; + preferred_username: string; + username_visibility?: "public" | "private"; + } + + interface Session { + roles: any + } + +} + +declare module "next-auth/jwt" { + interface JWT { + expires_at: number; + access_token: string; + refresh_token: string; + profile: Profile; + } +} + + + + + + +export const authOptions: NextAuthOptions = { + providers: [ + KeycloakProvider({ + clientId: configs.keycloakClientId, + clientSecret: configs.keycloakClientSecret, + issuer: configs.keycloakIssuer + }) + ], + callbacks: { + async jwt({ token, account, profile }) { + + // console.log(`jwt() callback. account as follows.`) + // console.log(account) + // console.log(`jwt() callback. token as follows.`) + // console.log(token) + console.log(`jwt() callback. profile as follows.`) + console.log(profile) + + if (account) { + const decodedAccountToken = jwtDecode(account.access_token as any) as KeycloakProfile + console.log(`jwt() callback. decodedAccountToken as follows.`) + console.log(decodedAccountToken) + token.client_roles = decodedAccountToken.realm_access.roles + token.scope = decodedAccountToken.scope + + token.expires_at = account.expires_at ?? 0; + token.access_token = account.access_token!; + token.refresh_token = account.refresh_token!; + } + + if (profile) { + token.profile = profile + } + + return token; + }, + async session({ session, token, trigger }) { + // console.log(`Token interceptor to add token info to the session to use on the pages. trigger=${trigger}`) + // console.log(JSON.stringify(token, null, 2)) + // console.log('session as follows') + // console.log(JSON.stringify(session, null, 2)) + + const userId = token?.sub + if (!userId) throw new Error('failed to get userId from token.sub'); + + + // side effect which gets user's patreon tiers and adds them to the appropriate keycloak group + await updateKeycloakUserPatreonEntitlements(token) + + // session.account = token.account + session.profile = token.profile + session.roles = token.client_roles + session.token = token + + + return session + } + } +} + + + +// async jwt({ token, account, profile }) { +// try { +// if (account) { +// const decodedToken = jwtDecode(account.access_token as any) +// if (token == null){ +// throw new Error("Unable to decode token") +// } +// console.log('decodedToken as follows') +// console.log(JSON.stringify(decodedToken, null, 2)) +// // Do something here to add more info, maybe just overwrite profile (thats the one that should have this info) +// profile = decodedToken +// token.account = account +// } +// if (profile) { +// console.log('profile as follows') +// console.log(profile) +// token.profile = profile +// // Then do here the assignation of roles elements to token so session has access +// // This can be modified so uses by client, realm or account BE AWARE OF THAT! +// // Modify the "resource_access['next-auth-AFB']" value to the one your resource/realm/accout +// // json scope roles you need + +// // While the info is already on profile, we could make a new key on the json response of session +// const clientRoles = profile.realm_access.roles +// token.client_roles = clientRoles +// } +// } catch (error) { +// console.log(error) +// } +// return token +// }, \ No newline at end of file diff --git a/services/next/app/lib/constants.ts b/services/next/app/lib/constants.ts index 2261167..d036fa5 100644 --- a/services/next/app/lib/constants.ts +++ b/services/next/app/lib/constants.ts @@ -1,16 +1,18 @@ -if (!process.env.NEXT_PUBLIC_SITE_URL) throw new Error('NEXT_PUBLIC_SITE_URL was missing in env'); -if (!process.env.NEXT_PUBLIC_STRAPI_URL) throw new Error('NEXT_PUBLIC_STRAPI_URL was missing in env'); -if (!process.env.NEXT_PUBLIC_UPPY_COMPANION_URL) throw new Error('NEXT_PUBLIC_UPPY_COMPANION_URL undefined in env'); +if (!process.env.NEXT_PUBLIC_SITE_URL) console.error('NEXT_PUBLIC_SITE_URL was missing in env'); +if (!process.env.NEXT_PUBLIC_STRAPI_URL) console.error('NEXT_PUBLIC_STRAPI_URL was missing in env'); +if (!process.env.NEXT_PUBLIC_UPPY_COMPANION_URL) console.error('NEXT_PUBLIC_UPPY_COMPANION_URL undefined in env'); export const companionUrl = ''+process.env.NEXT_PUBLIC_UPPY_COMPANION_URL export const siteUrl = ''+process.env.NEXT_PUBLIC_SITE_URL export const strapiUrl = ''+process.env.NEXT_PUBLIC_STRAPI_URL export const postgrestUrl = ''+process.env.NEXT_PUBLIC_POSTGREST_URL export const postgrestLocalUrl = 'http://postgrest.futureporn.svc.cluster.local:9000' +export const keycloakLocalUrl = 'http://keycloak.futureporn.svc.cluster.local:8080' export const patreonCampaignId: string = '8012692' export const patreonSupporterBenefitId: string = '4760169' export const patreonQuantumSupporterId: string = '10663202' export const patreonVideoAccessBenefitId: string = '13462019' +export const patreonApiIdentityUrl = 'https://www.patreon.com/api/oauth2/v2/identity' export const skeletonHeight = '32pt' export const skeletonBaseColor = '#000' export const skeletonHighlightColor = '#000' @@ -24,3 +26,4 @@ export const authorEmail = 'cj@futureporn.net' export const authorLink = 'https://futureporn.net' export const giteaUrl = 'https://gitea.futureporn.net' export const projektMelodyEpoch = new Date('2020-02-07T23:21:48.000Z') +export const defaultImageBlur = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABmJLR0QA/wD/AP+gvaeTAAAADUlEQVQImWOIUT7wHwAEXQI/2aBnMAAAAABJRU5ErkJggg==' \ No newline at end of file diff --git a/services/next/app/lib/dates.ts b/services/next/app/lib/dates.ts index 743ffcf..fa4ab5f 100644 --- a/services/next/app/lib/dates.ts +++ b/services/next/app/lib/dates.ts @@ -8,7 +8,7 @@ const localTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; export function getSafeDate(date: string | Date): string { let dateString: string; - + if (!date) throw new Error(`date passed to getSafeDate() was falsy`); if (typeof date === 'string') { const dateObject = toZonedTime(date, 'UTC'); dateString = format(dateObject, safeDateFormatString, { timeZone: 'UTC' }); diff --git a/services/next/app/lib/keycloak.ts b/services/next/app/lib/keycloak.ts new file mode 100644 index 0000000..bf7646d --- /dev/null +++ b/services/next/app/lib/keycloak.ts @@ -0,0 +1,195 @@ +import { type Tier, type TiersList, tiers } from './patreon' +import { keycloakLocalUrl } from './constants' +import { configs } from '../config/configs'; + +export interface UMAToken { + upgraded: boolean; + access_token: string; + expires_in: number; + refresh_expires_in: number; + token_type: string; + "not-before-policy": number; +} + +export interface KeycloakGroup { + id: string; + name: string; + path: string; + subGroups: KeycloakGroup[]; +} + +// Maps patreon tiers to keycloak groups. +export const patreonToKeycloakMap: Record = { + everyone: 'ccaece3a-bb62-4fc9-be1d-6326ecdec65c', + free: 'ccaece3a-bb62-4fc9-be1d-6326ecdec65c', + archiveSupporter: '9e70fe60-5015-44df-ab77-d93b90e86738', + stealthSupporter: 'fd5358fe-d5e8-43be-85f2-27ddff711bd0', + tuneItUp: '4127e407-0c09-4c0a-b231-0bc058fb3dbd', + maxQ: '25b3d3af-2015-49ea-949f-b052fc82c6a3', + archiveCollector: '9e70fe60-5015-44df-ab77-d93b90e86738', + advancedArchiveSupporter: '7f875c63-4c74-4ae1-b33c-eefa7904d031', + quantumSupporter: '13076175-3a6f-4d80-84f2-6a8d5711e5f5', + sneakyQuantumSupporter: '13076175-3a6f-4d80-84f2-6a8d5711e5f5', + luberPlusPlus: '205d9c95-68ec-4b20-a6c1-28162af8a8f5' +}; + + +export function getKeycloakIdFromTierId(tierId: string): string | undefined { + // Find the tier name corresponding to the provided tier ID + const tierName = Object.entries(tiers).find(([_, id]) => id === tierId)?.[0]; + + if (!tierName) { + console.warn(`Tier ID ${tierId} not found in tiers map.`); + return undefined; // Return undefined if the tier ID is not found + } + + // Use the tier name to get the corresponding Keycloak group ID + const keycloakId = patreonToKeycloakMap[tierName]; + + if (!keycloakId) { + console.warn(`Keycloak ID for tier ${tierName} not found in patreonToKeycloakMap.`); + } + + return keycloakId; +} + + + +/** + * This gets us an access_token with our service-account-futureporn user privs. + * This service account has manage-users role so it can modify their group memebership. + */ +export async function getUMAToken(): Promise { + const res = await fetch(`${keycloakLocalUrl}/realms/futureporn/protocol/openid-connect/token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: new URLSearchParams({ + grant_type: 'urn:ietf:params:oauth:grant-type:uma-ticket', + client_id: configs.keycloakClientId, + client_secret: configs.keycloakClientSecret, + audience: 'futureporn' + }).toString() + }) + const data = await res.json() + if (!res.ok) { + throw new Error(`failed to getUMAToken. body=${JSON.stringify(data, null, 2)}, res.status=${res.status}, res.statusText=${res.statusText}`); + } + return data +} + + + + +export async function getUserGroups(uma: UMAToken, userId: string): Promise { + const res = await fetch(`${keycloakLocalUrl}/admin/realms/futureporn/users/${userId}/groups`, { + headers: { + 'Authorization': `Bearer ${uma.access_token}` + } + }) + const data = await res.json() as KeycloakGroup[] + if (!res.ok) { + throw new Error(`failed to getUserGroups. body=${data}, res.status=${res.status}, res.statusText=${res.statusText}`); + } + return data.map((g) => g.id) +} + + + +export async function addUserGroup(uma: UMAToken, userId: string, groupId: string): Promise { + const res = await fetch(`${keycloakLocalUrl}/admin/realms/futureporn/users/${userId}/groups/${groupId}`, { + method: 'PUT', + headers: { + 'Authorization': `Bearer ${uma.access_token}` + } + }) + if (!res.ok) { + throw new Error(`failed to addUserGroup. res.status=${res.status}, res.statusText=${res.statusText}`); + } +} + + +export async function deleteUserGroup(uma: UMAToken, userId: string, groupId: string): Promise { + const res = await fetch(`${keycloakLocalUrl}/admin/realms/futureporn/users/${userId}/groups/${groupId}`, { + method: 'DELETE', + headers: { + 'Authorization': `Bearer ${uma.access_token}` + } + }) + if (!res.ok) { + throw new Error(`failed to deleteUserGroup. res.status=${res.status}, res.statusText=${res.statusText}`); + } +} + + + + + +function compareArrays(desired: T[], actual: T[]) { + const missing = desired.filter(item => !actual.includes(item)); + const extra = actual.filter(item => !desired.includes(item)); + + return { missing, extra }; +} + +export async function syncronizeKeycloakRoles(keycloakUserId: string, patreonTiersList: TiersList) { + + // 1. [-] get Keycloak service account uma token + // POST https://keycloak.fp.sbtp.xyz/realms/futureporn/protocol/openid-connect/token + // 2. [-] use the map to determine the groups the user needs to be assigned to + // 3. [-] get a list of groups the user is currently a member of + // GET https://keycloak.fp.sbtp.xyz/admin/realms/futureporn/users/fbec8417-e5e3-4282-a98d-ed2fbc4a7e82/groups + // GET https://keycloak.fp.sbtp.xyz/admin/realms/futureporn/users/fbec8417-e5e3-4282-a98d-ed2fbc4a7e82/role-mappings/realm + // 4. [ ] add any groups the user should be a member of + // PUT https://keycloak.fp.sbtp.xyz/admin/realms/futureporn/users/fbec8417-e5e3-4282-a98d-ed2fbc4a7e82/groups/ccaece3a-bb62-4fc9-be1d-6326ecdec65c + // 5. [ ] remove any groups the user should not be a member of + // DELETE https://keycloak.fp.sbtp.xyz/admin/realms/futureporn/users/fbec8417-e5e3-4282-a98d-ed2fbc4a7e82/groups/ccaece3a-bb62-4fc9-be1d-6326ecdec65c + // + + + console.log('uma token as follows') + const uma = await getUMAToken() + console.log(uma) + + + const desiredKeycloakGroupIds = patreonTiersList + .map((entitledTierId) => getKeycloakIdFromTierId(entitledTierId)) + .filter((groupId): groupId is string => groupId !== undefined); + + + + if (desiredKeycloakGroupIds.length === 0) { + throw new Error(`failed to get keycloak group id from the following patreon tiers: ${patreonTiersList.join(',')}`); + } + console.log('desiredKeycloakGroupIds as follows') + console.log(desiredKeycloakGroupIds) + + + const actualKeycloakGroupIds = await getUserGroups(uma, keycloakUserId); + console.log(`actualKeyclaokGroupIds as follows`) + console.log(actualKeycloakGroupIds) + + + const { missing, extra } = compareArrays(desiredKeycloakGroupIds, actualKeycloakGroupIds) + console.log(`missing=${missing.join(',')} extra=${extra.join(',')}`) + + + if (missing) { + console.log(`Adding the following keycloak groups ${missing.join(',')}`) + const addPromises = missing.map((groupId) => addUserGroup(uma, keycloakUserId, groupId)) + await Promise.all(addPromises) + } + + + if (extra) { + console.log(`Removing the following keycloak groups ${extra.join(',')}`) + const removePromises = extra.map((groupId) => deleteUserGroup(uma, keycloakUserId, groupId)) + await Promise.all(removePromises) + } + + console.log('finished updating keycloak groups.') + + +} + diff --git a/services/next/app/lib/patreon.ts b/services/next/app/lib/patreon.ts index e13ae34..8faf873 100644 --- a/services/next/app/lib/patreon.ts +++ b/services/next/app/lib/patreon.ts @@ -1,61 +1,236 @@ import { postgrestLocalUrl, patreonVideoAccessBenefitId, giteaUrl } from './constants' -import { IAuthData } from '@/app/components/auth'; import { type IPatron } from '@futureporn/types' +import { PublicPatron } from '@futureporn/utils/patron.ts'; +import { keycloakLocalUrl, patreonApiIdentityUrl } from "./constants"; +import { Session } from 'next-auth'; +import { JWT } from 'next-auth/jwt'; +import { type UMAToken, getKeycloakIdFromTierId, getUMAToken, syncronizeKeycloakRoles } from './keycloak'; + +export type TiersList = string[] +interface KeycloakToken { + account: { + access_token: string + } +} +interface KeycloakIdpToken { + access_token: string; + expires_in: number; + refresh_expires_in: number; + refresh_token: string; + token_type: string; + "not-before-policy": number; + scope: string; + version: string; +} +interface PatreonResponse { + data: UserData; + included: IncludedItem[]; + links: { + self: string; + }; +} + +interface UserData { + attributes: UserAttributes; + id: string; + relationships: { + memberships: { + data: Relationship[]; + }; + }; + type: "user"; +} + +interface UserAttributes { + about: string | null; + created: string; // ISO 8601 date string + first_name: string; + full_name: string; + image_url: string; + last_name: string; + thumb_url: string; + url: string; + vanity: string | null; +} + +interface Relationship { + id: string; + type: string; +} + +type IncludedItem = Member | Tier | Benefit; + +interface Member { + attributes: Record; + id: string; + relationships: { + currently_entitled_tiers: { + data: Relationship[]; + }; + }; + type: "member"; +} + +export interface Tier { + attributes: Record; + id: string; + relationships: { + benefits: { + data: Relationship[]; + }; + }; + type: "tier"; +} + +interface Benefit { + attributes: Record; + id: string; + type: "benefit"; +} + export interface ICampaign { - pledgeSum: number; - patronCount: number; + pledgeSum: number; + patronCount: number; } export interface IMarshalledCampaign { - data: { - attributes: { - pledge_sum: number, - patron_count: number - } + data: { + attributes: { + pledge_sum: number, + patron_count: number } + } +} + + +export const tiers = { + everyone: '-1', + free: '10620388', + archiveSupporter: '8154170', + stealthSupporter: '9561793', + tuneItUp: '9184994', + maxQ: '22529959', + archiveCollector: '8154171', + advancedArchiveSupporter: '8686045', + quantumSupporter: '8694826', + sneakyQuantumSupporter: '9560538', + luberPlusPlus: '8686022' +} + + +export async function updateKeycloakUserPatreonEntitlements(token: JWT) { + + console.log(`updateKeycloakUserPatreonEntitlements() invoked.`) + + const userId = token?.sub + if (!userId) { + throw new Error(`failed to get userId from token.sub`); + } + + const keycloakidpToken = await getKeycloakIdpToken(token.access_token) + + if (!keycloakidpToken) { + throw new Error(`failed to get keycloakIdpToken; it was falsy.`) + } + + const patreonTiersList = await getPatreonMemberships(keycloakidpToken) + + + await syncronizeKeycloakRoles(userId, patreonTiersList) + + +} + +export async function getKeycloakIdpToken(access_token: string): Promise { + console.log(`getKeycloakIdpToken() using access_token=${access_token}`) + + // @todo check the access_token. if it is expired, use the refresh_token to get a new access_token. + + const res = await fetch(`https://keycloak.fp.sbtp.xyz/realms/futureporn/broker/patreon/token`, { + headers: { + 'Authorization': `Bearer ${access_token}` + } + }) + + if (!res.ok) { + const bod = await res.text() + const msg = `req.status=${res.status} req.statusText=${res.statusText} body=${bod}` + console.log(msg) + throw new Error(`Failed to getKeycloakIdpToken. ${msg}`) + } + + const idpToken = await res.json() + return idpToken +} + +export function extractCurrentlyEntitledTiers(response: PatreonResponse): Relationship[] { + return response.included + .filter((item): item is Member => item.type === "member") + .flatMap(member => member.relationships.currently_entitled_tiers.data); +} + +export async function getPatreonMemberships(token: KeycloakIdpToken): Promise { + // console.log(`getPatreonMemberships with keycloakidpToken as follows`) + // console.log(token) + const query = 'fields%5Buser%5D=about,created,email,first_name,full_name,image_url,last_name,thumb_url,url,vanity&include=memberships,memberships.currently_entitled_tiers,memberships.currently_entitled_tiers.benefits'; + const res = await fetch(`${patreonApiIdentityUrl}?${query}`, { + headers: { + 'Authorization': `Bearer ${token.access_token}` + } + }) + const data = await res.json() as PatreonResponse + + // from the currently_entitled_tiers, we only want the id. + // we filter out any non-futureporn patreon tiers. + return extractCurrentlyEntitledTiers(data) + .map((rel) => rel.id) + .filter((t) => Object.values(tiers).includes(t)) } -export function isEntitledToPatronVideoAccess(authData: IAuthData): boolean { - if (!authData.user?.patreonBenefits) return false; - const patreonBenefits = authData.user.patreonBenefits - return (patreonBenefits.includes(patreonVideoAccessBenefitId)) +export function isEntitledToPatronVideoAccess(session: Session): boolean { + if (!session.user?.patreonBenefits) return false; + const patreonBenefits = session.user.patreonBenefits + return (patreonBenefits.includes(patreonVideoAccessBenefitId)) } -export async function getPatrons(): Promise { - let patrons = [] - try { - const res = await fetch(`${postgrestLocalUrl}/patrons`); - const data = await res.json(); - if (!data) throw new Error(`no patron data was available. ${JSON.stringify(data)}`); - patrons = data.map((patron: IPatron) => patron.username) - } catch (e) { - console.error('failed to get patrons~ list') - console.error(e) - return [] as IPatron[] - } - return patrons +export async function getPatrons(): Promise { + let patrons: PublicPatron[] = [] + try { + const url = `${postgrestLocalUrl}/patrons` + console.log(`GET requesting ${url}`) + const res = await fetch(url); + const data = await res.json(); + if (!res.ok) throw new Error(`failed to get /patrons. res.status=${res.status}, res.statusText=${res.statusText}`); + if (!data) throw new Error(`no patron data was available. ${JSON.stringify(data)}`); + patrons = data + } catch (e) { + console.error('failed to get patrons~ list') + console.error(e) + return [] as PublicPatron[] + } + return patrons } export async function getCampaign(): Promise { - const res = await fetch('https://www.patreon.com/api/campaigns/8012692', { - headers: { - accept: 'application/json' - }, - next: { - revalidate: 43200 // 12 hour cache - } - }) - const campaignData = await res.json(); - const data = { - patronCount: campaignData.data.attributes.patron_count, - pledgeSum: campaignData.data.attributes.campaign_pledge_sum + const res = await fetch('https://www.patreon.com/api/campaigns/8012692', { + headers: { + accept: 'application/json' + }, + next: { + revalidate: 43200 // 12 hour cache } - return data + }) + const campaignData = await res.json(); + const data = { + patronCount: campaignData.data.attributes.patron_count, + pledgeSum: campaignData.data.attributes.campaign_pledge_sum + } + return data } diff --git a/services/next/app/lib/streams.ts b/services/next/app/lib/streams.ts index ae09452..9ae60f6 100644 --- a/services/next/app/lib/streams.ts +++ b/services/next/app/lib/streams.ts @@ -1,9 +1,8 @@ -import { siteUrl, strapiUrl } from './constants'; +import { postgrestLocalUrl, postgrestUrl, siteUrl, strapiUrl } from './constants'; import { getSafeDate } from './dates'; import qs from 'qs'; import { IStream } from '@futureporn/types'; -import { IStreamsResponse } from '@futureporn/types'; @@ -43,7 +42,7 @@ export async function getStreamByCuid(cuid: string): Promise { } } }); - const res = await fetch(`${strapiUrl}/api/streams?${query}`); + const res = await fetch(`${postgrestUrl}/streams?${query}`); const json = await res.json(); return json.data[0]; } @@ -62,7 +61,7 @@ export function getPaginatedUrl(): (slug: string, pageNumber: number) => string export function getLocalizedDate(stream: IStream): string { - return new Date(stream.attributes.date).toLocaleDateString() + return new Date(stream.date).toLocaleDateString() } @@ -95,7 +94,7 @@ export async function getStreamsForYear(year: number): Promise { } }); - const res = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + const res = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions); if (!res.ok) { // Handle error if needed @@ -106,14 +105,14 @@ export async function getStreamsForYear(year: number): Promise { } const json = await res.json(); - const streams = json as IStreamsResponse; + const streams = json as IStream[]; - if (streams.data.length === 0) { + if (streams.length === 0) { // No more records, break the loop break; } - allStreams = [...allStreams, ...streams.data]; + allStreams = [...allStreams, ...streams]; currentPage += pageSize; } @@ -128,7 +127,7 @@ export async function getStream(id: number): Promise { } } }); - const res = await fetch(`${strapiUrl}/api/vods?${query}`, fetchStreamsOptions); + const res = await fetch(`${postgrestUrl}/vods?${query}`, fetchStreamsOptions); const json = await res.json(); return json.data; } @@ -180,7 +179,7 @@ export async function getAllStreams(archiveStatuses = ['missing', 'issue', 'good page: currentPage, }, }); - const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + const response = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions); const responseData = await response.json(); if (!responseData.data || responseData.data.length === 0) { @@ -212,7 +211,7 @@ export async function getStreamForVtuber(vtuberId: number, safeDate: string): Pr } }); - const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions); + const response = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions); if (response.status !== 200) throw new Error('network fetch error while attempting to getStreamForVtuber'); @@ -251,7 +250,7 @@ export async function getAllStreamsForVtuber(vtuberId: number, archiveStatuses = }); // console.log(`strapiUrl=${strapiUrl}`) - const response = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) + const response = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions) if (response.status !== 200) { // If the response status is not 200 (OK), consider it a network failure @@ -291,65 +290,64 @@ export async function getAllStreamsForVtuber(vtuberId: number, archiveStatuses = export async function fetchStreamData({ pageIndex, pageSize }: { pageIndex: number, pageSize: number }) { const offset = pageIndex * pageSize; - const query = qs.stringify({ - populate: { - vtuber: { - fields: ['slug', 'displayName', 'publishedAt', 'image', 'imageBlur'] - } - }, - filters: { - vtuber: { - publishedAt: { - $notNull: true - } - } - }, - pagination: { - start: offset, - limit: pageSize, - withCount: true - }, - sort: ['date:desc'] - }) + // const query = qs.stringify({ + // populate: { + // vtuber: { + // fields: ['slug', 'displayName', 'publishedAt', 'image', 'imageBlur'] + // } + // }, + // filters: { + // vtuber: { + // publishedAt: { + // $notNull: true + // } + // } + // }, + // pagination: { + // start: offset, + // limit: pageSize, + // withCount: true + // }, + // sort: ['date:desc'] + // }) + const query = 'select=*,vtuber:vtubers(*)' const response = await fetch( - `${strapiUrl}/api/streams?${query}` + `${postgrestUrl}/streams?${query}` ); - const json = await response.json(); - console.log(json) + const data = await response.json(); + console.log(data) + const filtered = data.filter((datum: any) => !!datum?.vtuber) const d = { - rows: json.data, - pageCount: Math.ceil(json.meta.pagination.total / pageSize), - rowCount: json.meta.pagination.total, + rows: filtered, + pageCount: Math.ceil(filtered.length / pageSize), + rowCount: filtered, } - // console.log(`fetchStreamData with pageIndex=${pageIndex}, pageSize=${pageSize}\n\n${JSON.stringify(d, null, 2)}`) + console.log(`fetchStreamData with pageIndex=${pageIndex}, pageSize=${pageSize}\n\n${JSON.stringify(d, null, 2)}`) return d; } export async function getStreamCountForVtuber(vtuberId: number, archiveStatuses = ['missing', 'issue', 'good']): Promise { if (!vtuberId) throw new Error(`getStreamCountForVtuber requires a vtuberId, but it was undefined.`); - // @todo possible performance improvement is to only request the meta field, since we don't use any of the data.attributes - const query = qs.stringify( - { - filters: { - vtuber: { - id: { - $eq: vtuberId - } - }, - archiveStatus: { - '$in': archiveStatuses - } + const res = await fetch( + `${postgrestLocalUrl}/streams?vtuber_id=eq.${vtuberId}&archive_status=in.(${archiveStatuses.join(',')})&limit=1`, + Object.assign(fetchStreamsOptions, { + headers: { + 'Prefer': 'count=exact' } - } + }) ) - const res = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) + const total = res.headers.get('Content-Range')?.split('/').at(-1) const data = await res.json() - // console.log(`getStreamCountForVtuber with archiveStatuses=${archiveStatuses}`) + // console.log(`getStreamCountForVtuber with vtuberId=${vtuberId}, archiveStatuses=${archiveStatuses}, Content-Range=${res.headers.get('Content-Range')}`) // console.log(JSON.stringify(data, null, 2)) - return data.meta.pagination.total + if (!total) { + console.error(`Failed to getStreamCountForVtuber-- total was falsy.`) + return 1; + } + else return parseInt(total)+1; } -export async function getStreamsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { +export async function getStreamsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { // console.log(`getStreamsForVtuber() with strapiUrl=${strapiUrl}`) const query = qs.stringify( { @@ -376,7 +374,7 @@ export async function getStreamsForVtuber(vtuberId: number, page: number = 1, pa } } ) - const res = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) + const res = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions) const data = await res.json() // console.log(data) return data @@ -398,7 +396,7 @@ export async function getStreamsForVtuber(vtuberId: number, page: number = 1, pa // } // } // }) -// const data = await fetch(`${strapiUrl}/api/streams?${query}`, fetchStreamsOptions) +// const data = await fetch(`${postgrestUrl}/streams?${query}`, fetchStreamsOptions) // .then((res) => res.json()) // .then((g) => { // return g diff --git a/services/next/app/lib/tag-vod-relations.ts b/services/next/app/lib/tag-vod-relations.ts index e3250bd..e1db946 100644 --- a/services/next/app/lib/tag-vod-relations.ts +++ b/services/next/app/lib/tag-vod-relations.ts @@ -10,7 +10,6 @@ import qs from 'qs'; import { strapiUrl } from './constants' import { ITagResponse, IToyTagResponse } from './tags'; import { IVod, IVodResponse } from './vods'; -import { IAuthData } from '@/app/components/auth'; import { IMeta } from '@futureporn/types'; export interface ITagVodRelation { @@ -86,7 +85,7 @@ export async function createTagVodRelation(accessToken: string, tagId: number, v } }) const json = await res.json(); - console.log(json) + // console.log(json) return json.data; } diff --git a/services/next/app/lib/tags.ts b/services/next/app/lib/tags.ts index ad880b3..192281b 100644 --- a/services/next/app/lib/tags.ts +++ b/services/next/app/lib/tags.ts @@ -3,7 +3,6 @@ import { fetchPaginatedData } from './fetchers'; import { IVod } from './vods'; import slugify from 'slugify'; import { IToy } from './toys'; -import { IAuthData } from '@/app/components/auth'; import qs from 'qs'; import { IMeta } from '@futureporn/types'; @@ -57,7 +56,7 @@ export async function createTag(accessToken: string, tagName: string): Promise { - let vod: IVod|null; +export async function getVodFromSafeDateOrCuid(safeDateOrCuid: string): Promise { + let vod: IVod | null; let date: Date; if (!safeDateOrCuid) { console.log(`safeDateOrCuid was missing`); @@ -101,18 +99,18 @@ export function getPaginatedUrl(): (slug: string, pageNumber: number) => string /** @deprecated old format for futureporn.net/api/v1.json, which is deprecated. Please use getUrl() instead */ export function getDeprecatedUrl(vod: IVod): string { - return `${siteUrl}/vods/${getSafeDate(vod.attributes.date2)}` + return `${siteUrl}/vods/${getSafeDate(vod.date2)}` } export async function getNextVod(vod: IVod): Promise { const query = qs.stringify({ filters: { date2: { - $gt: vod.attributes.date2 + $gt: vod.date2 }, vtuber: { slug: { - $eq: vod.attributes.vtuber.data.attributes.slug + $eq: vod.vtuber.slug } }, publishedAt: { @@ -139,7 +137,7 @@ export async function getNextVod(vod: IVod): Promise { } export function getLocalizedDate(vod: IVod): string { - return new Date(vod.attributes.date2).toLocaleDateString() + return new Date(vod.date2).toLocaleDateString() } export async function getPreviousVod(vod: IVod): Promise { @@ -148,11 +146,11 @@ export async function getPreviousVod(vod: IVod): Promise { { filters: { date2: { - $lt: vod.attributes.date2 + $lt: vod.date2 }, vtuber: { slug: { - $eq: vod.attributes.vtuber.data.attributes.slug + $eq: vod.vtuber.slug } } }, @@ -205,21 +203,21 @@ export async function getVodByCuid(cuid: string): Promise { } }) - try { - const res = await fetch(`${postgrestLocalUrl}/vods?${query}`, { cache: 'no-store', next: { tags: ['vods'] } }) - if (!res.ok) { - throw new Error('failed to fetch vodForDate') - } - const json = await res.json() - const vod = json.data[0] - if (!vod) return null; - return vod; - } catch (e) { - if (e instanceof Error) { - console.error(e) - } - return null; + try { + const res = await fetch(`${postgrestLocalUrl}/vods?${query}`, { cache: 'no-store', next: { tags: ['vods'] } }) + if (!res.ok) { + throw new Error('failed to fetch vodForDate') } + const json = await res.json() + const vod = json.data[0] + if (!vod) return null; + return vod; + } catch (e) { + if (e instanceof Error) { + console.error(e) + } + return null; + } } export async function getVodForDate(date: Date): Promise { @@ -272,7 +270,7 @@ export async function getVodForDate(date: Date): Promise { } } -export async function getVod(id: number): Promise { +export async function getVod(id: number): Promise { const query = qs.stringify( { filters: { @@ -288,43 +286,61 @@ export async function getVod(id: number): Promise { return data; } -export async function getVods(page: number = 1, pageSize: number = 25, sortDesc = true): Promise { - const query = qs.stringify( - { - // populate: { - // vtuber: { - // fields: ['slug', 'displayName', 'image', 'imageBlur'] - // }, - // muxAsset: { - // fields: ['playbackId', 'assetId'] - // }, - // thumbnail: { - // fields: ['cdnUrl', 'url'] - // }, - // tagVodRelations: { - // fields: ['tag'], - // populate: ['tag'] - // }, - // videoSrcB2: { - // fields: ['url', 'key', 'uploadId', 'cdnUrl'] - // } - // }, - // sort: { - // date: (sortDesc) ? 'desc' : 'asc' - // }, - // pagination: { - // pageSize: pageSize, - // page: page - // } - } - ) +export async function getVods(page: number = 1, pageSize: number = 25, sortDesc = true): Promise { + // const query = qs.stringify( + // { + // populate: { + // vtuber: { + // fields: ['slug', 'displayName', 'image', 'imageBlur'] + // }, + // muxAsset: { + // fields: ['playbackId', 'assetId'] + // }, + // thumbnail: { + // fields: ['cdnUrl', 'url'] + // }, + // tagVodRelations: { + // fields: ['tag'], + // populate: ['tag'] + // }, + // videoSrcB2: { + // fields: ['url', 'key', 'uploadId', 'cdnUrl'] + // } + // }, + // sort: { + // date: (sortDesc) ? 'desc' : 'asc' + // }, + // pagination: { + // pageSize: pageSize, + // page: page + // } + // } + // ) - console.log(`postgrestLocalUrl=${postgrestLocalUrl} query=${query}`) - const res = await fetch(`${postgrestLocalUrl}/vods?${query}`, fetchVodsOptions); + // console.log(`postgrestLocalUrl=${postgrestLocalUrl} query=${query}`) + // const url = `${configs.postgrestUrl}/vods?select=*,segments(*),recording:recordings(is_aborted)&id=eq.${vodId}` + const selects = [ + '*', + 'vtuber:vtubers(id,slug,image,display_name,image_blur)', + // 'mux_asset:mux_assets(playback_id,asset_id)', + // 'thumbnail:b2_files(cdn_url)' + ] + const queryObject = { + select: selects.join(','), + }; + const query = qs.stringify(queryObject, { encode: false }); + + const res = await fetch( + `${postgrestLocalUrl}/vods?${query}`, + fetchVodsOptions + ); if (!res.ok) { - throw new Error(`Failed to fetch vods status=${res.status}, statusText=${res.statusText}`); + const body = await res.text() + throw new Error(`Failed to fetch vods status=${res.status}, statusText=${res.statusText}, body=${body}`); } const json = await res.json() + // console.log('vods as follows') + // console.log(json) return json; } @@ -397,51 +413,19 @@ export async function getAllVods(): Promise { -export async function getVodsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { - const query = qs.stringify( - { - populate: { - thumbnail: { - fields: ['cdnUrl', 'url'] - }, - vtuber: { - fields: [ - 'id', - 'slug', - 'displayName', - 'image', - 'imageBlur' - ] - }, - videoSrcB2: { - fields: ['url', 'key', 'uploadId', 'cdnUrl'] - } - }, - filters: { - vtuber: { - id: { - $eq: vtuberId - } - } - }, - pagination: { - page: page, - pageSize: pageSize - }, - sort: { - date: (sortDesc) ? 'desc' : 'asc' - } - } - ) - const res = await fetch(`${postgrestLocalUrl}/vods?${query}`, fetchVodsOptions) - if (!res.ok) return null; - const data = await res.json() as IVodsResponse; +export async function getVodsForVtuber(vtuberId: number, page: number = 1, pageSize: number = 25, sortDesc = true): Promise { + const res = await fetch(`${postgrestLocalUrl}/vods?select=*,vtuber:vtubers(id,slug,image,display_name,image_blur)&vtuber.id=eq.${vtuberId}`, fetchVodsOptions) + if (!res.ok) { + const body = await res.text() + console.error(`getVodsForVtuber() failed. ok=${res.ok} status=${res.status} statusText=${res.statusText} body=${body}`); + return null; + } + const data = await res.json() as IVod[]; return data; - } -export async function getVodsForTag(tag: string): Promise { +export async function getVodsForTag(tag: string): Promise { const query = qs.stringify( { populate: { diff --git a/services/next/app/lib/vtubers.ts b/services/next/app/lib/vtubers.ts index e504b67..1028e87 100644 --- a/services/next/app/lib/vtubers.ts +++ b/services/next/app/lib/vtubers.ts @@ -1,9 +1,9 @@ import { IVod } from './vods' -import { strapiUrl, siteUrl } from './constants'; +import { strapiUrl, siteUrl, postgrestLocalUrl } from './constants'; import qs from 'qs'; -import { IMeta, IVtuber, IVtubersResponse } from '@futureporn/types'; +import { IMeta, IVtuber } from '@futureporn/types'; const fetchVtubersOptions = { @@ -23,27 +23,17 @@ export function getUrl(slug: string): string { export async function getVtuberBySlug(slug: string): Promise { - const query = qs.stringify( - { - filters: { - slug: { - $eq: slug - } - } - } - ) - - const res = await fetch(`${strapiUrl}/api/vtubers?${query}`); + const res = await fetch(`${postgrestLocalUrl}/vtubers?slug=eq.${slug}`); if (!res.ok) { console.error(`error inside getVtuberBySlug-- ${res.statusText}`); return null; } - const vtuber = await res.json(); - return vtuber.data[0]; + const vtubers = await res.json(); + return vtubers[0]; } export async function getVtuberById(id: number): Promise { - const res = await fetch(`${strapiUrl}/api/vtubers?filters[id][$eq]=${id}`); + const res = await fetch(`${postgrestLocalUrl}/vtubers?id=eq.${id}`); if (!res.ok) { console.error(`error inside getVtuberById-- ${res.statusText}`); return null; @@ -52,8 +42,8 @@ export async function getVtuberById(id: number): Promise { return vtuber } -export async function getVtubers(): Promise { - const res = await fetch(`${strapiUrl}/api/vtubers`); +export async function getVtubers(): Promise { + const res = await fetch(`${postgrestLocalUrl}/vtubers`); if (!res.ok) { console.error(`error inside getVtubers-- ${res.statusText}`); return null; diff --git a/services/next/app/logto.ts b/services/next/app/logto.ts deleted file mode 100644 index bc999e6..0000000 --- a/services/next/app/logto.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { LogtoNextConfig } from "@logto/next"; -import { configs } from "./config/configs"; - -export const logtoConfig: LogtoNextConfig = { - endpoint: 'https://logto.fp.sbtp.xyz/', - appId: configs.logtoAppId, - appSecret: configs.logtoAppSecret, - baseUrl: configs.websiteDomain, - cookieSecret: configs.logtoCookieSecret, - cookieSecure: process.env.NODE_ENV === 'production', -}; \ No newline at end of file diff --git a/services/next/app/page.tsx b/services/next/app/page.tsx index 586cecb..970d1f1 100644 --- a/services/next/app/page.tsx +++ b/services/next/app/page.tsx @@ -9,34 +9,15 @@ import { getVtubers } from "./lib/vtubers"; import VTuberCard from "./components/vtuber-card"; import Link from 'next/link'; import { notFound } from "next/navigation"; -// import { getLogtoContext, signIn, signOut } from '@logto/next/server-actions'; -// import SignIn from './sign-in'; -// import SignOut from './sign-out'; -// import { logtoConfig } from './logto'; +import { ErrorCard } from "./components/error-card"; + export default async function Page() { - // const { isAuthenticated, claims } = await getLogtoContext(logtoConfig); - const vods = await getVods(1, 9, true); - // console.log('vods as follows') - // console.log(JSON.stringify(vods, null, 2)) - const vtubers = await getVtubers(); - // if (!vtubers) notFound(); - // console.log(`vtubers as follows`) - // console.log(JSON.stringify(vtubers, null, 2)) - - - // return ( - //
-  //     
-  //       {JSON.stringify(vods, null, 2)}
-  //     
-  //   
- // ) return ( <>
@@ -51,7 +32,7 @@ export default async function Page() { - Login +
@@ -59,9 +40,9 @@ export default async function Page() {
- {!vods?.data &&

Error: Failed to fetch VODs from the database

} - + {!vods &&

Error: Failed to fetch VODs from the database

} + {vods && vods.map((vod: IVod) => ( VTubers
diff --git a/services/next/app/profile/hooks/updateIsUsernamePublic.ts b/services/next/app/profile/hooks/updateIsUsernamePublic.ts new file mode 100644 index 0000000..1053ad7 --- /dev/null +++ b/services/next/app/profile/hooks/updateIsUsernamePublic.ts @@ -0,0 +1,14 @@ +export type UpdateMetadataInput = { isUsernamePublic: boolean } + + +export async function updateIsUsernamePublic (data: UpdateMetadataInput): Promise { + const res = await fetch('/api/user/metadata', { + method: 'POST', + body: JSON.stringify({ + isUsernamePublic: data.isUsernamePublic + }) + }) + const d = await res.json() + if (!res.ok) throw new Error(`failed to updateIsUsernamePublic. ${res.status} ${res.statusText}`); + return d +} \ No newline at end of file diff --git a/services/next/app/profile/hooks/useMetadata.ts b/services/next/app/profile/hooks/useMetadata.ts new file mode 100644 index 0000000..09049a2 --- /dev/null +++ b/services/next/app/profile/hooks/useMetadata.ts @@ -0,0 +1,52 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' + +export type MetadataPayload = { + metadata: { + first_name: string + last_name: string + patreon: { + entitledTiers: string[], + lifetimeSupportCents: number, + currentlyEntitledSupportCents: number + }, + isUsernamePublic: boolean + } +} +export type UpdateMetadataInput = { isUsernamePublic: boolean } + + +const fetchMetadata = async (): Promise => { + const response = await fetch(`/api/user/metadata`) + const data = await response.json() + return data +} + +const fetchMutateMetadata = async (data: UpdateMetadataInput): Promise => { + const res = await fetch('/api/user/metadata', { + method: 'POST', + body: JSON.stringify({ + isUsernamePublic: data.isUsernamePublic + }) + }) + if (!res.ok) throw new Error(`failed to fetchMutateMetadata. ${res.status} ${res.statusText}`); +} + +const useMetadata = () => { + return useQuery({ + queryKey: ['user/metadata'], + queryFn: () => fetchMetadata() + }) +} + +const useMutateMetadata = () => { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (data: UpdateMetadataInput) => fetchMutateMetadata(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['user/metadata'] }) + } + }) +} + +export { useMetadata, fetchMetadata, useMutateMetadata } diff --git a/services/next/app/profile/page.tsx b/services/next/app/profile/page.tsx index 8c5dcdb..291afc9 100644 --- a/services/next/app/profile/page.tsx +++ b/services/next/app/profile/page.tsx @@ -1,62 +1,183 @@ -'use client' +'use client'; -import { useAuth, LoginButton, LogoutButton } from "../components/auth" -import { patreonVideoAccessBenefitId } from "../lib/constants"; -import UserControls from "../components/user-controls"; -import Skeleton, { SkeletonTheme } from "react-loading-skeleton" -import { skeletonHeight, skeletonBorderRadius, skeletonBaseColor, skeletonHighlightColor } from '../lib/constants' +import 'react-loading-skeleton/dist/skeleton.css'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faSpinner, faCheckCircle, faXmarkCircle } from "@fortawesome/free-solid-svg-icons"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { updateIsUsernamePublic } from "./hooks/updateIsUsernamePublic"; +import ProtectedRoute from "../components/protected-route"; +import VibrateTest from "../components/vibrate-test"; +import { useSession } from "next-auth/react" +import { LoginButton, LogoutButton } from "../components/auth-buttons"; +import Skeleton, { SkeletonTheme } from 'react-loading-skeleton'; export default function Page() { - const { authData } = useAuth() - const isLoggedIn = (!!authData?.accessToken) - const isEntitledToCDN = (!!authData?.user?.patreonBenefits.split(',').includes(patreonVideoAccessBenefitId)) - if (!authData) { - return
- - - -
- } + const queryClient = useQueryClient() + // const sessionContext = Session.useSessionContext() + // if (sessionContext.loading) { + // return + // } + const session = useSession() + + const metadataMutation = useMutation({ + mutationFn: updateIsUsernamePublic, + mutationKey: ['user/metadata'], + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['user/metadata'] }) + }) + + // const metadataQuery = useQuery({ + // queryKey: ['user/metadata'], + // queryFn: () => fetchMetadata() + // }) + + // const [ isUsernamePublic, setIsUsernamePublic ] = useState() + // const [ username, setUsername ] = useState('') + // useEffect(() => { + // if (metadataQuery.data?.metadata) { + // setUsername(formatPatronUsername(metadataQuery.data.metadata.first_name, metadataQuery.data.metadata.last_name)) + // setIsUsernamePublic(metadataQuery.data.metadata.isUsernamePublic) + // } + // }, [metadataQuery.data]) + + const isUsernamePublic = (session.data?.profile?.username_visibility === 'public') + + - return ( - <> -
- -

{authData?.user?.username} Profile

- - {/* if not logged in, show login button */} - { - (!authData?.user) && ( - - ) - } - - {/* if logged in and not patron, display welcome */} - { - (!!authData?.accessToken && !isEntitledToCDN) && - <> -

Welcome to Futureporn, {authData?.user?.username || 'chatmember'}! It seems that you are not a patron yet. Please log out and log in again if you believe this is an error. Thank you for your interest!

- - - } - - {/* if logged in and patron, display profile*/} - { - (!!authData?.user?.patreonBenefits && isEntitledToCDN) && - - } +
+ {/* {(metadataQuery.isPending) && } */} +
+

Debug section

+ {/* {(metadataQuery.isPending) ? isPending=true : isPending=false} */} + {/*

metadataQuery as follows

*/} + {/*
{JSON.stringify(metadataQuery.data, null, 2)}
*/} +

session as follows

+
{JSON.stringify(session, null, 2)}
+ {(session.status === 'unauthenticated') && } +
+ {(session.data?.user) && +
+

Profile

+ + + +
+
+
+ {/*

{username}

*/} +

{session.data?.user?.name}

+
+
+
+ +

Patron Perks

+
+

Test Perk (not real)

+ +

Hey look at you, you're granted access to this perk!

+
+
+ +
+

Website Shoutout

+ + Display {(!!session.data.user.name) ? session.data.user.name : } publicly? + metadataMutation.mutate({ isUsernamePublic: evt.target.checked })}/> + {(metadataMutation.status === 'success') && } + {(metadataMutation.status === 'pending') && } + {(metadataMutation.status === 'error') && } + +
+ +
+

Premium Content Delivery Network (CDN1)

+ +

You have access to Futureporn's Premium CDN for reliable video playback

+
+
+ +
+

Economy Content Delivery Network (CDN2)

+ +

You have access to Futureporn's Economy CDN for basic video playback

+
+
+ + +
+

Vibrate (test)

+ +
- + } +
) + // if (metadataQuery.isPending) return ; + // return
{JSON.stringify(metadataQuery.data, null, 2)}
+ // return

@todo

+ // if (!isPending) return ; + // if (!data) return

there is no data

; + // if (!data?.metadata) return

there is no data.metadata

; + // const { first_name, last_name } = data.metadata + // const username = formatPatronUsername(first_name, last_name) + // const { metadata } = data || {} + // const { isUsernamePublic } = metadata + + + // return ( + // <> + + //
+ //
+ //

Profile

+ + //
+ //
Username
+ //

+ // {isPending && + // + // + // + // } + // {!isPending &&

{username}

} + //

+ //
+ + //
+ //
User ID
+ //
+ //

{sessionContext.userId}

+ //
+ //
+ + //

Patron Perks

+ //

Website Shoutout

+ // + //
+ // Display {(!!username) ? username : } publicly? + // metadataMutation.mutate({ isUsernamePublic: evt.target.checked })}/> + // {(metadataMutation.status === 'success') && } + // {(metadataMutation.status === 'pending') && } + // {(metadataMutation.status === 'error') && } + //
+ //
+ + //

Content Delivery Network (CDN)

+ // + //

You have access to Futureporn's CDN for reliable video playback

+ //
+ + + //

Vibrate (test)

+ // + //
+ //
+ + // + // ) } \ No newline at end of file diff --git a/services/next/app/tags/[slug]/page.tsx b/services/next/app/tags/[slug]/page.tsx index c674906..5d30b5e 100644 --- a/services/next/app/tags/[slug]/page.tsx +++ b/services/next/app/tags/[slug]/page.tsx @@ -19,10 +19,10 @@ export default async function Page({ params }: { params: { slug: string }}) { key={vod.id} id={vod.id} title={getVodTitle(vod)} - date={vod.attributes.date2} - muxAsset={vod.attributes.muxAsset?.data?.attributes?.assetId} - thumbnail={vod.attributes.thumbnail?.data?.attributes?.cdnUrl} - vtuber={vod.attributes.vtuber.data} + date={vod.date2} + muxAsset={vod.muxAsset?.assetId} + thumbnail={vod.thumbnail?.cdnUrl} + vtuber={vod.vtuber.data} /> ))}
diff --git a/services/next/app/vt/[slug]/page.tsx b/services/next/app/vt/[slug]/page.tsx index 5c7941d..5b99a03 100644 --- a/services/next/app/vt/[slug]/page.tsx +++ b/services/next/app/vt/[slug]/page.tsx @@ -29,23 +29,6 @@ export default async function Page({ params }: { params: { slug: string } }) { const vods = await getVodsForVtuber(vtuber.id, 1, 9); if (!vods) notFound(); - // @todo -// const missingStreams = await getAllStreamsForVtuber(vtuber.id, ['missing']); -// const issueStreams = await getAllStreamsForVtuber(vtuber.id, ['issue']); -// const goodStreams = await getAllStreamsForVtuber(vtuber.id, ['good']); - - - -// return ( -// <> -//

hi mom!

-//
-//       
-//         {JSON.stringify(missingStreams, null, 2)}
-//       
-//     
-// -// ) return ( <> @@ -55,24 +38,24 @@ export default async function Page({ params }: { params: { slug: string } }) {
-

{vtuber.attributes.displayName}

+

{vtuber.display_name}

{vtuber.attributes.displayName}
-

{vtuber.attributes.description1}

-

{vtuber.attributes.description2}

+

{vtuber.description_1}

+

{vtuber.description_2}

@@ -80,126 +63,125 @@ export default async function Page({ params }: { params: { slug: string } }) { Socials -
- {vtuber.attributes.patreon && ( + {vtuber.patreon && (
- + Patreon
)} - {vtuber.attributes.twitter && ( + {vtuber.twitter && (
- + Twitter
)} - {vtuber.attributes.youtube && ( + {vtuber.youtube && (
- + YouTube
)} - {vtuber.attributes.twitch && ( + {vtuber.twitch && (
- + Twitch
)} - {vtuber.attributes.tiktok && ( + {vtuber.tiktok && (
- + TikTok
)} - {vtuber.attributes.fansly && ( + {vtuber.fansly && (
- + Fansly
)} - {vtuber.attributes.onlyfans && ( + {vtuber.onlyfans && (
- + OnlyFans
)} - {vtuber.attributes.pornhub && ( + {vtuber.pornhub && (
- + Pornhub
)} - {vtuber.attributes.reddit && ( + {vtuber.reddit && (
- + Reddit
)} - {vtuber.attributes.discord && ( + {vtuber.discord && (
- + Discord
)} - {vtuber.attributes.instagram && ( + {vtuber.instagram && (
- + Instagram
)} - {vtuber.attributes.facebook && ( + {vtuber.facebook && (
- + Facebook
)} - {vtuber.attributes.merch && ( + {vtuber.merch && (
- + Merch
)} - {vtuber.attributes.chaturbate && ( + {vtuber.chaturbate && (
- + Chaturbate
)} - {vtuber.attributes.throne && ( + {vtuber.throne && (
- + Throne
)} - {vtuber.attributes.linktree && ( + {vtuber.linktree && (
- + Linktree
)} - {vtuber.attributes.carrd && ( + {vtuber.carrd && (
- + Carrd
@@ -214,17 +196,17 @@ export default async function Page({ params }: { params: { slug: string } }) { <> - {(toys.pagination.total > toySampleCount) && See all of {vtuber.displayName}'s toys} + {(toys.pagination.total > toySampleCount) && See all of {vtuber.display_name}'s toys} */}

Vods

- + { - (vods.data) ? ( - See all {vtuber.attributes.displayName} vods + (vods) ? ( + See all {vtuber.display_name} vods ) : (

No VODs have been added, yet.

) } diff --git a/services/next/app/vt/[slug]/vods/[page]/page.tsx b/services/next/app/vt/[slug]/vods/[page]/page.tsx index 32c8d13..b4ccf4b 100644 --- a/services/next/app/vt/[slug]/vods/[page]/page.tsx +++ b/services/next/app/vt/[slug]/vods/[page]/page.tsx @@ -15,6 +15,7 @@ interface IPageParams { export default async function Page({ params }: IPageParams) { let vtuber, vods; const pageNumber = parseInt(params.page); + const pageSize = 24 try { vtuber = await getVtuberBySlug(params.slug); @@ -31,12 +32,12 @@ export default async function Page({ params }: IPageParams) { if (!vods) return

error

return ( <> - - + + ); diff --git a/services/next/app/vt/[slug]/vods/page.tsx b/services/next/app/vt/[slug]/vods/page.tsx index 072ebbd..1849ddb 100644 --- a/services/next/app/vt/[slug]/vods/page.tsx +++ b/services/next/app/vt/[slug]/vods/page.tsx @@ -13,14 +13,20 @@ interface IPageParams { export default async function Page({ params }: IPageParams) { const vtuber = await getVtuberBySlug(params.slug) + const pageCount = 24 if (!vtuber) notFound(); - const vods = await getVodsForVtuber(vtuber.id, 1, 24) + const vods = await getVodsForVtuber(vtuber.id, 1, pageCount) if (!vods) notFound(); return ( <> - - - +
+                
+                    {JSON.stringify(vods, null, 2)}
+                
+            
+ {/* + + */} ) } \ No newline at end of file diff --git a/services/next/app/vt/page.tsx b/services/next/app/vt/page.tsx index de76ad3..93a0cd9 100644 --- a/services/next/app/vt/page.tsx +++ b/services/next/app/vt/page.tsx @@ -16,11 +16,16 @@ export default async function Page() { // ) return ( <> + {/*
+                
+                    {JSON.stringify(vtubers, null, 2)}
+                
+            
*/}

VTubers

diff --git a/services/next/logto.ts b/services/next/logto.ts deleted file mode 100644 index e2fc8fb..0000000 --- a/services/next/logto.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { UserScope } from '@logto/next'; - -export const logtoConfig = { - appId: process.env.APP_ID ?? '', - appSecret: process.env.APP_SECRET ?? '', - endpoint: process.env.ENDPOINT ?? 'http://localhost:3001', - baseUrl: process.env.BASE_URL ?? 'http://localhost:3000', - cookieSecret: process.env.COOKIE_SECRET ?? 'complex_password_at_least_32_characters_long', - cookieSecure: process.env.NODE_ENV === 'production', - // Optional fields for RBAC - resources: process.env.RESOURCES?.split(','), - scopes: process.env.SCOPES?.split(',') ?? [UserScope.Organizations, UserScope.OrganizationRoles], -}; \ No newline at end of file diff --git a/services/next/middleware.ts b/services/next/middleware.ts new file mode 100644 index 0000000..01ab6e7 --- /dev/null +++ b/services/next/middleware.ts @@ -0,0 +1,176 @@ +/** + * middleware.ts + * + * 2024-12-04 + * The purpose of this middleware is to keep our Keycloak session up-to-date, so we can call Keycloak endpoints and not get rejected. + * We are doing this because Keycloak client tokens expire after 5 minutes. + * Ultimately we are doing this so we can get a Keycloak Identity Provider (Patreon) token, + * get a list of the user's currently entitled tiers, + * and assign them appropriate roles for access control. + * + * + * @see https://github.com/nextauthjs/next-auth/discussions/9715#discussioncomment-10640404 + * @see https://github.com/nextauthjs/next-auth/discussions/3940 + * @see https://nextjs.org/docs/app/building-your-application/routing/middleware + * @see https://github.com/keycloak/keycloak/blob/main/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java#L517 + * @see https://github.com/nextauthjs/next-auth/discussions/3940#discussioncomment-8292882 tl;dr: use next.js middleware to refresh session + * + */ + +import { NextMiddleware, NextRequest, NextResponse } from "next/server"; +import { encode, JWT } from 'next-auth/jwt'; +import { getToken } from "next-auth/jwt"; +import { configs } from "./app/config/configs"; + +export const tokenRefreshBufferSeconds = 300; +export const isSessionSecure = configs.nextAuthUrl.startsWith("https://"); +export const SESSION_COOKIE = isSessionSecure ? "__Secure-next-auth.session-token" : "next-auth.session-token"; +// export const sessionTimeout = 60 * 60 * 24 * 30; // 30 days +export const sessionTimeout = 60 * 5 +export const signinSubUrl = "/api/auth/signin"; + +let isRefreshing = false; + + +export async function refreshAccessToken(token: JWT): Promise { + + if (isRefreshing) { + return token; + } + + const timeInSeconds = Math.floor(Date.now() / 1000); + isRefreshing = true; + + try { + const newAccessTokenRes = await fetch(`https://keycloak.fp.sbtp.xyz/realms/futureporn/protocol/openid-connect/token`, { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'refresh_token', + client_id: configs.keycloakClientId, + client_secret: configs.keycloakClientSecret, + refresh_token: token.refresh_token, + }).toString(), + }) + const newAccessToken = await newAccessTokenRes.json() + // console.log('newAccessToken json as follows') + // console.log(newAccessToken) + if (!newAccessTokenRes.ok) { + console.error(newAccessToken) + throw new Error(JSON.stringify(newAccessToken)); + } + + const newToken = { + ...token, + access_token: newAccessToken?.access_token ?? token?.access_token, + expires_at: newAccessToken?.expires_in + timeInSeconds, + refresh_token: newAccessToken?.refresh_token ?? token?.refresh_token + }; + console.log('newToken as follows') + console.log(newToken) + return newToken + + } catch (e) { + console.error('failed to refreshAccessToken()') + console.error(e); + } finally { + isRefreshing = false; + } + + return token; +} + + +/** + * @see https://github.com/nextauthjs/next-auth/discussions/9715#discussioncomment-8319836 + */ +export function shouldUpdateToken(token: JWT): boolean { + const timeInSeconds = Math.floor(Date.now() / 1000); + return timeInSeconds >= (token?.expires_at - tokenRefreshBufferSeconds); +} + + +export function updateCookie( + sessionToken: string | null, + request: NextRequest, + response: NextResponse +): NextResponse { + /* + * BASIC IDEA: + * + * 1. Set request cookies for the incoming getServerSession to read new session + * 2. Updated request cookie can only be passed to server if it's passed down here after setting its updates + * 3. Set response cookies to send back to browser + */ + + if (sessionToken) { + // console.log('Set the session token in the request and response cookies for a valid session') + // request.cookies.set(SESSION_COOKIE, sessionToken); + response = NextResponse.next({ + request: { + headers: request.headers + } + }); + response.cookies.set(SESSION_COOKIE, sessionToken, { + httpOnly: true, + maxAge: sessionTimeout, + secure: isSessionSecure, + sameSite: "lax" + }); + } else { + request.cookies.delete(SESSION_COOKIE); + return NextResponse.redirect(new URL(signinSubUrl, request.url)); + } + + return response; +} + + +// @todo this is broken. This sets multiple cookies with the same name. +// @todo this is broken. The updated session token does not get used! +export const middleware: NextMiddleware = async (request: NextRequest) => { + + + const token = await getToken({ req: request }); + // console.log(`middlew.are. token as follows`) + // console.log(token) + + let response = NextResponse.next(); + // return response + + if (!token) { + return response + } + + // we only want to consider updating the token if the request is not an auth request + if (request.nextUrl.pathname.startsWith('/api/auth')) { + return response + } + + // we only want to consider updating the token if the request is accessing /api/patreon/* endpoint + if (!request.nextUrl.pathname.startsWith('/api/patreon')) { + return response + } + + if (shouldUpdateToken(token)) { + console.log(`Attempting to update the token.`) + + try { + const newSessionToken = await encode({ + secret: configs.nextAuthSecret, + token: await refreshAccessToken(token), + maxAge: sessionTimeout + }); + response = updateCookie(newSessionToken, request, response); + } catch (error) { + console.log("Error refreshing token: ", error); + return updateCookie(null, request, response); + } + } + + console.log(`We succeeded in updating the token.`) + // console.log(response) + return response; +}; \ No newline at end of file diff --git a/services/next/package.json b/services/next/package.json index 9d8bd34..d0d8130 100644 --- a/services/next/package.json +++ b/services/next/package.json @@ -12,12 +12,15 @@ "superclean": "rm -rf node_modules && rm -rf pnpm-lock.yaml && rm -rf dist" }, "dependencies": { + "@dicebear/collection": "^9.2.2", + "@dicebear/core": "^9.2.2", "@fortawesome/fontawesome-free": "^6.6.0", "@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/free-brands-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", "@futureporn/types": "workspace:*", + "@futureporn/utils": "workspace:^", "@hookform/error-message": "^2.0.1", "@hookform/resolvers": "^3.9.0", "@logto/next": "^3.7.1", @@ -49,15 +52,17 @@ "feed": "^4.2.2", "gray-matter": "^4.0.3", "hls.js": "^1.5.15", + "jose": "^5.9.6", + "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "lunarphase-js": "^2.0.3", "multiformats": "^13.2.2", "next": "14.2.7", + "next-auth": "4.24.10", "next-goatcounter": "^1.0.5", "nextjs-cors": "^2.2.0", "nextjs-toploader": "^3.6.15", "plyr": "^3.7.8", - "prism-react-renderer": "^2.4.0", "qs": "^6.13.0", "react": "^18.3.1", "react-data-table-component": "^7.6.2", @@ -69,9 +74,6 @@ "sharp": "^0.33.5", "slugify": "^1.6.6", "styled-components": "6.1.13", - "supertokens-auth-react": "^0.48.0", - "supertokens-node": "^21.0.0", - "supertokens-web-js": "^0.14.0", "yup": "^1.4.0" }, "devDependencies": { @@ -80,5 +82,5 @@ "eslint-config-next": "14.2.7", "typescript": "5.5.4" }, - "packageManager": "pnpm@9.1.3" + "packageManager": "pnpm@9.14.4" } diff --git a/services/next/pnpm-lock.yaml b/services/next/pnpm-lock.yaml index adab63d..a7b4b90 100644 --- a/services/next/pnpm-lock.yaml +++ b/services/next/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@dicebear/collection': + specifier: ^9.2.2 + version: 9.2.2(@dicebear/core@9.2.2) + '@dicebear/core': + specifier: ^9.2.2 + version: 9.2.2 '@fortawesome/fontawesome-free': specifier: ^6.6.0 version: 6.6.0 @@ -26,6 +32,9 @@ importers: '@futureporn/types': specifier: workspace:* version: link:../../packages/types + '@futureporn/utils': + specifier: workspace:^ + version: link:../../packages/utils '@hookform/error-message': specifier: ^2.0.1 version: 2.0.1(react-dom@18.3.1(react@18.3.1))(react-hook-form@7.53.0(react@18.3.1))(react@18.3.1) @@ -119,6 +128,12 @@ importers: hls.js: specifier: ^1.5.15 version: 1.5.15 + jose: + specifier: ^5.9.6 + version: 5.9.6 + jwt-decode: + specifier: ^4.0.0 + version: 4.0.0 lodash: specifier: ^4.17.21 version: 4.17.21 @@ -131,6 +146,9 @@ importers: next: specifier: 14.2.7 version: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + next-auth: + specifier: 4.24.10 + version: 4.24.10(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(nodemailer@6.9.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-goatcounter: specifier: ^1.0.5 version: 1.0.5(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -143,9 +161,6 @@ importers: plyr: specifier: ^3.7.8 version: 3.7.8 - prism-react-renderer: - specifier: ^2.4.0 - version: 2.4.0(react@18.3.1) qs: specifier: ^6.13.0 version: 6.13.0 @@ -179,15 +194,6 @@ importers: styled-components: specifier: 6.1.13 version: 6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - supertokens-auth-react: - specifier: ^0.48.0 - version: 0.48.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supertokens-web-js@0.14.0) - supertokens-node: - specifier: ^21.0.0 - version: 21.0.0 - supertokens-web-js: - specifier: ^0.14.0 - version: 0.14.0 yup: specifier: ^1.4.0 version: 1.4.0 @@ -205,36 +211,202 @@ importers: specifier: 5.5.4 version: 5.5.4 - ../..: {} - - ../../packages/fetchers: {} - - ../../packages/infra: {} - - ../../packages/storage: {} - - ../../packages/types: {} - - ../../packages/utils: {} - - ../bot: {} - - ../capture: {} - - ../factory: {} - - ../mailbox: {} - - ../migrations: {} - - ../scout: {} - - ../strapi: {} - - ../uppy: {} - packages: + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} + engines: {node: '>=6.9.0'} + + '@dicebear/adventurer-neutral@9.2.2': + resolution: {integrity: sha512-XVAjhUWjav6luTZ7txz8zVJU/H0DiUy4uU1Z7IO5MDO6kWvum+If1+0OUgEWYZwM+RDI7rt2CgVP910DyZGd1w==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/adventurer@9.2.2': + resolution: {integrity: sha512-WjBXCP9EXbUul2zC3BS2/R3/4diw1uh/lU4jTEnujK1mhqwIwanFboIMzQsasNNL/xf+m3OHN7MUNJfHZ1fLZA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/avataaars-neutral@9.2.2': + resolution: {integrity: sha512-pRj16P27dFDBI3LtdiHUDwIXIGndHAbZf5AxaMkn6/+0X93mVQ/btVJDXyW0G96WCsyC88wKAWr6/KJotPxU6Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/avataaars@9.2.2': + resolution: {integrity: sha512-WqJPQEt0OhBybTpI0TqU1uD1pSk9M2+VPIwvBye/dXo46b+0jHGpftmxjQwk6tX8z0+mRko8pwV5n+cWht1/+w==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/big-ears-neutral@9.2.2': + resolution: {integrity: sha512-IPHt8fi3dv9cyfBJBZ4s8T+PhFCrQvOCf91iRHBT3iOLNPdyZpI5GNLmGiV0XMAvIDP5NvA5+f6wdoBLhYhbDA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/big-ears@9.2.2': + resolution: {integrity: sha512-hz4UXdPq4qqZpu0YVvlqM4RDFhk5i0WgPcuwj/MOLlgTjuj63uHUhCQSk6ZiW1DQOs12qpwUBMGWVHxBRBas9g==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/big-smile@9.2.2': + resolution: {integrity: sha512-D4td0GL8or1nTNnXvZqkEXlzyqzGPWs3znOnm1HIohtFTeIwXm72Ob2lNDsaQJSJvXmVlwaQQ0CCTvyCl8Stjw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/bottts-neutral@9.2.2': + resolution: {integrity: sha512-lSgpqmSJtlnyxVuUgNdBwyzuA0O9xa5zRJtz7x2KyWbicXir5iYdX0MVMCkp1EDvlcxm9rGJsclktugOyakTlw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/bottts@9.2.2': + resolution: {integrity: sha512-wugFkzw8JNWV1nftq/Wp/vmQsLAXDxrMtRK3AoMODuUpSVoP3EHRUfKS043xggOsQFvoj0HZ7kadmhn0AMLf5A==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/collection@9.2.2': + resolution: {integrity: sha512-vZAmXhPWCK3sf8Fj9/QflFC6XOLroJOT5K1HdnzHaPboEvffUQideGCrrEamnJtlH0iF0ZDXh8gqmwy2fu+yHA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/core@9.2.2': + resolution: {integrity: sha512-ROhgHG249dPtcXgBHcqPEsDeAPRPRD/9d+tZCjLYyueO+cXDlIA8dUlxpwIVcOuZFvCyW6RJtqo8BhNAi16pIQ==} + engines: {node: '>=18.0.0'} + + '@dicebear/croodles-neutral@9.2.2': + resolution: {integrity: sha512-/4mNirxoQ+z1kHXnpDRbJ1JV1ZgXogeTeNp0MaFYxocCgHfJ7ckNM23EE1I7akoo9pqPxrKlaeNzGAjKHdS9vA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/croodles@9.2.2': + resolution: {integrity: sha512-OzvAXQWsOgMwL3Sl+lBxCubqSOWoBJpC78c4TKnNTS21rR63TtXUyVdLLzgKVN4YHRnvMgtPf8F/W9YAgIDK4w==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/dylan@9.2.2': + resolution: {integrity: sha512-s7e3XliC1YXP+Wykj+j5kwdOWFRXFzYHYk/PB4oZ1F3sJandXiG0HS4chaNu4EoP0yZgKyFMUVTGZx+o6tMaYg==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/fun-emoji@9.2.2': + resolution: {integrity: sha512-M+rYTpB3lfwz18f+/i+ggNwNWUoEj58SJqXJ1wr7Jh/4E5uL+NmJg9JGwYNaVtGbCFrKAjSaILNUWGQSFgMfog==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/glass@9.2.2': + resolution: {integrity: sha512-imCMxcg+XScHYtQq2MUv1lCzhQSCUglMlPSezKEpXhTxgbgUpmGlSGVkOfmX5EEc7SQowKkF1W/1gNk6CXvBaQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/icons@9.2.2': + resolution: {integrity: sha512-Tqq2OVCdS7J02DNw58xwlgLGl40sWEckbqXT3qRvIF63FfVq+wQZBGuhuiyAURcSgvsc3h2oQeYFi9iXh7HTOA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/identicon@9.2.2': + resolution: {integrity: sha512-POVKFulIrcuZf3rdAgxYaSm2XUg/TJg3tg9zq9150reEGPpzWR7ijyJ03dzAADPzS3DExfdYVT9+z3JKwwJnTQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/initials@9.2.2': + resolution: {integrity: sha512-/xNnsEmsstWjmF77htAOuwOMhFlP6eBVXgcgFlTl/CCH/Oc6H7t0vwX1he8KLQBBzjGpvJcvIAn4Wh9rE4D5/A==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/lorelei-neutral@9.2.2': + resolution: {integrity: sha512-Eys9Os6nt2Xll7Mvu66CfRR2YggTopWcmFcRZ9pPdohS96kT0MsLI2iTcfZXQ51K8hvT3IbwoGc86W8n0cDxAQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/lorelei@9.2.2': + resolution: {integrity: sha512-koXqVr/vcWUPo00VP5H6Czsit+uF1tmwd2NK7Q/e34/9Sd1f4QLLxHjjBNm/iNjCI1+UNTOvZ2Qqu0N5eo7Flw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/micah@9.2.2': + resolution: {integrity: sha512-NCajcJV5yw8uMKiACp694w1T/UyYme2CUEzyTzWHgWnQ+drAuCcH8gpAoLWd67viNdQB/MTpNlaelUgTjmI4AQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/miniavs@9.2.2': + resolution: {integrity: sha512-vvkWXttdw+KHF3j+9qcUFzK+P0nbNnImGjvN48wwkPIh2h08WWFq0MnoOls4IHwUJC4GXBjWtiyVoCxz6hhtOA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/notionists-neutral@9.2.2': + resolution: {integrity: sha512-AhOzk+lz6kB4uxGun8AJhV+W1nttnMlxmxd+5KbQ/txCIziYIaeD3il44wsAGegEpGFvAZyMYtR/jjfHcem3TA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/notionists@9.2.2': + resolution: {integrity: sha512-Z9orRaHoj7Y9Ap4wEu8XOrFACsG1KbbBQUPV1R50uh6AHwsyNrm4cS84ICoGLvxgLNHHOae3YCjd8aMu2z19zg==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/open-peeps@9.2.2': + resolution: {integrity: sha512-6PeQDHYyjvKrGSl/gP+RE5dSYAQGKpcGnM65HorgyTIugZK7STo0W4hvEycedupZ3MCCEH8x/XyiChKM2sHXog==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/personas@9.2.2': + resolution: {integrity: sha512-705+ObNLC0w1fcgE/Utav+8bqO+Esu53TXegpX5j7trGEoIMf2bThqJGHuhknZ3+T2az3Wr89cGyOGlI0KLzLA==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/pixel-art-neutral@9.2.2': + resolution: {integrity: sha512-CdUY77H6Aj7dKLW3hdkv7tu0XQJArUjaWoXihQxlhl3oVYplWaoyu9omYy5pl8HTqs8YgVTGljjMXYoFuK0JUw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/pixel-art@9.2.2': + resolution: {integrity: sha512-BvbFdrpzQl04+Y9UsWP63YGug+ENGC7GMG88qbEFWxb/IqRavGa4H3D0T4Zl2PSLiw7f2Ctv98bsCQZ1PtCznQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/rings@9.2.2': + resolution: {integrity: sha512-eD1J1k364Arny+UlvGrk12HP/XGG6WxPSm4BarFqdJGSV45XOZlwqoi7FlcMr9r9yvE/nGL8OizbwMYusEEdjw==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/shapes@9.2.2': + resolution: {integrity: sha512-e741NNWBa7fg0BjomxXa0fFPME2XCIR0FA+VHdq9AD2taTGHEPsg5x1QJhCRdK6ww85yeu3V3ucpZXdSrHVw5Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + + '@dicebear/thumbs@9.2.2': + resolution: {integrity: sha512-FkPLDNu7n5kThLSk7lR/0cz/NkUqgGdZGfLZv6fLkGNGtv6W+e2vZaO7HCXVwIgJ+II+kImN41zVIZ6Jlll7pQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@dicebear/core': ^9.0.0 + '@edge-runtime/cookies@4.1.1': resolution: {integrity: sha512-ATZLTOpnCUD9ZLNBIXhxOmP/UVx6BfhCjDy9P1YACpD8vrHb5Uw7YlG9RYUl1AMF7Y10TIIN3jhFbUSMiH2J7g==} engines: {node: '>=16'} @@ -551,6 +723,9 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@panva/hkdf@1.2.1': + resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==} + '@paralleldrive/cuid2@2.2.2': resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} @@ -610,6 +785,9 @@ packages: '@transloadit/prettier-bytes@0.3.4': resolution: {integrity: sha512-8/SnIF9Q2k52mbjRVAYLranwkaDTLb+O9r4Z/uo8uNw//SjygKvvbF4BHSOuReufaAyum1q13602VcNud25Dfg==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} @@ -619,9 +797,6 @@ packages: '@types/node@22.5.2': resolution: {integrity: sha512-acJsPTEqYqulZS/Yp/S3GgeE6GZ0qYODUR8aVr/DkhHQ8l9nd4j5x1/ZJy9/gHrRlFMqkO6i0I3E27Alu4jjPg==} - '@types/prismjs@1.26.4': - resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==} - '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} @@ -821,10 +996,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -896,9 +1067,6 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -907,9 +1075,6 @@ packages: resolution: {integrity: sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==} engines: {node: '>=4'} - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} - axobject-query@3.1.1: resolution: {integrity: sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==} @@ -939,18 +1104,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browser-tabs-lock@1.3.0: - resolution: {integrity: sha512-g6nHaobTiT0eMZ7jh16YpD2kcjAp+PInbiVq3M1x6KKaEIVhT4v9oURNIpZLOZ3LQbQ3XYfNhMAb/9hzNLIWrw==} - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bulma@1.0.2: resolution: {integrity: sha512-D7GnDuF6seb6HkcnRMM9E739QpEY9chDzzeFrHMyEns/EXyDJuQ0XA0KxbBl/B2NTsKSoDomW61jFGFaAxhK5A==} @@ -1018,23 +1174,15 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} core-js@3.38.1: @@ -1044,16 +1192,10 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} - cross-fetch@3.1.8: - resolution: {integrity: sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==} - cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} - crypto-js@4.2.0: - resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==} - css-color-keywords@1.0.0: resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} engines: {node: '>=4'} @@ -1140,10 +1282,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -1163,9 +1301,6 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1391,15 +1526,6 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -1407,10 +1533,6 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} - engines: {node: '>= 6'} - fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -1507,10 +1629,6 @@ packages: hls.js@1.5.15: resolution: {integrity: sha512-6cD7xN6bycBHaXz2WyPIaHn/iXFizE5au2yvY5q9aC4wfihxAr16C9fUy4nxh2a3wOw0fEgLRa9dN6wsYjlpNg==} - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -1539,9 +1657,6 @@ packages: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} - intl-tel-input@17.0.21: - resolution: {integrity: sha512-TfyPxLe41QZPOf6RqBxRE2dpQ0FThB/PBD/gRbxVhGW7IuYg30QD90x/vjmEo4vkZw7j8etxpVcjIZVRcG+Otw==} - is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -1715,19 +1830,13 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + jwt-decode@4.0.0: + resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} + engines: {node: '>=18'} keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1747,9 +1856,6 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - libphonenumber-js@1.11.12: - resolution: {integrity: sha512-QkJn9/D7zZ1ucvT++TQSvZuSA2xAWeUytU+DiEQwbPKLyrDpvbul2AFs1CGbRAPpSCCk47aRAb5DX5mmcayp4g==} - loadjs@4.3.0: resolution: {integrity: sha512-vNX4ZZLJBeDEOBvdr2v/F+0aN5oMuPu7JTqrMwp+DtgK+AryOlpy6Xtm2/HpNr+azEa828oQjOtWsB6iDtSfSQ==} @@ -1757,30 +1863,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1791,6 +1876,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + lunarphase-js@2.0.3: resolution: {integrity: sha512-zTr/UWbxQ1lyKgaBnyJ/DvKCPONhZQcwmQ8PuF2g2QwdkF8JkhgPe8QlDroxSjZrfpg/9x6jQ6lFtBkSXXf1oQ==} @@ -1815,17 +1904,9 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - mime-match@1.0.2: resolution: {integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==} - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -1882,6 +1963,20 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + next-auth@4.24.10: + resolution: {integrity: sha512-8NGqiRO1GXBcVfV8tbbGcUgQkAGsX4GRzzXXea4lDikAsJtD5KiEY34bfhUOjHLvr6rT6afpcxw2H8EZqOV6aQ==} + peerDependencies: + '@auth/core': 0.34.2 + next: ^12.2.5 || ^13 || ^14 || ^15 + nodemailer: ^6.6.5 + react: ^17.0.2 || ^18 + react-dom: ^17.0.2 || ^18 + peerDependenciesMeta: + '@auth/core': + optional: true + nodemailer: + optional: true + next-goatcounter@1.0.5: resolution: {integrity: sha512-t2khpwRk1sDeZwth8wg0SF6MKAQ4lrUJ0qKFETmeEkIudcrc6hbMSvmhiVc8XrpTl13S4F2wWRH248FSyY8Gvw==} peerDependencies: @@ -1926,15 +2021,6 @@ packages: node-addon-api@5.1.0: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - nodemailer@6.9.16: resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} engines: {node: '>=6.0.0'} @@ -1946,10 +2032,17 @@ packages: nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + oauth@0.9.15: + resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@2.2.0: + resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==} + engines: {node: '>= 6'} + object-inspect@1.13.2: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} @@ -1982,9 +2075,16 @@ packages: resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} engines: {node: '>= 0.4'} + oidc-token-hash@5.0.3: + resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} + engines: {node: ^10.13.0 || >=12.0.0} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + openid-client@5.7.1: + resolution: {integrity: sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2009,9 +2109,6 @@ packages: resolution: {integrity: sha512-UbD77BuZ9Bc9aABo74gfXhNvzC9Tx7SxtHSh1fxvx3jTLLYvmVhiQZZrJzqqU0jKbN32kb5VOKiLEQI/3bIjgQ==} engines: {node: '>=14.16'} - pako@2.1.0: - resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2042,9 +2139,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pkce-challenge@3.1.0: - resolution: {integrity: sha512-bQ/0XPZZ7eX+cdAkd61uYWpfMhakH3NeteUF1R8GNa+LMqX8QFAkbCLqq+AYAns1/ueACBu/BMWhrlKGrdvGZg==} - plyr@3.7.8: resolution: {integrity: sha512-yG/EHDobwbB/uP+4Bm6eUpJ93f8xxHjjk2dYcD1Oqpe1EcuQl5tzzw9Oq+uVAzd2lkM11qZfydSiyIpiB8pgdA==} @@ -2063,6 +2157,11 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} + preact-render-to-string@5.2.3: + resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==} + peerDependencies: + preact: '>=10' + preact@10.23.2: resolution: {integrity: sha512-kKYfePf9rzKnxOAKDpsWhg/ysrHPqT+yQ7UW4JjdnqjFIeNUnNcEJvhuA8fDenxAGWzUqtd51DfVg7xp/8T9NA==} @@ -2075,14 +2174,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prism-react-renderer@2.4.0: - resolution: {integrity: sha512-327BsVCD/unU4CNLZTWVHyUHKnsqcvj2qbPlQ8MiBE2eq2rgctjigPA1Gp9HLF83kZ20zNN6jgizHJeEsyFYOw==} - peerDependencies: - react: '>=16.0.0' - - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} + pretty-format@3.8.0: + resolution: {integrity: sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==} prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -2090,9 +2183,6 @@ packages: property-expr@2.0.6: resolution: {integrity: sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} @@ -2100,16 +2190,10 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qr.js@0.0.0: - resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} - qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2149,11 +2233,6 @@ packages: peerDependencies: react: '>=16.8.0' - react-qr-code@2.0.15: - resolution: {integrity: sha512-MkZcjEXqVKqXEIMVE0mbcGgDpkfSdd8zhuzXEl9QzYeNcw8Hq2oVIzDLWuZN2PQBwM5PWjc2S31K8Q1UbcFMfw==} - peerDependencies: - react: '*' - react-toastify@10.0.5: resolution: {integrity: sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==} peerDependencies: @@ -2176,13 +2255,13 @@ packages: resolution: {integrity: sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==} engines: {node: '>= 0.4'} + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + regexp.prototype.flags@1.5.2: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2231,9 +2310,6 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - scmp@2.1.0: - resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} - section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} @@ -2247,9 +2323,6 @@ packages: engines: {node: '>=10'} hasBin: true - set-cookie-parser@2.7.1: - resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} - set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2399,26 +2472,6 @@ packages: stylis@4.3.2: resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} - supertokens-auth-react@0.48.0: - resolution: {integrity: sha512-JDahnvSKahso6LbD3Oe/e2Ifxz/dg7kMVGnlt+sZbz/kNSQAKk7yaTrxrCemcNDw5IDB9etheCVrLu7ye/ndmQ==} - engines: {node: '>=16.0.0', npm: '>=8'} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - supertokens-web-js: ^0.14.0 - - supertokens-js-override@0.0.4: - resolution: {integrity: sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==} - - supertokens-node@21.0.0: - resolution: {integrity: sha512-2Ui7uoLEDXjk39rny/EONslZm4tc5ISS/bup93MNnCxY3BDOmn5f9Qyvori66jIDDOVbPD0kQLlE4RD61i8jtg==} - - supertokens-web-js@0.14.0: - resolution: {integrity: sha512-p4HZ580YX9UYFfY9Sv2VzBQOilqHnNzhrmHlOc45oMxqr/vqvqf+Ih7OXS1lx6RUVmQT8TAsLlFFKIWslIkbHA==} - - supertokens-website@20.1.5: - resolution: {integrity: sha512-2yN42BvHY41/pNIFdJTKSRW3sWZzfOY607i6cY+WWjHSAx7ppMgujyk8tKj+fiQ4MLWCk3HL6QsXZl0zLV4yEw==} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2444,13 +2497,6 @@ packages: tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} - tldts-core@6.1.58: - resolution: {integrity: sha512-dR936xmhBm7AeqHIhCWwK765gZ7dFyL+IqLSFAjJbFlUXGMLCb8i2PzlzaOuWBuplBTaBYseSb565nk/ZEM0Bg==} - - tldts@6.1.58: - resolution: {integrity: sha512-MQJrJhjHOYGYb8DobR6Y4AdDbd4TYkyQ+KBDVc5ODzs1cbrvPpfN1IemYi9jfipJ/vR1YWvrDli0hg1y19VRoA==} - hasBin: true - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -2458,9 +2504,6 @@ packages: toposort@2.0.2: resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -2479,10 +2522,6 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - twilio@4.23.0: - resolution: {integrity: sha512-LdNBQfOe0dY2oJH2sAsrxazpgfFQo5yXGxe96QA8UWB5uu+433PrUbkv8gQ5RmrRCqUTPQ0aOrIyAdBr1aB03Q==} - engines: {node: '>=14.0'} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -2525,9 +2564,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - url-polyfill@1.1.12: resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==} @@ -2539,16 +2575,14 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2591,9 +2625,8 @@ packages: resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} hasBin: true - xmlbuilder@13.0.2: - resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==} - engines: {node: '>=6.0'} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} @@ -2604,6 +2637,168 @@ packages: snapshots: + '@babel/runtime@7.26.0': + dependencies: + regenerator-runtime: 0.14.1 + + '@dicebear/adventurer-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/adventurer@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/avataaars-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/avataaars@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/big-ears-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/big-ears@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/big-smile@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/bottts-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/bottts@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/collection@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/adventurer': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/adventurer-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/avataaars': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/avataaars-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/big-ears': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/big-ears-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/big-smile': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/bottts': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/bottts-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/core': 9.2.2 + '@dicebear/croodles': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/croodles-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/dylan': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/fun-emoji': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/glass': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/icons': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/identicon': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/initials': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/lorelei': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/lorelei-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/micah': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/miniavs': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/notionists': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/notionists-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/open-peeps': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/personas': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/pixel-art': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/pixel-art-neutral': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/rings': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/shapes': 9.2.2(@dicebear/core@9.2.2) + '@dicebear/thumbs': 9.2.2(@dicebear/core@9.2.2) + + '@dicebear/core@9.2.2': + dependencies: + '@types/json-schema': 7.0.15 + + '@dicebear/croodles-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/croodles@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/dylan@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/fun-emoji@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/glass@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/icons@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/identicon@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/initials@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/lorelei-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/lorelei@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/micah@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/miniavs@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/notionists-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/notionists@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/open-peeps@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/personas@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/pixel-art-neutral@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/pixel-art@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/rings@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/shapes@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + + '@dicebear/thumbs@9.2.2(@dicebear/core@9.2.2)': + dependencies: + '@dicebear/core': 9.2.2 + '@edge-runtime/cookies@4.1.1': {} '@emnapi/runtime@1.2.0': @@ -2880,6 +3075,8 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@panva/hkdf@1.2.1': {} + '@paralleldrive/cuid2@2.2.2': dependencies: '@noble/hashes': 1.5.0 @@ -2925,6 +3122,8 @@ snapshots: '@transloadit/prettier-bytes@0.3.4': {} + '@types/json-schema@7.0.15': {} + '@types/json5@0.0.29': {} '@types/lodash@4.17.7': {} @@ -2933,8 +3132,6 @@ snapshots: dependencies: undici-types: 6.19.8 - '@types/prismjs@1.26.4': {} - '@types/prop-types@15.7.12': {} '@types/qs@6.9.15': {} @@ -3206,12 +3403,6 @@ snapshots: acorn@8.12.1: {} - agent-base@6.0.2: - dependencies: - debug: 4.3.6 - transitivePeerDependencies: - - supports-color - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3313,22 +3504,12 @@ snapshots: ast-types-flow@0.0.8: {} - asynckit@0.4.0: {} - available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 axe-core@4.10.0: {} - axios@1.7.7(debug@4.3.6): - dependencies: - follow-redirects: 1.15.9(debug@4.3.6) - form-data: 4.0.1 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - axobject-query@3.1.1: dependencies: deep-equal: 2.2.3 @@ -3360,22 +3541,11 @@ snapshots: dependencies: fill-range: 7.1.1 - browser-tabs-lock@1.3.0: - dependencies: - lodash: 4.17.21 - - buffer-equal-constant-time@1.0.1: {} - buffer@5.7.1: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bulma@1.0.2: {} busboy@1.6.0: @@ -3450,17 +3620,11 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - concat-map@0.0.1: {} - content-type@1.0.5: {} - cookie@0.6.0: {} - cookie@0.7.2: {} + cookie@0.7.1: {} core-js@3.38.1: {} @@ -3469,20 +3633,12 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cross-fetch@3.1.8: - dependencies: - node-fetch: 2.7.0 - transitivePeerDependencies: - - encoding - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - crypto-js@4.2.0: {} - css-color-keywords@1.0.0: {} css-to-react-native@3.2.0: @@ -3576,8 +3732,6 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - delayed-stream@1.0.0: {} - detect-libc@2.0.3: {} dir-glob@3.0.1: @@ -3592,10 +3746,6 @@ snapshots: eastasianwidth@0.2.0: {} - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.2.1 - emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -3966,10 +4116,6 @@ snapshots: flatted@3.3.1: {} - follow-redirects@1.15.9(debug@4.3.6): - optionalDependencies: - debug: 4.3.6 - for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -3979,12 +4125,6 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 - form-data@4.0.1: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - fs-constants@1.0.0: {} fsevents@2.3.3: @@ -4088,13 +4228,6 @@ snapshots: hls.js@1.5.15: {} - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.3.6 - transitivePeerDependencies: - - supports-color - ieee754@1.2.1: {} ignore@5.3.2: {} @@ -4118,8 +4251,6 @@ snapshots: hasown: 2.0.2 side-channel: 1.0.6 - intl-tel-input@17.0.21: {} - is-arguments@1.1.1: dependencies: call-bind: 1.0.7 @@ -4278,19 +4409,6 @@ snapshots: dependencies: minimist: 1.2.8 - jsonwebtoken@9.0.2: - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.6.3 - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -4298,16 +4416,7 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 - jwa@1.4.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - - jws@3.2.2: - dependencies: - jwa: 1.4.1 - safe-buffer: 5.2.1 + jwt-decode@4.0.0: {} keyv@4.5.4: dependencies: @@ -4326,30 +4435,14 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - libphonenumber-js@1.11.12: {} - loadjs@4.3.0: {} locate-path@6.0.0: dependencies: p-locate: 5.0.0 - lodash.includes@4.3.0: {} - - lodash.isboolean@3.0.3: {} - - lodash.isinteger@4.0.4: {} - - lodash.isnumber@3.0.3: {} - - lodash.isplainobject@4.0.6: {} - - lodash.isstring@4.0.1: {} - lodash.merge@4.6.2: {} - lodash.once@4.1.1: {} - lodash@4.17.21: {} loose-envify@1.4.0: @@ -4358,6 +4451,10 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + lunarphase-js@2.0.3: {} map-obj@4.3.0: {} @@ -4375,16 +4472,10 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} - mime-match@1.0.2: dependencies: wildcard: 1.1.2 - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mimic-response@3.1.0: {} minimatch@3.1.2: @@ -4423,6 +4514,23 @@ snapshots: natural-compare@1.4.0: {} + next-auth@4.24.10(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(nodemailer@6.9.16)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.26.0 + '@panva/hkdf': 1.2.1 + cookie: 0.7.1 + jose: 4.15.9 + next: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) + oauth: 0.9.15 + openid-client: 5.7.1 + preact: 10.23.2 + preact-render-to-string: 5.2.3(preact@10.23.2) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + uuid: 8.3.2 + optionalDependencies: + nodemailer: 6.9.16 + next-goatcounter@1.0.5(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: next: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8) @@ -4474,18 +4582,19 @@ snapshots: node-addon-api@5.1.0: {} - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - nodemailer@6.9.16: {} + nodemailer@6.9.16: + optional: true normalize-path@3.0.0: {} nprogress@0.2.0: {} + oauth@0.9.15: {} + object-assign@4.1.1: {} + object-hash@2.2.0: {} + object-inspect@1.13.2: {} object-is@1.1.6: @@ -4527,10 +4636,19 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + oidc-token-hash@5.0.3: {} + once@1.4.0: dependencies: wrappy: 1.0.2 + openid-client@5.7.1: + dependencies: + jose: 4.15.9 + lru-cache: 6.0.0 + object-hash: 2.2.0 + oidc-token-hash: 5.0.3 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4561,8 +4679,6 @@ snapshots: p-timeout@6.1.2: {} - pako@2.1.0: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4584,10 +4700,6 @@ snapshots: picomatch@2.3.1: {} - pkce-challenge@3.1.0: - dependencies: - crypto-js: 4.2.0 - plyr@3.7.8: dependencies: core-js: 3.38.1 @@ -4612,6 +4724,11 @@ snapshots: picocolors: 1.1.0 source-map-js: 1.2.0 + preact-render-to-string@5.2.3(preact@10.23.2): + dependencies: + preact: 10.23.2 + pretty-format: 3.8.0 + preact@10.23.2: {} prebuild-install@7.1.2: @@ -4631,13 +4748,7 @@ snapshots: prelude-ls@1.2.1: {} - prism-react-renderer@2.4.0(react@18.3.1): - dependencies: - '@types/prismjs': 1.26.4 - clsx: 2.1.1 - react: 18.3.1 - - process@0.11.10: {} + pretty-format@3.8.0: {} prop-types@15.8.1: dependencies: @@ -4647,8 +4758,6 @@ snapshots: property-expr@2.0.6: {} - proxy-from-env@1.1.0: {} - pump@3.0.0: dependencies: end-of-stream: 1.4.4 @@ -4656,14 +4765,10 @@ snapshots: punycode@2.3.1: {} - qr.js@0.0.0: {} - qs@6.13.0: dependencies: side-channel: 1.0.6 - querystringify@2.2.0: {} - queue-microtask@1.2.3: {} quick-lru@5.1.1: {} @@ -4699,12 +4804,6 @@ snapshots: dependencies: react: 18.3.1 - react-qr-code@2.0.15(react@18.3.1): - dependencies: - prop-types: 15.8.1 - qr.js: 0.0.0 - react: 18.3.1 - react-toastify@10.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: clsx: 2.1.1 @@ -4735,6 +4834,8 @@ snapshots: globalthis: 1.0.4 which-builtin-type: 1.1.4 + regenerator-runtime@0.14.1: {} + regexp.prototype.flags@1.5.2: dependencies: call-bind: 1.0.7 @@ -4742,8 +4843,6 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 - requires-port@1.0.0: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -4795,8 +4894,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - scmp@2.1.0: {} - section-matter@1.0.0: dependencies: extend-shallow: 2.0.1 @@ -4806,8 +4903,6 @@ snapshots: semver@7.6.3: {} - set-cookie-parser@2.7.1: {} - set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -5003,49 +5098,6 @@ snapshots: stylis@4.3.2: {} - supertokens-auth-react@0.48.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supertokens-web-js@0.14.0): - dependencies: - intl-tel-input: 17.0.21 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-qr-code: 2.0.15(react@18.3.1) - supertokens-js-override: 0.0.4 - supertokens-web-js: 0.14.0 - - supertokens-js-override@0.0.4: {} - - supertokens-node@21.0.0: - dependencies: - buffer: 6.0.3 - content-type: 1.0.5 - cookie: 0.7.2 - cross-fetch: 3.1.8 - debug: 4.3.6 - jose: 4.15.9 - libphonenumber-js: 1.11.12 - nodemailer: 6.9.16 - pako: 2.1.0 - pkce-challenge: 3.1.0 - process: 0.11.10 - set-cookie-parser: 2.7.1 - supertokens-js-override: 0.0.4 - tldts: 6.1.58 - twilio: 4.23.0(debug@4.3.6) - transitivePeerDependencies: - - encoding - - supports-color - - supertokens-web-js@0.14.0: - dependencies: - supertokens-js-override: 0.0.4 - supertokens-website: 20.1.5 - - supertokens-website@20.1.5: - dependencies: - browser-tabs-lock: 1.3.0 - supertokens-js-override: 0.0.4 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -5073,20 +5125,12 @@ snapshots: tiny-case@1.0.3: {} - tldts-core@6.1.58: {} - - tldts@6.1.58: - dependencies: - tldts-core: 6.1.58 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 toposort@2.0.2: {} - tr46@0.0.3: {} - ts-api-utils@1.3.0(typescript@5.5.4): dependencies: typescript: 5.5.4 @@ -5106,20 +5150,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - twilio@4.23.0(debug@4.3.6): - dependencies: - axios: 1.7.7(debug@4.3.6) - dayjs: 1.11.13 - https-proxy-agent: 5.0.1 - jsonwebtoken: 9.0.2 - qs: 6.13.0 - scmp: 2.1.0 - url-parse: 1.5.10 - xmlbuilder: 13.0.2 - transitivePeerDependencies: - - debug - - supports-color - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -5175,11 +5205,6 @@ snapshots: dependencies: punycode: 2.3.1 - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - url-polyfill@1.1.12: {} use-sync-external-store@1.2.2(react@18.3.1): @@ -5188,15 +5213,10 @@ snapshots: util-deprecate@1.0.2: {} + uuid@8.3.2: {} + vary@1.1.2: {} - webidl-conversions@3.0.1: {} - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -5261,7 +5281,7 @@ snapshots: dependencies: sax: 1.4.1 - xmlbuilder@13.0.2: {} + yallist@4.0.0: {} yocto-queue@0.1.0: {} diff --git a/services/next/test-jwe-decrypt.js b/services/next/test-jwe-decrypt.js new file mode 100644 index 0000000..58d8ce8 --- /dev/null +++ b/services/next/test-jwe-decrypt.js @@ -0,0 +1,18 @@ +// const { jwtDecrypt } = require('jose'); +// const dotenv = require('dotenv') +// dotenv.config({ +// path: '../../.env.development' +// }) + +// async function decryptJWE(jwe, secret) { +// const key = new TextEncoder().encode(secret); // Encode the secret to Uint8Array +// const { payload, protectedHeader } = await jwtDecrypt(jwe, key); +// console.log('Decrypted payload:', new TextDecoder().decode(payload)); +// console.log('Header:', protectedHeader); +// } + +// const jwe = '375hO4PPr4GCghunRf52r7tlG7HAFLanZ7D5blc0QDamW3nXp_terShYROf7bpPak8N1k29cF_e_hdDZNeI42ZaFQInhjCOBLrYDpcxbvjALNhkwFNPz_wj1IPqDehjMz1vowdN9LdrwJup9a8Q.HbClJBjeMIyzT3vrwRdM0Q'; + + +// const secret = process.env.NEXTAUTH_SECRET; +// decryptJWE(jwe, secret).catch(console.error); diff --git a/services/next/tsconfig.json b/services/next/tsconfig.json index 05350a8..a6e306c 100644 --- a/services/next/tsconfig.json +++ b/services/next/tsconfig.json @@ -21,7 +21,7 @@ "@/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/components/auth.tsx.old"], "exclude": ["node_modules"] } \ No newline at end of file diff --git a/services/strapi/src/api/tweet/content-types/tweet/lifecycles.js b/services/strapi/src/api/tweet/content-types/tweet/lifecycles.js index b92154a..e8cc61f 100644 --- a/services/strapi/src/api/tweet/content-types/tweet/lifecycles.js +++ b/services/strapi/src/api/tweet/content-types/tweet/lifecycles.js @@ -10,7 +10,7 @@ const cbAlternativeUrls = [ /** * Returns true if the tweet contains a chaturbate.com link - * + * * @param {Object} tweet * @returns {Boolean} */ @@ -48,7 +48,7 @@ const containsFanslyInviteLink = (tweet) => { const deriveTitle = (text) => { - // greetz https://www.urlregex.com/ + // @see https://www.urlregex.com/ const urlRegex = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[\-;:&=\+\$,\w]+@)?[A-Za-z0-9\.\-]+|(?:www\.|[\-;:&=\+\$,\w]+@)[A-Za-z0-9\.\-]+)((?:\/[\+~%\/\.\w\-_]*)?\??(?:[\-\+=&;%@\.\w_]*)#?(?:[\.\!\/\\\w]*))?)/g; let title = text .replace(urlRegex, '') // remove urls @@ -69,7 +69,7 @@ module.exports = { const id = event.result.id; console.log(`>>> tweet afterCreate id=${id}`); const { data } = event.params; - + console.log(data); // IF this tweet was a fansly or chaturbate invite, create & associate Stream @@ -133,4 +133,4 @@ module.exports = { } -} \ No newline at end of file +}