diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12b786da..fad934d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,6 @@ on: - web - docs - console - - console-fb dryrun: description: Build with dey-run. type: boolean diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml index 227d3e8f..0c81a12d 100644 --- a/.github/workflows/docs-deploy.yml +++ b/.github/workflows/docs-deploy.yml @@ -10,6 +10,15 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: + inputs: + ghSiteUrl: + description: GitHub Pages URL. + default: https://xmlking.github.io + required: false + ghBasePath: + description: GitHub Pages Base. + default: '' + required: false permissions: contents: read @@ -23,12 +32,14 @@ concurrency: env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} - GH_SITE_URL: ${{ secrets.GH_SITE_URL || 'https://xmlking.github.io' }} - GH_BASE_PATH: ${{ secrets.GH_BASE_PATH || '/spectacular' }} + jobs: build: runs-on: ubuntu-latest + env: + GH_SITE_URL: ${{ secrets.GH_SITE_URL || 'https://xmlking.github.io' }} + GH_BASE_PATH: ${{ secrets.GH_BASE_PATH || '/spectacular' }} steps: - name: Checkout 🛫 uses: actions/checkout@v4 @@ -53,6 +64,6 @@ jobs: name: github-pages url: ${{ steps.deployment.outputs.page_url }} steps: - - name: Deploy to GitHub Pages 💖 + - name: 💖 Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5fe75e0f..834d1c82 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -15,6 +15,7 @@ "mikestead.dotenv", "alberto-varela.monorepo-focus-workspace", "biomejs.biome", + "ms-azuretools.vscode-docker", // inlang (i18n) "inlang.vs-code-extension", // markdown @@ -25,13 +26,6 @@ "bpruitt-goddard.mermaid-markdown-syntax-highlighting", // Rust "tamasfe.even-better-toml", - // GolLang - "golang.go", - "paulvarache.vscode-taskfile", - "bufbuild.vscode-buf", - "ms-azuretools.vscode-docker", - "zxh404.vscode-proto3", - "xaver.clang-format", // WebApps "bradlc.vscode-tailwindcss", "codezombiech.gitignore", diff --git a/apps/console/src/app.d.ts b/apps/console/src/app.d.ts index ec7bee59..17cdb491 100644 --- a/apps/console/src/app.d.ts +++ b/apps/console/src/app.d.ts @@ -38,6 +38,7 @@ declare global { logResult?: boolean | null; backendToken?: string | null; useRole?: string | null; + adminSecret?: string | null; } } diff --git a/apps/console/src/lib/graphql/client.ts b/apps/console/src/lib/graphql/client.ts index 2fcf6751..a0309970 100644 --- a/apps/console/src/lib/graphql/client.ts +++ b/apps/console/src/lib/graphql/client.ts @@ -8,7 +8,7 @@ import { Logger, hasErrorMessage, hasErrorTypes, isErrorType } from '@spectacula import { error, redirect } from '@sveltejs/kit'; import { createClient as createWSClient } from 'graphql-ws'; -const url = env.PUBLIC_GRAPHQL_ENDPOINT!; +const url = env.PUBLIC_GRAPHQL_ENDPOINT; const log = new Logger('houdini.client'); @@ -48,11 +48,13 @@ export default new HoudiniClient({ const accessToken = session?.accessToken; const backendToken = metadata?.backendToken; const useRole = metadata?.useRole; + const adminSecret = metadata?.adminSecret; return { headers: { ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), ...(useRole ? { 'x-hasura-role': useRole } : {}), + ...(adminSecret ? { 'X-Hasura-Admin-Secret': adminSecret } : {}), ...(backendToken ? { backendToken } : {}), }, }; diff --git a/apps/console/src/lib/server/middleware/houdini.ts b/apps/console/src/lib/server/middleware/houdini.ts index 3cffeb57..34849dc9 100644 --- a/apps/console/src/lib/server/middleware/houdini.ts +++ b/apps/console/src/lib/server/middleware/houdini.ts @@ -1,3 +1,4 @@ +import { building } from '$app/environment'; import { setSession } from '$houdini'; import { Logger } from '@spectacular/utils'; import type { Handle } from '@sveltejs/kit'; @@ -6,7 +7,7 @@ const log = new Logger('server:middleware:houdini'); export const houdini = (async ({ event, resolve }) => { // skip auth logic on build to prevent infinite redirection in production mode // FIXME: https://github.com/nextauthjs/next-auth/discussions/6186 - return await resolve(event); + if (building) return await resolve(event); const { locals } = event; const accessToken = locals.nhost.auth.getAccessToken(); diff --git a/apps/console/src/lib/server/utils/getOrgs.ts b/apps/console/src/lib/server/utils/getOrgs.ts index d04ff998..0324861b 100644 --- a/apps/console/src/lib/server/utils/getOrgs.ts +++ b/apps/console/src/lib/server/utils/getOrgs.ts @@ -2,11 +2,12 @@ import { env as secrets } from '$env/dynamic/private'; import { ListOrganizationsStore } from '$houdini'; import type { ListOrganizations$result } from '$houdini'; import type { NhostClient } from '@nhost/nhost-js'; -import { Logger } from '@spectacular/utils'; +import { Logger, asArray } from '@spectacular/utils'; import type { GraphQLError } from 'graphql'; +import type { ServerLoadEvent } from '@sveltejs/kit'; /** - * prevent calling server-side only functions in client-side code, we keep them under `lib/server` + * To prevent calling server-side only functions in client-side code, we keep them under `lib/server` * IMPORTENT: if new `org` is added to database, call `resetOrgs()` */ @@ -18,7 +19,7 @@ const ORGS_QUERY = listOrganizationsStore.artifact.raw; const ORGS_HASH = listOrganizationsStore.artifact.hash; const cache = new Map(); -export async function getOrgs(nhost: NhostClient) { +export async function getOrgsNH(nhost: NhostClient) { if (!cache.has(ORGS_HASH)) { log.info('cache miss, fetching org data'); const { data, error } = await nhost.graphql.request( @@ -31,7 +32,24 @@ export async function getOrgs(nhost: NhostClient) { }, ); if (error) { - return { errors: error as GraphQLError[], data: null }; + return { errors: asArray(error), data: null }; + } + cache.set(ORGS_HASH, data); + } + return { errors: null, data: cache.get(ORGS_HASH) as ListOrganizations$result }; +} + +export async function getOrgs(event: ServerLoadEvent) { + if (!cache.has(ORGS_HASH)) { + log.info('cache miss, fetching org data'); + const { errors, data } = await listOrganizationsStore.fetch( { + event, + blocking: true, + metadata: { logResult: true, useRole: 'admin', adminSecret: ADMIN_SECRET }, + variables: {} + }); + if (errors) { + return { errors, data: null }; } cache.set(ORGS_HASH, data); } diff --git a/apps/console/src/routes/(auth)/signup/+page.server.ts b/apps/console/src/routes/(auth)/signup/+page.server.ts index cbeba828..be0458c1 100644 --- a/apps/console/src/routes/(auth)/signup/+page.server.ts +++ b/apps/console/src/routes/(auth)/signup/+page.server.ts @@ -2,7 +2,7 @@ import { i18n } from '$lib/i18n'; import { setNhostSessionInCookies } from '$lib/nhost'; import { signUpSchema } from '$lib/schema/user'; import { limiter } from '$lib/server/limiter/limiter'; -import { getOrgs } from '$lib/server/utils/getOrgs'; +import { getOrgsNH } from '$lib/server/utils/getOrgs'; import { Logger, sleep } from '@spectacular/utils'; import { error, fail } from '@sveltejs/kit'; import type { GraphQLError } from 'graphql'; @@ -26,7 +26,7 @@ export const load = async (event) => { if (session) redirectWithFlash(302, i18n.resolveRoute('/dashboard')); const form = await superValidate(zod(signUpSchema)); // fetch orgs and render errors if backend throw error. - const { errors, data } = await getOrgs(nhost); + const { errors, data } = await getOrgsNH(nhost); if (errors) { for (const error of errors) { log.error('list orgs api error', error); diff --git a/apps/docs/src/components/Card.astro b/apps/docs/src/components/card.astro similarity index 100% rename from apps/docs/src/components/Card.astro rename to apps/docs/src/components/card.astro diff --git a/packages/utils/src/array.ts b/packages/utils/src/array.ts index 9dc880b1..e891cba8 100644 --- a/packages/utils/src/array.ts +++ b/packages/utils/src/array.ts @@ -6,3 +6,9 @@ */ export const groupBy = , K extends keyof T>(arr: T[], key: K): Record => arr.reduce((acc, item) => ((acc[item[key]] = [...(acc[item[key]] || []), item]), acc), {} as Record); + +export function asArray(x: T | T[]): T[]; +export function asArray(x: T | readonly T[]): readonly T[]; +export function asArray(x: T | T[]): T[] { + return Array.isArray(x) ? x : [x]; +} diff --git a/packages/utils/src/strings.ts b/packages/utils/src/strings.ts index 74b36862..2ba29b64 100644 --- a/packages/utils/src/strings.ts +++ b/packages/utils/src/strings.ts @@ -1,3 +1,11 @@ export function startsWith(str: string, substrs: string[]) { return substrs.some((substr) => str.startsWith(substr)); } + +export function camelize(str: string) { + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) { + return index === 0 ? word.toLowerCase() : word.toUpperCase(); + }) + .replace(/\s+/g, ''); +}