From 2622392f1496ae525e6fb99e32f5252e3ca01362 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:01:14 +0000 Subject: [PATCH 01/11] feat: create libs/server-platform and move server composition from apps/api Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/api/package.json | 10 +--- apps/api/src/app/app.ts | 25 ++------- apps/api/tsconfig.app.json | 3 + apps/api/tsconfig.spec.json | 7 ++- libs/server-platform/package.json | 56 +++++++++++++++++++ libs/server-platform/src/index.ts | 2 + .../src/lib/createServerPlatform.ts | 35 ++++++++++++ .../src/lib}/plugins/database/index.ts | 8 +-- .../src/lib}/plugins/database/init.spec.ts | 2 +- .../src/lib}/plugins/database/init.ts | 4 +- .../src/lib}/plugins/database/pageStore.ts | 2 +- .../src/lib}/plugins/database/store.ts | 4 +- .../src/lib}/plugins/database/types.ts | 0 .../plugins/database/userSettingsStore.ts | 2 +- .../src/lib}/plugins/oauth/index.ts | 8 +-- .../src/lib}/plugins/oauth/oauthConfigs.ts | 0 .../src/lib}/plugins/oauth/pkce.ts | 0 .../src/lib}/plugins/oauth/providers.ts | 2 +- .../src/lib}/plugins/oauth/service.ts | 4 +- .../src/lib}/plugins/oauth/types.ts | 0 .../src/lib}/plugins/oauth/userInfo.ts | 0 .../src/lib}/plugins/sensible.ts | 0 .../src/lib}/plugins/session/checkSession.ts | 4 +- .../src/lib}/plugins/session/index.spec.ts | 4 +- .../src/lib}/plugins/session/index.ts | 10 ++-- .../src/lib}/plugins/session/mutateSession.ts | 6 +- .../session/requireAuthenticatedSession.ts | 0 .../src/lib}/plugins/session/types.ts | 2 +- .../src/lib}/routes/auth.spec.ts | 8 +-- .../server-platform/src/lib}/routes/auth.ts | 0 .../src/lib}/routes/oauth.spec.ts | 10 ++-- .../server-platform/src/lib}/routes/oauth.ts | 4 +- .../src/lib}/routes/pages.spec.ts | 8 +-- .../server-platform/src/lib}/routes/pages.ts | 0 .../src/lib}/routes/root.spec.ts | 0 .../server-platform/src/lib}/routes/root.ts | 0 .../server-platform/src/lib}/routes/ssr.ts | 0 .../src/lib}/routes/user-settings.spec.ts | 8 +-- .../src/lib}/routes/user-settings.ts | 0 libs/server-platform/tsconfig.json | 9 +++ libs/server-platform/tsconfig.lib.json | 17 ++++++ libs/server-platform/tsconfig.spec.json | 12 ++++ libs/server-platform/vitest.config.mts | 9 +++ package-lock.json | 30 +++++++--- 44 files changed, 228 insertions(+), 87 deletions(-) create mode 100644 libs/server-platform/package.json create mode 100644 libs/server-platform/src/index.ts create mode 100644 libs/server-platform/src/lib/createServerPlatform.ts rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/index.ts (87%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/init.spec.ts (99%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/init.ts (99%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/pageStore.ts (97%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/store.ts (99%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/types.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/database/userSettingsStore.ts (94%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/index.ts (73%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/oauthConfigs.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/pkce.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/providers.ts (98%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/service.ts (99%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/types.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/oauth/userInfo.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/sensible.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/session/checkSession.ts (91%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/session/index.spec.ts (97%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/session/index.ts (89%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/session/mutateSession.ts (91%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/session/requireAuthenticatedSession.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/plugins/session/types.ts (91%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/auth.spec.ts (96%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/auth.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/oauth.spec.ts (97%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/oauth.ts (99%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/pages.spec.ts (94%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/pages.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/root.spec.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/root.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/ssr.ts (100%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/user-settings.spec.ts (93%) rename {apps/api/src/app => libs/server-platform/src/lib}/routes/user-settings.ts (100%) create mode 100644 libs/server-platform/tsconfig.json create mode 100644 libs/server-platform/tsconfig.lib.json create mode 100644 libs/server-platform/tsconfig.spec.json create mode 100644 libs/server-platform/vitest.config.mts diff --git a/apps/api/package.json b/apps/api/package.json index f21d0c2..6f4f027 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -122,14 +122,8 @@ } }, "dependencies": { - "@fastify/autoload": "~6.0.3", - "@fastify/cookie": "^11.0.2", - "@fastify/middie": "^9.0.3", - "@fastify/sensible": "~6.0.2", - "@fastify/static": "^8.3.0", - "better-sqlite3": "^12.9.0", + "@rod-manager/server-platform": "0.0.1", "dotenv": "^16.4.7", - "fastify": "5.8.3", - "fastify-plugin": "~5.0.1" + "fastify": "5.8.3" } } diff --git a/apps/api/src/app/app.ts b/apps/api/src/app/app.ts index 7d1edec..d7bc8ff 100644 --- a/apps/api/src/app/app.ts +++ b/apps/api/src/app/app.ts @@ -1,28 +1,13 @@ -import * as path from 'path'; import type { FastifyInstance } from 'fastify'; -import AutoLoad from '@fastify/autoload'; +import { createServerPlatform } from '@rod-manager/server-platform'; export interface AppOptions { - logLevel?: 'string'; + logLevel?: string; } +/** Registers all server platform plugins and routes on the given Fastify instance. */ export function app(fastify: FastifyInstance, opts: AppOptions) { - // Place here your custom code! - - // Do not touch the following lines - - // This loads all plugins defined in plugins - // those should be support plugins that are reused - // through your application - fastify.register(AutoLoad, { - dir: path.join(__dirname, 'plugins'), - options: { ...opts }, - }); - - // This loads all plugins defined in routes - // define your routes in one of these - fastify.register(AutoLoad, { - dir: path.join(__dirname, 'routes'), - options: { ...opts }, + fastify.register(async (instance) => { + await createServerPlatform(instance, opts); }); } diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json index 6424e1b..71a9bd5 100644 --- a/apps/api/tsconfig.app.json +++ b/apps/api/tsconfig.app.json @@ -17,6 +17,9 @@ "references": [ { "path": "../../libs/shared/tsconfig.lib.json" + }, + { + "path": "../../libs/server-platform/tsconfig.lib.json" } ] } diff --git a/apps/api/tsconfig.spec.json b/apps/api/tsconfig.spec.json index 8afa0f3..85da96e 100644 --- a/apps/api/tsconfig.spec.json +++ b/apps/api/tsconfig.spec.json @@ -8,5 +8,10 @@ "tsBuildInfoFile": "dist/tsconfig.spec.tsbuildinfo" }, "include": ["src/**/*.ts"], - "exclude": ["eslint.config.js", "eslint.config.cjs", "eslint.config.mjs"] + "exclude": ["eslint.config.js", "eslint.config.cjs", "eslint.config.mjs"], + "references": [ + { + "path": "../../libs/server-platform/tsconfig.lib.json" + } + ] } diff --git a/libs/server-platform/package.json b/libs/server-platform/package.json new file mode 100644 index 0000000..ae530b6 --- /dev/null +++ b/libs/server-platform/package.json @@ -0,0 +1,56 @@ +{ + "name": "@rod-manager/server-platform", + "version": "0.0.1", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@rod-manager/source": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": ["dist", "!**/*.tsbuildinfo"], + "dependencies": { + "@fastify/autoload": "~6.0.3", + "@fastify/cookie": "^11.0.2", + "@fastify/middie": "^9.0.3", + "@fastify/sensible": "~6.0.2", + "@fastify/static": "^8.3.0", + "@rod-manager/shared": "0.0.1", + "better-sqlite3": "^12.9.0", + "dotenv": "^16.4.7", + "fastify": "5.8.3", + "fastify-plugin": "~5.0.1" + }, + "nx": { + "targets": { + "typecheck": { + "executor": "nx:run-commands", + "options": { + "cwd": "libs/server-platform", + "command": "node ../../node_modules/typescript/bin/tsc --noEmit -p tsconfig.lib.json && node ../../node_modules/typescript/bin/tsc --noEmit -p tsconfig.spec.json" + } + }, + "build": { + "executor": "nx:run-commands", + "outputs": ["{projectRoot}/dist"], + "options": { + "cwd": "libs/server-platform", + "command": "node ../../node_modules/typescript/bin/tsc --build tsconfig.lib.json" + } + }, + "test": { + "executor": "nx:run-commands", + "options": { + "cwd": "libs/server-platform", + "command": "node ../../node_modules/vitest/vitest.mjs run --config vitest.config.mts" + } + } + } + } +} diff --git a/libs/server-platform/src/index.ts b/libs/server-platform/src/index.ts new file mode 100644 index 0000000..20626cb --- /dev/null +++ b/libs/server-platform/src/index.ts @@ -0,0 +1,2 @@ +export { createServerPlatform } from './lib/createServerPlatform.js'; +export type { ServerPlatformOptions } from './lib/createServerPlatform.js'; diff --git a/libs/server-platform/src/lib/createServerPlatform.ts b/libs/server-platform/src/lib/createServerPlatform.ts new file mode 100644 index 0000000..90b4c73 --- /dev/null +++ b/libs/server-platform/src/lib/createServerPlatform.ts @@ -0,0 +1,35 @@ +import type { FastifyInstance } from 'fastify'; +import databasePlugin from './plugins/database/index.js'; +import sessionPlugin from './plugins/session/index.js'; +import oauthPlugin from './plugins/oauth/index.js'; +import sensiblePlugin from './plugins/sensible.js'; +import authRoutes from './routes/auth.js'; +import oauthRoutes from './routes/oauth.js'; +import pagesRoutes from './routes/pages.js'; +import rootRoute from './routes/root.js'; +import ssrRoute from './routes/ssr.js'; +import userSettingsRoutes from './routes/user-settings.js'; + +export interface ServerPlatformOptions { + logLevel?: string; +} + +/** Registers all core plugins and routes on the given Fastify instance. */ +export async function createServerPlatform( + fastify: FastifyInstance, + opts: ServerPlatformOptions = {}, +): Promise { + // Core plugins + await fastify.register(sensiblePlugin); + await fastify.register(databasePlugin); + await fastify.register(sessionPlugin); + await fastify.register(oauthPlugin); + + // Core routes + fastify.register(authRoutes); + fastify.register(oauthRoutes); + fastify.register(pagesRoutes); + fastify.register(rootRoute); + fastify.register(userSettingsRoutes); + fastify.register(ssrRoute); +} diff --git a/apps/api/src/app/plugins/database/index.ts b/libs/server-platform/src/lib/plugins/database/index.ts similarity index 87% rename from apps/api/src/app/plugins/database/index.ts rename to libs/server-platform/src/lib/plugins/database/index.ts index 0fe93b6..5121dd9 100644 --- a/apps/api/src/app/plugins/database/index.ts +++ b/libs/server-platform/src/lib/plugins/database/index.ts @@ -12,10 +12,10 @@ import { seedInitialUser, shouldSeedInitialUser, ensureAdministratorExists, -} from './init'; -import { createStore } from './store'; -import { createUserSettingsStore } from './userSettingsStore'; -import { createPageStore } from './pageStore'; +} from './init.js'; +import { createStore } from './store.js'; +import { createUserSettingsStore } from './userSettingsStore.js'; +import { createPageStore } from './pageStore.js'; export type { AuthStore, diff --git a/apps/api/src/app/plugins/database/init.spec.ts b/libs/server-platform/src/lib/plugins/database/init.spec.ts similarity index 99% rename from apps/api/src/app/plugins/database/init.spec.ts rename to libs/server-platform/src/lib/plugins/database/init.spec.ts index 1a1d6ce..74b30bc 100644 --- a/apps/api/src/app/plugins/database/init.spec.ts +++ b/libs/server-platform/src/lib/plugins/database/init.spec.ts @@ -1,6 +1,6 @@ import Database from 'better-sqlite3'; import { afterEach, describe, expect, it } from 'vitest'; -import { ensurePageSlugValidationRules, initializeSchema } from './init'; +import { ensurePageSlugValidationRules, initializeSchema } from './init.js'; const databases: Database.Database[] = []; diff --git a/apps/api/src/app/plugins/database/init.ts b/libs/server-platform/src/lib/plugins/database/init.ts similarity index 99% rename from apps/api/src/app/plugins/database/init.ts rename to libs/server-platform/src/lib/plugins/database/init.ts index c53ae84..0b9138f 100644 --- a/apps/api/src/app/plugins/database/init.ts +++ b/libs/server-platform/src/lib/plugins/database/init.ts @@ -2,8 +2,8 @@ import { mkdirSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import type { UserRole } from '@rod-manager/shared'; import type Database from 'better-sqlite3'; -import type { CountRow } from './types'; -import { hashPassword } from './store'; +import type { CountRow } from './types.js'; +import { hashPassword } from './store.js'; const RESERVED_PAGE_SLUGS = ['account', 'register', 'pages', 'auth', 'api']; const RESERVED_PAGE_SLUGS_SQL = RESERVED_PAGE_SLUGS.map( diff --git a/apps/api/src/app/plugins/database/pageStore.ts b/libs/server-platform/src/lib/plugins/database/pageStore.ts similarity index 97% rename from apps/api/src/app/plugins/database/pageStore.ts rename to libs/server-platform/src/lib/plugins/database/pageStore.ts index 17983be..5549e60 100644 --- a/apps/api/src/app/plugins/database/pageStore.ts +++ b/libs/server-platform/src/lib/plugins/database/pageStore.ts @@ -5,7 +5,7 @@ import type { ContentPageSummary, ContentPageSummaryRow, PageStore, -} from './types'; +} from './types.js'; export function createPageStore(db: Database.Database): PageStore { const listPagesStatement = db.prepare<[], ContentPageSummaryRow>( diff --git a/apps/api/src/app/plugins/database/store.ts b/libs/server-platform/src/lib/plugins/database/store.ts similarity index 99% rename from apps/api/src/app/plugins/database/store.ts rename to libs/server-platform/src/lib/plugins/database/store.ts index 07a7e15..ae9c29a 100644 --- a/apps/api/src/app/plugins/database/store.ts +++ b/libs/server-platform/src/lib/plugins/database/store.ts @@ -13,8 +13,8 @@ import type { OAuthProviderRow, SessionRow, UserRow, -} from './types'; -import { createSessionExpiration } from './types'; +} from './types.js'; +import { createSessionExpiration } from './types.js'; export type { AuthStore }; diff --git a/apps/api/src/app/plugins/database/types.ts b/libs/server-platform/src/lib/plugins/database/types.ts similarity index 100% rename from apps/api/src/app/plugins/database/types.ts rename to libs/server-platform/src/lib/plugins/database/types.ts diff --git a/apps/api/src/app/plugins/database/userSettingsStore.ts b/libs/server-platform/src/lib/plugins/database/userSettingsStore.ts similarity index 94% rename from apps/api/src/app/plugins/database/userSettingsStore.ts rename to libs/server-platform/src/lib/plugins/database/userSettingsStore.ts index 5fba527..bb2ce20 100644 --- a/apps/api/src/app/plugins/database/userSettingsStore.ts +++ b/libs/server-platform/src/lib/plugins/database/userSettingsStore.ts @@ -1,6 +1,6 @@ import type { UserLanguage } from '@rod-manager/shared'; import type Database from 'better-sqlite3'; -import type { UserSettingsStore } from './types'; +import type { UserSettingsStore } from './types.js'; export function createUserSettingsStore( db: Database.Database, diff --git a/apps/api/src/app/plugins/oauth/index.ts b/libs/server-platform/src/lib/plugins/oauth/index.ts similarity index 73% rename from apps/api/src/app/plugins/oauth/index.ts rename to libs/server-platform/src/lib/plugins/oauth/index.ts index 8283814..aebd618 100644 --- a/apps/api/src/app/plugins/oauth/index.ts +++ b/libs/server-platform/src/lib/plugins/oauth/index.ts @@ -1,10 +1,10 @@ import type { FastifyInstance } from 'fastify'; import fp from 'fastify-plugin'; -import { createOAuthConfigs } from './providers'; -import { generatePKCE } from './pkce'; -import { createOAuthService } from './service'; +import { createOAuthConfigs } from './providers.js'; +import { generatePKCE } from './pkce.js'; +import { createOAuthService } from './service.js'; -export type { OAuthConfig, OAuthService, OAuthState } from './types'; +export type { OAuthConfig, OAuthService, OAuthState } from './types.js'; export { generatePKCE }; /** diff --git a/apps/api/src/app/plugins/oauth/oauthConfigs.ts b/libs/server-platform/src/lib/plugins/oauth/oauthConfigs.ts similarity index 100% rename from apps/api/src/app/plugins/oauth/oauthConfigs.ts rename to libs/server-platform/src/lib/plugins/oauth/oauthConfigs.ts diff --git a/apps/api/src/app/plugins/oauth/pkce.ts b/libs/server-platform/src/lib/plugins/oauth/pkce.ts similarity index 100% rename from apps/api/src/app/plugins/oauth/pkce.ts rename to libs/server-platform/src/lib/plugins/oauth/pkce.ts diff --git a/apps/api/src/app/plugins/oauth/providers.ts b/libs/server-platform/src/lib/plugins/oauth/providers.ts similarity index 98% rename from apps/api/src/app/plugins/oauth/providers.ts rename to libs/server-platform/src/lib/plugins/oauth/providers.ts index 53a8bcb..9030765 100644 --- a/apps/api/src/app/plugins/oauth/providers.ts +++ b/libs/server-platform/src/lib/plugins/oauth/providers.ts @@ -1,5 +1,5 @@ import type { OAuthProviderType } from '@rod-manager/shared'; -import type { OAuthConfig } from './types'; +import type { OAuthConfig } from './types.js'; /** * Build OAuth provider configuration map from environment variables. diff --git a/apps/api/src/app/plugins/oauth/service.ts b/libs/server-platform/src/lib/plugins/oauth/service.ts similarity index 99% rename from apps/api/src/app/plugins/oauth/service.ts rename to libs/server-platform/src/lib/plugins/oauth/service.ts index ebbdb5c..10760e4 100644 --- a/apps/api/src/app/plugins/oauth/service.ts +++ b/libs/server-platform/src/lib/plugins/oauth/service.ts @@ -1,10 +1,10 @@ import type { OAuthProviderType, OAuthUserInfo } from '@rod-manager/shared'; -import type { OAuthConfig, OAuthService, ProviderTokenResponse } from './types'; +import type { OAuthConfig, OAuthService, ProviderTokenResponse } from './types.js'; import { buildOAuthUserInfo, decodeJwtPayload, normalizeValue, -} from './userInfo'; +} from './userInfo.js'; /** * Create OAuth service instance backed by provider configurations. diff --git a/apps/api/src/app/plugins/oauth/types.ts b/libs/server-platform/src/lib/plugins/oauth/types.ts similarity index 100% rename from apps/api/src/app/plugins/oauth/types.ts rename to libs/server-platform/src/lib/plugins/oauth/types.ts diff --git a/apps/api/src/app/plugins/oauth/userInfo.ts b/libs/server-platform/src/lib/plugins/oauth/userInfo.ts similarity index 100% rename from apps/api/src/app/plugins/oauth/userInfo.ts rename to libs/server-platform/src/lib/plugins/oauth/userInfo.ts diff --git a/apps/api/src/app/plugins/sensible.ts b/libs/server-platform/src/lib/plugins/sensible.ts similarity index 100% rename from apps/api/src/app/plugins/sensible.ts rename to libs/server-platform/src/lib/plugins/sensible.ts diff --git a/apps/api/src/app/plugins/session/checkSession.ts b/libs/server-platform/src/lib/plugins/session/checkSession.ts similarity index 91% rename from apps/api/src/app/plugins/session/checkSession.ts rename to libs/server-platform/src/lib/plugins/session/checkSession.ts index c85170b..a02406b 100644 --- a/apps/api/src/app/plugins/session/checkSession.ts +++ b/libs/server-platform/src/lib/plugins/session/checkSession.ts @@ -1,6 +1,6 @@ import type { FastifyRequest } from 'fastify'; -import type { AuthStore, AuthStoreSession } from '../database'; -import { SESSION_COOKIE_NAME } from './types'; +import type { AuthStore, AuthStoreSession } from '../database/index.js'; +import { SESSION_COOKIE_NAME } from './types.js'; /** * Reads the session token from request cookies. diff --git a/apps/api/src/app/plugins/session/index.spec.ts b/libs/server-platform/src/lib/plugins/session/index.spec.ts similarity index 97% rename from apps/api/src/app/plugins/session/index.spec.ts rename to libs/server-platform/src/lib/plugins/session/index.spec.ts index e866ffa..6a3bc46 100644 --- a/apps/api/src/app/plugins/session/index.spec.ts +++ b/libs/server-platform/src/lib/plugins/session/index.spec.ts @@ -1,7 +1,7 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from './index'; -import databasePlugin from '../database'; +import sessionPlugin, { SESSION_COOKIE_NAME } from './index.js'; +import databasePlugin from '../database/index.js'; describe('session plugin', () => { beforeEach(() => { diff --git a/apps/api/src/app/plugins/session/index.ts b/libs/server-platform/src/lib/plugins/session/index.ts similarity index 89% rename from apps/api/src/app/plugins/session/index.ts rename to libs/server-platform/src/lib/plugins/session/index.ts index a2c2606..3c5cd91 100644 --- a/apps/api/src/app/plugins/session/index.ts +++ b/libs/server-platform/src/lib/plugins/session/index.ts @@ -1,19 +1,19 @@ import type { FastifyInstance } from 'fastify'; import fp from 'fastify-plugin'; import cookie from '@fastify/cookie'; -import './types'; +import './types.js'; import { createGetSessionDecorator, getSessionToken, hasSession, -} from './checkSession'; +} from './checkSession.js'; import { createRemoveSessionDecorator, createStartSessionDecorator, -} from './mutateSession'; -import { requireAuthenticatedSession } from './requireAuthenticatedSession'; +} from './mutateSession.js'; +import { requireAuthenticatedSession } from './requireAuthenticatedSession.js'; -export { SESSION_COOKIE_NAME } from './types'; +export { SESSION_COOKIE_NAME } from './types.js'; /** * Registers cookie parsing plus session helpers for request and reply lifecycle. diff --git a/apps/api/src/app/plugins/session/mutateSession.ts b/libs/server-platform/src/lib/plugins/session/mutateSession.ts similarity index 91% rename from apps/api/src/app/plugins/session/mutateSession.ts rename to libs/server-platform/src/lib/plugins/session/mutateSession.ts index 35d0019..931de24 100644 --- a/apps/api/src/app/plugins/session/mutateSession.ts +++ b/libs/server-platform/src/lib/plugins/session/mutateSession.ts @@ -1,7 +1,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; -import { SESSION_COOKIE_NAME } from './types'; -import type { AuthStore, AuthStoreSession } from '../database'; -import { resolveSessionFromRequest } from './checkSession'; +import { SESSION_COOKIE_NAME } from './types.js'; +import type { AuthStore, AuthStoreSession } from '../database/index.js'; +import { resolveSessionFromRequest } from './checkSession.js'; export const COOKIE_OPTIONS = { path: '/', diff --git a/apps/api/src/app/plugins/session/requireAuthenticatedSession.ts b/libs/server-platform/src/lib/plugins/session/requireAuthenticatedSession.ts similarity index 100% rename from apps/api/src/app/plugins/session/requireAuthenticatedSession.ts rename to libs/server-platform/src/lib/plugins/session/requireAuthenticatedSession.ts diff --git a/apps/api/src/app/plugins/session/types.ts b/libs/server-platform/src/lib/plugins/session/types.ts similarity index 91% rename from apps/api/src/app/plugins/session/types.ts rename to libs/server-platform/src/lib/plugins/session/types.ts index 6cbf6cc..f2a38bb 100644 --- a/apps/api/src/app/plugins/session/types.ts +++ b/libs/server-platform/src/lib/plugins/session/types.ts @@ -1,5 +1,5 @@ import type { preHandlerAsyncHookHandler } from 'fastify'; -import type { AuthStoreSession } from '../database'; +import type { AuthStoreSession } from '../database/index.js'; export type RequireAuthenticatedSessionHook = preHandlerAsyncHookHandler; diff --git a/apps/api/src/app/routes/auth.spec.ts b/libs/server-platform/src/lib/routes/auth.spec.ts similarity index 96% rename from apps/api/src/app/routes/auth.spec.ts rename to libs/server-platform/src/lib/routes/auth.spec.ts index ab3e152..b0761d6 100644 --- a/apps/api/src/app/routes/auth.spec.ts +++ b/libs/server-platform/src/lib/routes/auth.spec.ts @@ -1,10 +1,10 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import type { SessionResponse } from '@rod-manager/shared'; -import databasePlugin from '../plugins/database'; -import sessionPlugin from '../plugins/session'; -import authRoutes from './auth'; -import { SESSION_COOKIE_NAME } from '../plugins/session'; +import databasePlugin from '../plugins/database/index.js'; +import sessionPlugin from '../plugins/session/index.js'; +import authRoutes from './auth.js'; +import { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; describe('auth routes', () => { beforeEach(() => { diff --git a/apps/api/src/app/routes/auth.ts b/libs/server-platform/src/lib/routes/auth.ts similarity index 100% rename from apps/api/src/app/routes/auth.ts rename to libs/server-platform/src/lib/routes/auth.ts diff --git a/apps/api/src/app/routes/oauth.spec.ts b/libs/server-platform/src/lib/routes/oauth.spec.ts similarity index 97% rename from apps/api/src/app/routes/oauth.spec.ts rename to libs/server-platform/src/lib/routes/oauth.spec.ts index 4baeb2c..3921694 100644 --- a/apps/api/src/app/routes/oauth.spec.ts +++ b/libs/server-platform/src/lib/routes/oauth.spec.ts @@ -1,11 +1,11 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import type { OAuthProviderType } from '@rod-manager/shared'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session'; -import databasePlugin from '../plugins/database'; -import type { OAuthService } from '../plugins/oauth'; -import authRoutes from './auth'; -import oauthRoutes from './oauth'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; +import databasePlugin from '../plugins/database/index.js'; +import type { OAuthService } from '../plugins/oauth/index.js'; +import authRoutes from './auth.js'; +import oauthRoutes from './oauth.js'; function createOAuthService(): OAuthService { return { diff --git a/apps/api/src/app/routes/oauth.ts b/libs/server-platform/src/lib/routes/oauth.ts similarity index 99% rename from apps/api/src/app/routes/oauth.ts rename to libs/server-platform/src/lib/routes/oauth.ts index d61eba2..85441da 100644 --- a/apps/api/src/app/routes/oauth.ts +++ b/libs/server-platform/src/lib/routes/oauth.ts @@ -7,8 +7,8 @@ import type { OAuthProviderType, OAuthProvidersResponseBody, } from '@rod-manager/shared'; -import type { AuthStoreSession } from '../plugins/database'; -import { generatePKCE } from '../plugins/oauth'; +import type { AuthStoreSession } from '../plugins/database/index.js'; +import { generatePKCE } from '../plugins/oauth/index.js'; interface OAuthStateData { provider: OAuthProviderType; diff --git a/apps/api/src/app/routes/pages.spec.ts b/libs/server-platform/src/lib/routes/pages.spec.ts similarity index 94% rename from apps/api/src/app/routes/pages.spec.ts rename to libs/server-platform/src/lib/routes/pages.spec.ts index 1ace1e7..b7099e3 100644 --- a/apps/api/src/app/routes/pages.spec.ts +++ b/libs/server-platform/src/lib/routes/pages.spec.ts @@ -1,9 +1,9 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session'; -import databasePlugin from '../plugins/database'; -import authRoutes from './auth'; -import pagesRoutes from './pages'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; +import databasePlugin from '../plugins/database/index.js'; +import authRoutes from './auth.js'; +import pagesRoutes from './pages.js'; describe('pages routes', () => { beforeEach(() => { diff --git a/apps/api/src/app/routes/pages.ts b/libs/server-platform/src/lib/routes/pages.ts similarity index 100% rename from apps/api/src/app/routes/pages.ts rename to libs/server-platform/src/lib/routes/pages.ts diff --git a/apps/api/src/app/routes/root.spec.ts b/libs/server-platform/src/lib/routes/root.spec.ts similarity index 100% rename from apps/api/src/app/routes/root.spec.ts rename to libs/server-platform/src/lib/routes/root.spec.ts diff --git a/apps/api/src/app/routes/root.ts b/libs/server-platform/src/lib/routes/root.ts similarity index 100% rename from apps/api/src/app/routes/root.ts rename to libs/server-platform/src/lib/routes/root.ts diff --git a/apps/api/src/app/routes/ssr.ts b/libs/server-platform/src/lib/routes/ssr.ts similarity index 100% rename from apps/api/src/app/routes/ssr.ts rename to libs/server-platform/src/lib/routes/ssr.ts diff --git a/apps/api/src/app/routes/user-settings.spec.ts b/libs/server-platform/src/lib/routes/user-settings.spec.ts similarity index 93% rename from apps/api/src/app/routes/user-settings.spec.ts rename to libs/server-platform/src/lib/routes/user-settings.spec.ts index b9de8b7..a5b83a6 100644 --- a/apps/api/src/app/routes/user-settings.spec.ts +++ b/libs/server-platform/src/lib/routes/user-settings.spec.ts @@ -1,9 +1,9 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session'; -import databasePlugin from '../plugins/database'; -import authRoutes from './auth'; -import userSettingsRoutes from './user-settings'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; +import databasePlugin from '../plugins/database/index.js'; +import authRoutes from './auth.js'; +import userSettingsRoutes from './user-settings.js'; describe('user settings routes', () => { beforeEach(() => { diff --git a/apps/api/src/app/routes/user-settings.ts b/libs/server-platform/src/lib/routes/user-settings.ts similarity index 100% rename from apps/api/src/app/routes/user-settings.ts rename to libs/server-platform/src/lib/routes/user-settings.ts diff --git a/libs/server-platform/tsconfig.json b/libs/server-platform/tsconfig.json new file mode 100644 index 0000000..c823650 --- /dev/null +++ b/libs/server-platform/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.lib.json" }, + { "path": "./tsconfig.spec.json" } + ] +} diff --git a/libs/server-platform/tsconfig.lib.json b/libs/server-platform/tsconfig.lib.json new file mode 100644 index 0000000..29d699d --- /dev/null +++ b/libs/server-platform/tsconfig.lib.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"], + "references": [ + { "path": "../../libs/shared/tsconfig.lib.json" } + ] +} diff --git a/libs/server-platform/tsconfig.spec.json b/libs/server-platform/tsconfig.spec.json new file mode 100644 index 0000000..8afa0f3 --- /dev/null +++ b/libs/server-platform/tsconfig.spec.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "vitest/globals"], + "rootDir": "src", + "module": "esnext", + "moduleResolution": "bundler", + "tsBuildInfoFile": "dist/tsconfig.spec.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "exclude": ["eslint.config.js", "eslint.config.cjs", "eslint.config.mjs"] +} diff --git a/libs/server-platform/vitest.config.mts b/libs/server-platform/vitest.config.mts new file mode 100644 index 0000000..a15ad69 --- /dev/null +++ b/libs/server-platform/vitest.config.mts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.{spec,test}.ts'], + }, +}); diff --git a/package-lock.json b/package-lock.json index d5084ce..85f587e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,15 +98,9 @@ "name": "@rod-manager/api", "version": "0.0.1", "dependencies": { - "@fastify/autoload": "~6.0.3", - "@fastify/cookie": "^11.0.2", - "@fastify/middie": "^9.0.3", - "@fastify/sensible": "~6.0.2", - "@fastify/static": "^8.3.0", - "better-sqlite3": "^12.9.0", + "@rod-manager/server-platform": "0.0.1", "dotenv": "^16.4.7", - "fastify": "5.8.3", - "fastify-plugin": "~5.0.1" + "fastify": "5.8.3" } }, "apps/web": { @@ -118,6 +112,22 @@ "zod": "^4.3.6" } }, + "libs/server-platform": { + "name": "@rod-manager/server-platform", + "version": "0.0.1", + "dependencies": { + "@fastify/autoload": "~6.0.3", + "@fastify/cookie": "^11.0.2", + "@fastify/middie": "^9.0.3", + "@fastify/sensible": "~6.0.2", + "@fastify/static": "^8.3.0", + "@rod-manager/shared": "0.0.1", + "better-sqlite3": "^12.9.0", + "dotenv": "^16.4.7", + "fastify": "5.8.3", + "fastify-plugin": "~5.0.1" + } + }, "libs/shared": { "name": "@rod-manager/shared", "version": "0.0.1" @@ -5853,6 +5863,10 @@ "resolved": "apps/api", "link": true }, + "node_modules/@rod-manager/server-platform": { + "resolved": "libs/server-platform", + "link": true + }, "node_modules/@rod-manager/shared": { "resolved": "libs/shared", "link": true From fdccd90797c89e25ae44a46da77ac134dc84dbff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:02:59 +0000 Subject: [PATCH 02/11] feat: introduce ServerPlatformPlugin contract and plugin registry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- libs/server-platform/src/index.ts | 16 ++++ .../src/lib/contracts/capability.contract.ts | 5 ++ .../src/lib/contracts/plugin.contract.ts | 89 +++++++++++++++++++ .../src/lib/createServerPlatform.ts | 8 ++ .../src/lib/plugins/database/index.ts | 6 +- .../src/lib/plugins/database/types.ts | 3 + .../src/lib/runtime/context.ts | 1 + .../src/lib/serverPluginRegistry.ts | 86 ++++++++++++++++++ 8 files changed, 212 insertions(+), 2 deletions(-) create mode 100644 libs/server-platform/src/lib/contracts/capability.contract.ts create mode 100644 libs/server-platform/src/lib/contracts/plugin.contract.ts create mode 100644 libs/server-platform/src/lib/runtime/context.ts create mode 100644 libs/server-platform/src/lib/serverPluginRegistry.ts diff --git a/libs/server-platform/src/index.ts b/libs/server-platform/src/index.ts index 20626cb..4c361df 100644 --- a/libs/server-platform/src/index.ts +++ b/libs/server-platform/src/index.ts @@ -1,2 +1,18 @@ export { createServerPlatform } from './lib/createServerPlatform.js'; export type { ServerPlatformOptions } from './lib/createServerPlatform.js'; +export type { + ServerPlatformPlugin, + ServerPlatformPluginMeta, + ServerPlatformPluginContext, + ServerPlatformMigration, + ServerPlatformAuthStore, + ServerPlatformAuthStoreUser, + ServerPlatformAuthSession, + ServerPlatformSessionService, + ServerPlatformDbClient, + ServerPlatformDbStatement, + ApiErrorResponse, + JsonValue, + JsonPrimitive, +} from './lib/contracts/plugin.contract.js'; +export type { ServerPlatformCapability } from './lib/contracts/capability.contract.js'; diff --git a/libs/server-platform/src/lib/contracts/capability.contract.ts b/libs/server-platform/src/lib/contracts/capability.contract.ts new file mode 100644 index 0000000..b8bb47e --- /dev/null +++ b/libs/server-platform/src/lib/contracts/capability.contract.ts @@ -0,0 +1,5 @@ +export interface ServerPlatformCapability { + id: string; + version: string; + description?: string; +} diff --git a/libs/server-platform/src/lib/contracts/plugin.contract.ts b/libs/server-platform/src/lib/contracts/plugin.contract.ts new file mode 100644 index 0000000..73a2abf --- /dev/null +++ b/libs/server-platform/src/lib/contracts/plugin.contract.ts @@ -0,0 +1,89 @@ +import type { FastifyBaseLogger, FastifyInstance } from 'fastify'; + +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = + | JsonPrimitive + | { [key: string]: JsonValue } + | JsonValue[]; + +export interface ServerPlatformAuthStoreUser { + id: string; + email: string; + role: string; + displayName: string; +} + +export interface ServerPlatformAuthSession { + token: string; + userId: string; + expiresAt: number; + userEmail: string; + userRole: string; +} + +export interface ServerPlatformAuthStore { + findUserById(id: string): ServerPlatformAuthStoreUser | undefined; + findSession(token: string): ServerPlatformAuthSession | undefined; + createSession(userId: string): string; + deleteSession(token: string): void; +} + +export interface ServerPlatformSessionService { + createSession(userId: string): string; + invalidateSession(token: string): void; + deleteExpiredSessions(now: number): void; +} + +export interface ServerPlatformDbStatement< + TParams extends readonly JsonValue[] = readonly JsonValue[], + TResult = JsonValue, +> { + get(...params: TParams): TResult | undefined; + all(...params: TParams): TResult[]; + run(...params: TParams): { changes: number }; +} + +export interface ServerPlatformDbClient { + prepare< + TParams extends readonly JsonValue[] = readonly JsonValue[], + TResult = JsonValue, + >( + sql: string, + ): ServerPlatformDbStatement; + exec(sql: string): void; +} + +export interface ServerPlatformPluginMeta { + id: string; + version: string; + description?: string; + dependsOn?: string[]; + capabilities?: string[]; +} + +export interface ServerPlatformPluginContext { + fastify: FastifyInstance; + services: { + authStore: ServerPlatformAuthStore; + sessionService: ServerPlatformSessionService; + db: ServerPlatformDbClient; + logger: FastifyBaseLogger; + }; +} + +export interface ServerPlatformMigration { + id: string; + up: (ctx: ServerPlatformPluginContext) => Promise | void; +} + +export interface ServerPlatformPlugin { + meta: ServerPlatformPluginMeta; + migrations?: ServerPlatformMigration[]; + register: (ctx: ServerPlatformPluginContext) => Promise | void; +} + +export interface ApiErrorResponse { + message: string; + code?: string; + details?: Record; +} diff --git a/libs/server-platform/src/lib/createServerPlatform.ts b/libs/server-platform/src/lib/createServerPlatform.ts index 90b4c73..152e9ef 100644 --- a/libs/server-platform/src/lib/createServerPlatform.ts +++ b/libs/server-platform/src/lib/createServerPlatform.ts @@ -1,4 +1,6 @@ import type { FastifyInstance } from 'fastify'; +import type { ServerPlatformPlugin } from './contracts/plugin.contract.js'; +import { createPluginRegistrar } from './serverPluginRegistry.js'; import databasePlugin from './plugins/database/index.js'; import sessionPlugin from './plugins/session/index.js'; import oauthPlugin from './plugins/oauth/index.js'; @@ -12,6 +14,7 @@ import userSettingsRoutes from './routes/user-settings.js'; export interface ServerPlatformOptions { logLevel?: string; + plugins?: ServerPlatformPlugin[]; } /** Registers all core plugins and routes on the given Fastify instance. */ @@ -32,4 +35,9 @@ export async function createServerPlatform( fastify.register(rootRoute); fastify.register(userSettingsRoutes); fastify.register(ssrRoute); + + // Feature plugins + if (opts.plugins && opts.plugins.length > 0) { + fastify.register(createPluginRegistrar(opts.plugins)); + } } diff --git a/libs/server-platform/src/lib/plugins/database/index.ts b/libs/server-platform/src/lib/plugins/database/index.ts index 5121dd9..3c86c6b 100644 --- a/libs/server-platform/src/lib/plugins/database/index.ts +++ b/libs/server-platform/src/lib/plugins/database/index.ts @@ -16,6 +16,7 @@ import { import { createStore } from './store.js'; import { createUserSettingsStore } from './userSettingsStore.js'; import { createPageStore } from './pageStore.js'; +import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; export type { AuthStore, @@ -25,8 +26,8 @@ export type { OAuthProviderType, UserSettingsStore, PageStore, -} from './types'; -export { createSessionExpiration } from './types'; +} from './types.js'; +export { createSessionExpiration } from './types.js'; /** * Registers SQLite-backed store for authentication and session persistence. @@ -50,6 +51,7 @@ export default fp(function databasePlugin(fastify: FastifyInstance) { fastify.decorate('authStore', createStore(db)); fastify.decorate('userSettingsStore', createUserSettingsStore(db)); fastify.decorate('pageStore', createPageStore(db)); + fastify.decorate('db', db as unknown as ServerPlatformDbClient); fastify.addHook('onClose', async () => { db.close(); diff --git a/libs/server-platform/src/lib/plugins/database/types.ts b/libs/server-platform/src/lib/plugins/database/types.ts index 22173ab..aa4599e 100644 --- a/libs/server-platform/src/lib/plugins/database/types.ts +++ b/libs/server-platform/src/lib/plugins/database/types.ts @@ -154,11 +154,14 @@ export interface ContentPageRow { content_md: string; } +import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; + declare module 'fastify' { interface FastifyInstance { authStore: AuthStore; userSettingsStore: UserSettingsStore; pageStore: PageStore; + db: ServerPlatformDbClient; } } diff --git a/libs/server-platform/src/lib/runtime/context.ts b/libs/server-platform/src/lib/runtime/context.ts new file mode 100644 index 0000000..ee017da --- /dev/null +++ b/libs/server-platform/src/lib/runtime/context.ts @@ -0,0 +1 @@ +export type { ServerPlatformPluginContext } from '../contracts/plugin.contract.js'; diff --git a/libs/server-platform/src/lib/serverPluginRegistry.ts b/libs/server-platform/src/lib/serverPluginRegistry.ts new file mode 100644 index 0000000..9177074 --- /dev/null +++ b/libs/server-platform/src/lib/serverPluginRegistry.ts @@ -0,0 +1,86 @@ +import type { FastifyInstance } from 'fastify'; +import fp from 'fastify-plugin'; +import type { + ServerPlatformAuthStore, + ServerPlatformPlugin, + ServerPlatformPluginContext, + ServerPlatformSessionService, +} from './contracts/plugin.contract.js'; +import type { AuthStore } from './plugins/database/types.js'; + +function createSessionServiceAdapter( + authStore: AuthStore, +): ServerPlatformSessionService { + return { + createSession(userId: string): string { + return authStore.createSession(userId); + }, + invalidateSession(token: string): void { + authStore.deleteSession(token); + }, + deleteExpiredSessions(now: number): void { + authStore.deleteExpiredSessions(now); + }, + }; +} + +function createAuthStoreAdapter(authStore: AuthStore): ServerPlatformAuthStore { + return { + findUserById(id: string) { + const user = authStore.findUserById(id); + if (user === undefined) return undefined; + return { + id: user.id, + email: user.email, + role: user.role, + displayName: user.displayName, + }; + }, + findSession(token: string) { + const session = authStore.findSession(token); + if (session === undefined) return undefined; + return { + token: session.token, + userId: session.userId, + expiresAt: session.expiresAt, + userEmail: session.userEmail, + userRole: session.userRole, + }; + }, + createSession(userId: string): string { + return authStore.createSession(userId); + }, + deleteSession(token: string): void { + authStore.deleteSession(token); + }, + }; +} + +/** Creates a Fastify plugin that registers the given ServerPlatformPlugin list in order. */ +export function createPluginRegistrar(plugins: ServerPlatformPlugin[]) { + return fp(async function serverPluginRegistrar(fastify: FastifyInstance) { + for (const plugin of plugins) { + await fastify.register( + fp(async function serverPlugin(instance: FastifyInstance) { + const ctx: ServerPlatformPluginContext = { + fastify: instance, + services: { + authStore: createAuthStoreAdapter(instance.authStore), + sessionService: createSessionServiceAdapter(instance.authStore), + db: instance.db, + logger: instance.log, + }, + }; + + if (plugin.migrations) { + for (const migration of plugin.migrations) { + await migration.up(ctx); + } + } + + await plugin.register(ctx); + }), + ); + } + }); +} From 19951656182debeea16dd808cc51d1d119c15be8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:03:34 +0000 Subject: [PATCH 03/11] refactor: simplify apps/api/src/main.ts to bootstrap-only host Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/api/src/app/app.ts | 13 ------------- apps/api/src/main.ts | 9 ++++----- 2 files changed, 4 insertions(+), 18 deletions(-) delete mode 100644 apps/api/src/app/app.ts diff --git a/apps/api/src/app/app.ts b/apps/api/src/app/app.ts deleted file mode 100644 index d7bc8ff..0000000 --- a/apps/api/src/app/app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { FastifyInstance } from 'fastify'; -import { createServerPlatform } from '@rod-manager/server-platform'; - -export interface AppOptions { - logLevel?: string; -} - -/** Registers all server platform plugins and routes on the given Fastify instance. */ -export function app(fastify: FastifyInstance, opts: AppOptions) { - fastify.register(async (instance) => { - await createServerPlatform(instance, opts); - }); -} diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 34295df..f9f574c 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -2,7 +2,7 @@ import 'dotenv/config'; import Fastify from 'fastify'; import { existsSync, readFileSync } from 'node:fs'; import type { FastifyInstance } from 'fastify'; -import { app } from './app/app'; +import { createServerPlatform } from '@rod-manager/server-platform'; const host = process.env.HOST ?? 'localhost'; const port = process.env.PORT ? Number(process.env.PORT) : 3000; @@ -12,7 +12,6 @@ const defaultDevKeyPath = '.cert/localhost-key.pem'; const defaultDevCertPath = '.cert/localhost-cert.pem'; const httpsOptions = getHttpsOptions(); -// Instantiate Fastify with some config const server = Fastify({ logger: true, https: httpsOptions, @@ -44,10 +43,10 @@ function getHttpsOptions() { }; } -// Register your application as a normal plugin. -server.register(app); +server.register(async (instance) => { + await createServerPlatform(instance); +}); -// Start listening. server.listen({ port, host }, (err) => { if (err) { server.log.error(err); From 9ebb19d389e523347f0b385818e019ec1dad1d7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:06:30 +0000 Subject: [PATCH 04/11] feat: extract pages routes and store into libs/plugins/pages/server Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- apps/api/package.json | 1 + apps/api/src/main.ts | 5 +- apps/api/tsconfig.app.json | 3 + libs/plugins/pages/server/package.json | 48 +++++++++ libs/plugins/pages/server/src/index.ts | 1 + libs/plugins/pages/server/src/lib/plugin.ts | 18 ++++ .../pages/server/src/lib/routes.ts} | 20 ++-- libs/plugins/pages/server/src/lib/store.ts | 53 +++++++++ libs/plugins/pages/server/tsconfig.json | 9 ++ libs/plugins/pages/server/tsconfig.lib.json | 18 ++++ libs/plugins/pages/server/tsconfig.spec.json | 12 +++ libs/plugins/pages/server/vitest.config.mts | 9 ++ .../src/lib/createServerPlatform.ts | 2 - .../src/lib/routes/pages.spec.ts | 101 ------------------ package-lock.json | 16 ++- package.json | 3 +- 16 files changed, 202 insertions(+), 117 deletions(-) create mode 100644 libs/plugins/pages/server/package.json create mode 100644 libs/plugins/pages/server/src/index.ts create mode 100644 libs/plugins/pages/server/src/lib/plugin.ts rename libs/{server-platform/src/lib/routes/pages.ts => plugins/pages/server/src/lib/routes.ts} (66%) create mode 100644 libs/plugins/pages/server/src/lib/store.ts create mode 100644 libs/plugins/pages/server/tsconfig.json create mode 100644 libs/plugins/pages/server/tsconfig.lib.json create mode 100644 libs/plugins/pages/server/tsconfig.spec.json create mode 100644 libs/plugins/pages/server/vitest.config.mts delete mode 100644 libs/server-platform/src/lib/routes/pages.spec.ts diff --git a/apps/api/package.json b/apps/api/package.json index 6f4f027..679f544 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -122,6 +122,7 @@ } }, "dependencies": { + "@rod-manager/plugin-pages-server": "0.0.1", "@rod-manager/server-platform": "0.0.1", "dotenv": "^16.4.7", "fastify": "5.8.3" diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index f9f574c..7c00f62 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -3,6 +3,7 @@ import Fastify from 'fastify'; import { existsSync, readFileSync } from 'node:fs'; import type { FastifyInstance } from 'fastify'; import { createServerPlatform } from '@rod-manager/server-platform'; +import { pagesServerPlugin } from '@rod-manager/plugin-pages-server'; const host = process.env.HOST ?? 'localhost'; const port = process.env.PORT ? Number(process.env.PORT) : 3000; @@ -44,7 +45,9 @@ function getHttpsOptions() { } server.register(async (instance) => { - await createServerPlatform(instance); + await createServerPlatform(instance, { + plugins: [pagesServerPlugin()], + }); }); server.listen({ port, host }, (err) => { diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json index 71a9bd5..811a7b9 100644 --- a/apps/api/tsconfig.app.json +++ b/apps/api/tsconfig.app.json @@ -20,6 +20,9 @@ }, { "path": "../../libs/server-platform/tsconfig.lib.json" + }, + { + "path": "../../libs/plugins/pages/server/tsconfig.lib.json" } ] } diff --git a/libs/plugins/pages/server/package.json b/libs/plugins/pages/server/package.json new file mode 100644 index 0000000..06e6285 --- /dev/null +++ b/libs/plugins/pages/server/package.json @@ -0,0 +1,48 @@ +{ + "name": "@rod-manager/plugin-pages-server", + "version": "0.0.1", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@rod-manager/source": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": ["dist", "!**/*.tsbuildinfo"], + "dependencies": { + "@rod-manager/server-platform": "0.0.1", + "@rod-manager/shared": "0.0.1" + }, + "nx": { + "targets": { + "typecheck": { + "executor": "nx:run-commands", + "options": { + "cwd": "libs/plugins/pages/server", + "command": "node ../../../../node_modules/typescript/bin/tsc --noEmit -p tsconfig.lib.json && node ../../../../node_modules/typescript/bin/tsc --noEmit -p tsconfig.spec.json" + } + }, + "build": { + "executor": "nx:run-commands", + "outputs": ["{projectRoot}/dist"], + "options": { + "cwd": "libs/plugins/pages/server", + "command": "node ../../../../node_modules/typescript/bin/tsc --build tsconfig.lib.json" + } + }, + "test": { + "executor": "nx:run-commands", + "options": { + "cwd": "libs/plugins/pages/server", + "command": "node ../../../../node_modules/vitest/vitest.mjs run --config vitest.config.mts" + } + } + } + } +} diff --git a/libs/plugins/pages/server/src/index.ts b/libs/plugins/pages/server/src/index.ts new file mode 100644 index 0000000..4b6a8fc --- /dev/null +++ b/libs/plugins/pages/server/src/index.ts @@ -0,0 +1 @@ +export { pagesServerPlugin } from './lib/plugin.js'; diff --git a/libs/plugins/pages/server/src/lib/plugin.ts b/libs/plugins/pages/server/src/lib/plugin.ts new file mode 100644 index 0000000..a2271d5 --- /dev/null +++ b/libs/plugins/pages/server/src/lib/plugin.ts @@ -0,0 +1,18 @@ +import type { ServerPlatformPlugin } from '@rod-manager/server-platform'; +import { createPageStore } from './store.js'; +import { registerPagesRoutes } from './routes.js'; + +/** Creates the pages server plugin descriptor for use with createServerPlatform. */ +export function pagesServerPlugin(): ServerPlatformPlugin { + return { + meta: { + id: 'pages', + version: '0.0.1', + description: 'Content pages feature plugin', + }, + register(ctx) { + const pageStore = createPageStore(ctx.services.db); + registerPagesRoutes(ctx.fastify, pageStore); + }, + }; +} diff --git a/libs/server-platform/src/lib/routes/pages.ts b/libs/plugins/pages/server/src/lib/routes.ts similarity index 66% rename from libs/server-platform/src/lib/routes/pages.ts rename to libs/plugins/pages/server/src/lib/routes.ts index 5bcf4f8..69080e9 100644 --- a/libs/server-platform/src/lib/routes/pages.ts +++ b/libs/plugins/pages/server/src/lib/routes.ts @@ -3,8 +3,13 @@ import type { ContentPageListResponseBody, ContentPageResponseBody, } from '@rod-manager/shared'; +import type { PageStore } from './store.js'; -export default function pagesRoutes(fastify: FastifyInstance) { +/** Registers pages API routes on the given Fastify instance. */ +export function registerPagesRoutes( + fastify: FastifyInstance, + pageStore: PageStore, +): void { fastify.get( '/api/pages', { @@ -14,11 +19,9 @@ export default function pagesRoutes(fastify: FastifyInstance) { if (request.authenticatedSession === undefined) { return; } - const response: ContentPageListResponseBody = { - pages: fastify.pageStore.listPages(), + pages: pageStore.listPages(), }; - await reply.send(response); }, ); @@ -26,17 +29,12 @@ export default function pagesRoutes(fastify: FastifyInstance) { fastify.get<{ Params: { slug: string } }>( '/api/pages/:slug', async (request, reply) => { - const page = fastify.pageStore.findPageBySlug(request.params.slug); - + const page = pageStore.findPageBySlug(request.params.slug); if (page === undefined) { await reply.status(404).send({ message: 'Page not found.' }); return; } - - const response: ContentPageResponseBody = { - page, - }; - + const response: ContentPageResponseBody = { page }; await reply.send(response); }, ); diff --git a/libs/plugins/pages/server/src/lib/store.ts b/libs/plugins/pages/server/src/lib/store.ts new file mode 100644 index 0000000..17cea33 --- /dev/null +++ b/libs/plugins/pages/server/src/lib/store.ts @@ -0,0 +1,53 @@ +import type { ServerPlatformDbClient } from '@rod-manager/server-platform'; + +export interface ContentPageSummary { + slug: string; +} + +export interface ContentPage { + slug: string; + contentMd: string; +} + +export interface PageStore { + listPages(): ContentPageSummary[]; + findPageBySlug(slug: string): ContentPage | undefined; +} + +interface ContentPageSummaryRow { + slug: string; +} + +interface ContentPageRow { + slug: string; + content_md: string; +} + +/** Creates a page store backed by the given database client. */ +export function createPageStore(db: ServerPlatformDbClient): PageStore { + const listPagesStatement = db.prepare<[], ContentPageSummaryRow>( + `SELECT slug FROM pages ORDER BY slug ASC`, + ); + + const findPageBySlugStatement = db.prepare<[string], ContentPageRow>( + `SELECT slug, content_md FROM pages WHERE slug = ?`, + ); + + return { + listPages(): ContentPageSummary[] { + return listPagesStatement.all().map((row) => ({ + slug: row.slug, + })); + }, + findPageBySlug(slug: string): ContentPage | undefined { + const row = findPageBySlugStatement.get(slug); + if (row === undefined) { + return undefined; + } + return { + slug: row.slug, + contentMd: row.content_md, + }; + }, + }; +} diff --git a/libs/plugins/pages/server/tsconfig.json b/libs/plugins/pages/server/tsconfig.json new file mode 100644 index 0000000..1278b48 --- /dev/null +++ b/libs/plugins/pages/server/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.lib.json" }, + { "path": "./tsconfig.spec.json" } + ] +} diff --git a/libs/plugins/pages/server/tsconfig.lib.json b/libs/plugins/pages/server/tsconfig.lib.json new file mode 100644 index 0000000..d47cadc --- /dev/null +++ b/libs/plugins/pages/server/tsconfig.lib.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"], + "references": [ + { "path": "../../../../libs/shared/tsconfig.lib.json" }, + { "path": "../../../../libs/server-platform/tsconfig.lib.json" } + ] +} diff --git a/libs/plugins/pages/server/tsconfig.spec.json b/libs/plugins/pages/server/tsconfig.spec.json new file mode 100644 index 0000000..648f05a --- /dev/null +++ b/libs/plugins/pages/server/tsconfig.spec.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "vitest/globals"], + "rootDir": "src", + "module": "esnext", + "moduleResolution": "bundler", + "tsBuildInfoFile": "dist/tsconfig.spec.tsbuildinfo" + }, + "include": ["src/**/*.ts"], + "exclude": ["eslint.config.js", "eslint.config.cjs", "eslint.config.mjs"] +} diff --git a/libs/plugins/pages/server/vitest.config.mts b/libs/plugins/pages/server/vitest.config.mts new file mode 100644 index 0000000..a15ad69 --- /dev/null +++ b/libs/plugins/pages/server/vitest.config.mts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.{spec,test}.ts'], + }, +}); diff --git a/libs/server-platform/src/lib/createServerPlatform.ts b/libs/server-platform/src/lib/createServerPlatform.ts index 152e9ef..c6f5bc9 100644 --- a/libs/server-platform/src/lib/createServerPlatform.ts +++ b/libs/server-platform/src/lib/createServerPlatform.ts @@ -7,7 +7,6 @@ import oauthPlugin from './plugins/oauth/index.js'; import sensiblePlugin from './plugins/sensible.js'; import authRoutes from './routes/auth.js'; import oauthRoutes from './routes/oauth.js'; -import pagesRoutes from './routes/pages.js'; import rootRoute from './routes/root.js'; import ssrRoute from './routes/ssr.js'; import userSettingsRoutes from './routes/user-settings.js'; @@ -31,7 +30,6 @@ export async function createServerPlatform( // Core routes fastify.register(authRoutes); fastify.register(oauthRoutes); - fastify.register(pagesRoutes); fastify.register(rootRoute); fastify.register(userSettingsRoutes); fastify.register(ssrRoute); diff --git a/libs/server-platform/src/lib/routes/pages.spec.ts b/libs/server-platform/src/lib/routes/pages.spec.ts deleted file mode 100644 index b7099e3..0000000 --- a/libs/server-platform/src/lib/routes/pages.spec.ts +++ /dev/null @@ -1,101 +0,0 @@ -import Fastify from 'fastify'; -import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; -import databasePlugin from '../plugins/database/index.js'; -import authRoutes from './auth.js'; -import pagesRoutes from './pages.js'; - -describe('pages routes', () => { - beforeEach(() => { - process.env.AUTH_DB_PATH = ':memory:'; - process.env.AUTH_SEED_INITIAL_USER = 'true'; - process.env.AUTH_INITIAL_USER_EMAIL = 'admin@rod-manager.local'; - process.env.AUTH_INITIAL_USER_PASSWORD = 'admin1234'; - }); - - afterEach(() => { - delete process.env.AUTH_DB_PATH; - delete process.env.AUTH_SEED_INITIAL_USER; - delete process.env.AUTH_INITIAL_USER_EMAIL; - delete process.env.AUTH_INITIAL_USER_PASSWORD; - }); - - it('lists pages for authenticated users', async () => { - const server = Fastify(); - await server.register(sessionPlugin); - await server.register(databasePlugin); - - authRoutes(server); - pagesRoutes(server); - - const loginResponse = await server.inject({ - method: 'POST', - url: '/api/auth/login', - payload: { - email: 'admin@rod-manager.local', - password: 'admin1234', - }, - }); - - const sessionCookie = loginResponse.cookies.find( - (cookie) => cookie.name === SESSION_COOKIE_NAME, - ); - - const pagesResponse = await server.inject({ - method: 'GET', - url: '/api/pages', - cookies: { - [SESSION_COOKIE_NAME]: sessionCookie?.value ?? '', - }, - }); - - expect(pagesResponse.statusCode).toBe(200); - expect(pagesResponse.json()).toEqual({ - pages: [{ slug: 'about' }, { slug: 'home' }, { slug: 'rules' }], - }); - - await server.close(); - }); - - it('returns page content by slug', async () => { - const server = Fastify(); - await server.register(sessionPlugin); - await server.register(databasePlugin); - - pagesRoutes(server); - - const response = await server.inject({ - method: 'GET', - url: '/api/pages/about', - }); - - expect(response.statusCode).toBe(200); - expect(response.json()).toEqual({ - page: { - slug: 'about', - contentMd: - '# About\n\nThis page is stored in the database as Markdown content.', - }, - }); - - await server.close(); - }); - - it('returns not found for missing page slug', async () => { - const server = Fastify(); - await server.register(sessionPlugin); - await server.register(databasePlugin); - - pagesRoutes(server); - - const response = await server.inject({ - method: 'GET', - url: '/api/pages/missing-page', - }); - - expect(response.statusCode).toBe(404); - expect(response.json()).toEqual({ message: 'Page not found.' }); - - await server.close(); - }); -}); diff --git a/package-lock.json b/package-lock.json index 85f587e..e791890 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "license": "MIT", "workspaces": [ "apps/*", - "libs/*" + "libs/*", + "libs/plugins/pages/*" ], "dependencies": { "@fastify/autoload": "6.0.3", @@ -98,6 +99,7 @@ "name": "@rod-manager/api", "version": "0.0.1", "dependencies": { + "@rod-manager/plugin-pages-server": "0.0.1", "@rod-manager/server-platform": "0.0.1", "dotenv": "^16.4.7", "fastify": "5.8.3" @@ -112,6 +114,14 @@ "zod": "^4.3.6" } }, + "libs/plugins/pages/server": { + "name": "@rod-manager/plugin-pages-server", + "version": "0.0.1", + "dependencies": { + "@rod-manager/server-platform": "0.0.1", + "@rod-manager/shared": "0.0.1" + } + }, "libs/server-platform": { "name": "@rod-manager/server-platform", "version": "0.0.1", @@ -5863,6 +5873,10 @@ "resolved": "apps/api", "link": true }, + "node_modules/@rod-manager/plugin-pages-server": { + "resolved": "libs/plugins/pages/server", + "link": true + }, "node_modules/@rod-manager/server-platform": { "resolved": "libs/server-platform", "link": true diff --git a/package.json b/package.json index d8b6454..8717098 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "private": true, "workspaces": [ "apps/*", - "libs/*" + "libs/*", + "libs/plugins/pages/*" ], "devDependencies": { "@babel/core": "7.29.0", From 1f3bef1aaadb0a71c2d622061b975d50082aa95d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:07:15 +0000 Subject: [PATCH 05/11] feat: add libs/plugins/pages/ui stub for WebPlatform integration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- libs/plugins/pages/ui/package.json | 40 +++++++++++++++++++++++++ libs/plugins/pages/ui/src/index.ts | 1 + libs/plugins/pages/ui/src/lib/plugin.ts | 11 +++++++ libs/plugins/pages/ui/tsconfig.json | 8 +++++ libs/plugins/pages/ui/tsconfig.lib.json | 22 ++++++++++++++ package-lock.json | 11 +++++++ 6 files changed, 93 insertions(+) create mode 100644 libs/plugins/pages/ui/package.json create mode 100644 libs/plugins/pages/ui/src/index.ts create mode 100644 libs/plugins/pages/ui/src/lib/plugin.ts create mode 100644 libs/plugins/pages/ui/tsconfig.json create mode 100644 libs/plugins/pages/ui/tsconfig.lib.json diff --git a/libs/plugins/pages/ui/package.json b/libs/plugins/pages/ui/package.json new file mode 100644 index 0000000..4dc3f61 --- /dev/null +++ b/libs/plugins/pages/ui/package.json @@ -0,0 +1,40 @@ +{ + "name": "@rod-manager/plugin-pages-ui", + "version": "0.0.1", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "./package.json": "./package.json", + ".": { + "@rod-manager/source": "./src/index.ts", + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" + } + }, + "files": ["dist", "!**/*.tsbuildinfo"], + "dependencies": { + "@rod-manager/shared": "0.0.1" + }, + "nx": { + "targets": { + "typecheck": { + "executor": "nx:run-commands", + "options": { + "cwd": "libs/plugins/pages/ui", + "command": "node ../../../../node_modules/typescript/bin/tsc --noEmit -p tsconfig.lib.json" + } + }, + "build": { + "executor": "nx:run-commands", + "outputs": ["{projectRoot}/dist"], + "options": { + "cwd": "libs/plugins/pages/ui", + "command": "node ../../../../node_modules/typescript/bin/tsc --build tsconfig.lib.json" + } + } + } + } +} diff --git a/libs/plugins/pages/ui/src/index.ts b/libs/plugins/pages/ui/src/index.ts new file mode 100644 index 0000000..431e42e --- /dev/null +++ b/libs/plugins/pages/ui/src/index.ts @@ -0,0 +1 @@ +export type { PagesUiPlugin } from './lib/plugin.js'; diff --git a/libs/plugins/pages/ui/src/lib/plugin.ts b/libs/plugins/pages/ui/src/lib/plugin.ts new file mode 100644 index 0000000..5341afa --- /dev/null +++ b/libs/plugins/pages/ui/src/lib/plugin.ts @@ -0,0 +1,11 @@ +/** Placeholder for WebPlatform integration for the pages plugin. */ +export interface PagesUiPlugin { + id: string; +} + +/** Creates the pages UI plugin descriptor for WebPlatform integration. */ +export function pagesUiPlugin(): PagesUiPlugin { + return { + id: 'pages', + }; +} diff --git a/libs/plugins/pages/ui/tsconfig.json b/libs/plugins/pages/ui/tsconfig.json new file mode 100644 index 0000000..1fab1b5 --- /dev/null +++ b/libs/plugins/pages/ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { "path": "./tsconfig.lib.json" } + ] +} diff --git a/libs/plugins/pages/ui/tsconfig.lib.json b/libs/plugins/pages/ui/tsconfig.lib.json new file mode 100644 index 0000000..d31b520 --- /dev/null +++ b/libs/plugins/pages/ui/tsconfig.lib.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": true, + "forceConsistentCasingInFileNames": true, + "types": ["node"] + }, + "include": ["src/**/*.ts", "src/**/*.tsx"], + "exclude": [ + "src/**/*.spec.ts", + "src/**/*.spec.tsx", + "src/**/*.test.ts", + "src/**/*.test.tsx" + ], + "references": [ + { "path": "../../../../libs/shared/tsconfig.lib.json" } + ] +} diff --git a/package-lock.json b/package-lock.json index e791890..7dc99d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -122,6 +122,13 @@ "@rod-manager/shared": "0.0.1" } }, + "libs/plugins/pages/ui": { + "name": "@rod-manager/plugin-pages-ui", + "version": "0.0.1", + "dependencies": { + "@rod-manager/shared": "0.0.1" + } + }, "libs/server-platform": { "name": "@rod-manager/server-platform", "version": "0.0.1", @@ -5877,6 +5884,10 @@ "resolved": "libs/plugins/pages/server", "link": true }, + "node_modules/@rod-manager/plugin-pages-ui": { + "resolved": "libs/plugins/pages/ui", + "link": true + }, "node_modules/@rod-manager/server-platform": { "resolved": "libs/server-platform", "link": true From f4d591cf128b67a952f7028e085e43a17a291353 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:11:07 +0000 Subject: [PATCH 06/11] feat: move pages migrations to plugin package Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pages/server/src/lib/migrations.ts | 80 +++++++++++++++++++ libs/plugins/pages/server/src/lib/plugin.ts | 10 +++ .../src/lib/plugins/database/index.ts | 7 -- .../src/lib/plugins/database/init.ts | 74 ----------------- .../src/lib/plugins/database/types.ts | 1 - 5 files changed, 90 insertions(+), 82 deletions(-) create mode 100644 libs/plugins/pages/server/src/lib/migrations.ts diff --git a/libs/plugins/pages/server/src/lib/migrations.ts b/libs/plugins/pages/server/src/lib/migrations.ts new file mode 100644 index 0000000..695d043 --- /dev/null +++ b/libs/plugins/pages/server/src/lib/migrations.ts @@ -0,0 +1,80 @@ +import type { ServerPlatformMigration } from '@rod-manager/server-platform'; + +const RESERVED_PAGE_SLUGS = ['account', 'register', 'pages', 'auth', 'api']; +const RESERVED_PAGE_SLUGS_SQL = RESERVED_PAGE_SLUGS.map( + (slug) => `'${slug}'`, +).join(', '); +const RESERVED_PAGE_SLUG_ERROR_MESSAGE = + 'Page slug collides with a reserved application route.'; +const EMPTY_PAGE_SLUG_ERROR_MESSAGE = 'Page slug cannot be empty.'; + +export const pagesSchemaMigration: ServerPlatformMigration = { + id: 'pages-schema-v1', + up(ctx) { + ctx.services.db.exec(` + CREATE TABLE IF NOT EXISTS pages ( + slug TEXT PRIMARY KEY CHECK ( + trim(slug) <> '' AND + lower(slug) NOT IN (${RESERVED_PAGE_SLUGS_SQL}) + ), + content_md TEXT NOT NULL, + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ); + + CREATE INDEX IF NOT EXISTS idx_pages_slug ON pages(slug); + `); + }, +}; + +export const pagesValidationRulesMigration: ServerPlatformMigration = { + id: 'pages-validation-rules-v1', + up(ctx) { + ctx.services.db.exec(` + CREATE TRIGGER IF NOT EXISTS trg_pages_validate_empty_slug_on_insert + BEFORE INSERT ON pages + FOR EACH ROW + WHEN trim(NEW.slug) = '' + BEGIN + SELECT RAISE(ABORT, '${EMPTY_PAGE_SLUG_ERROR_MESSAGE}'); + END; + + CREATE TRIGGER IF NOT EXISTS trg_pages_validate_reserved_slug_on_insert + BEFORE INSERT ON pages + FOR EACH ROW + WHEN lower(NEW.slug) IN (${RESERVED_PAGE_SLUGS_SQL}) + BEGIN + SELECT RAISE(ABORT, '${RESERVED_PAGE_SLUG_ERROR_MESSAGE}'); + END; + + CREATE TRIGGER IF NOT EXISTS trg_pages_validate_empty_slug_on_update + BEFORE UPDATE OF slug ON pages + FOR EACH ROW + WHEN trim(NEW.slug) = '' + BEGIN + SELECT RAISE(ABORT, '${EMPTY_PAGE_SLUG_ERROR_MESSAGE}'); + END; + + CREATE TRIGGER IF NOT EXISTS trg_pages_validate_reserved_slug_on_update + BEFORE UPDATE OF slug ON pages + FOR EACH ROW + WHEN lower(NEW.slug) IN (${RESERVED_PAGE_SLUGS_SQL}) + BEGIN + SELECT RAISE(ABORT, '${RESERVED_PAGE_SLUG_ERROR_MESSAGE}'); + END; + `); + }, +}; + +export const pagesSeedMigration: ServerPlatformMigration = { + id: 'pages-seed-v1', + up(ctx) { + ctx.services.db.exec(` + INSERT OR IGNORE INTO pages (slug, content_md) + VALUES + ('home', '# Home\n\nWelcome to Rod Manager. This home page is stored in the database.'), + ('about', '# About\n\nThis page is stored in the database as Markdown content.'), + ('rules', '# Community Rules\n\n1. Be respectful.\n2. Keep discussions constructive.\n3. Follow project guidelines.'); + `); + }, +}; diff --git a/libs/plugins/pages/server/src/lib/plugin.ts b/libs/plugins/pages/server/src/lib/plugin.ts index a2271d5..1561849 100644 --- a/libs/plugins/pages/server/src/lib/plugin.ts +++ b/libs/plugins/pages/server/src/lib/plugin.ts @@ -1,6 +1,11 @@ import type { ServerPlatformPlugin } from '@rod-manager/server-platform'; import { createPageStore } from './store.js'; import { registerPagesRoutes } from './routes.js'; +import { + pagesSchemaMigration, + pagesValidationRulesMigration, + pagesSeedMigration, +} from './migrations.js'; /** Creates the pages server plugin descriptor for use with createServerPlatform. */ export function pagesServerPlugin(): ServerPlatformPlugin { @@ -10,6 +15,11 @@ export function pagesServerPlugin(): ServerPlatformPlugin { version: '0.0.1', description: 'Content pages feature plugin', }, + migrations: [ + pagesSchemaMigration, + pagesValidationRulesMigration, + pagesSeedMigration, + ], register(ctx) { const pageStore = createPageStore(ctx.services.db); registerPagesRoutes(ctx.fastify, pageStore); diff --git a/libs/server-platform/src/lib/plugins/database/index.ts b/libs/server-platform/src/lib/plugins/database/index.ts index 3c86c6b..c5862e1 100644 --- a/libs/server-platform/src/lib/plugins/database/index.ts +++ b/libs/server-platform/src/lib/plugins/database/index.ts @@ -7,15 +7,12 @@ import { ensureUserSettingsModel, ensureUserRoleColumn, ensureNameColumns, - ensurePageSlugValidationRules, - ensureSeedPages, seedInitialUser, shouldSeedInitialUser, ensureAdministratorExists, } from './init.js'; import { createStore } from './store.js'; import { createUserSettingsStore } from './userSettingsStore.js'; -import { createPageStore } from './pageStore.js'; import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; export type { @@ -25,7 +22,6 @@ export type { OAuthProviderData, OAuthProviderType, UserSettingsStore, - PageStore, } from './types.js'; export { createSessionExpiration } from './types.js'; @@ -39,8 +35,6 @@ export default fp(function databasePlugin(fastify: FastifyInstance) { ensureUserSettingsModel(db); ensureUserRoleColumn(db); ensureNameColumns(db); - ensurePageSlugValidationRules(db); - ensureSeedPages(db); if (shouldSeedInitialUser()) { seedInitialUser(db); @@ -50,7 +44,6 @@ export default fp(function databasePlugin(fastify: FastifyInstance) { fastify.decorate('authStore', createStore(db)); fastify.decorate('userSettingsStore', createUserSettingsStore(db)); - fastify.decorate('pageStore', createPageStore(db)); fastify.decorate('db', db as unknown as ServerPlatformDbClient); fastify.addHook('onClose', async () => { diff --git a/libs/server-platform/src/lib/plugins/database/init.ts b/libs/server-platform/src/lib/plugins/database/init.ts index 0b9138f..301b655 100644 --- a/libs/server-platform/src/lib/plugins/database/init.ts +++ b/libs/server-platform/src/lib/plugins/database/init.ts @@ -5,14 +5,6 @@ import type Database from 'better-sqlite3'; import type { CountRow } from './types.js'; import { hashPassword } from './store.js'; -const RESERVED_PAGE_SLUGS = ['account', 'register', 'pages', 'auth', 'api']; -const RESERVED_PAGE_SLUGS_SQL = RESERVED_PAGE_SLUGS.map( - (slug) => `'${slug}'`, -).join(', '); -const RESERVED_PAGE_SLUG_ERROR_MESSAGE = - 'Page slug collides with a reserved application route.'; -const EMPTY_PAGE_SLUG_ERROR_MESSAGE = 'Page slug cannot be empty.'; - export function getDatabasePath(): string { const configuredPath = process.env.AUTH_DB_PATH ?? 'tmp/auth.sqlite'; @@ -71,74 +63,8 @@ export function initializeSchema(db: Database.Database): void { FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); - CREATE TABLE IF NOT EXISTS pages ( - slug TEXT PRIMARY KEY CHECK ( - trim(slug) <> '' AND - lower(slug) NOT IN (${RESERVED_PAGE_SLUGS_SQL}) - ), - content_md TEXT NOT NULL, - created_at INTEGER NOT NULL DEFAULT (unixepoch()), - updated_at INTEGER NOT NULL DEFAULT (unixepoch()) - ); - CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at); CREATE INDEX IF NOT EXISTS idx_oauth_providers_user_id ON oauth_providers(user_id); - CREATE INDEX IF NOT EXISTS idx_pages_slug ON pages(slug); - `); -} - -export function ensurePageSlugValidationRules(db: Database.Database): void { - db.exec(` - CREATE TRIGGER IF NOT EXISTS trg_pages_validate_empty_slug_on_insert - BEFORE INSERT ON pages - FOR EACH ROW - WHEN trim(NEW.slug) = '' - BEGIN - SELECT RAISE(ABORT, '${EMPTY_PAGE_SLUG_ERROR_MESSAGE}'); - END; - - CREATE TRIGGER IF NOT EXISTS trg_pages_validate_reserved_slug_on_insert - BEFORE INSERT ON pages - FOR EACH ROW - WHEN lower(NEW.slug) IN (${RESERVED_PAGE_SLUGS_SQL}) - BEGIN - SELECT RAISE(ABORT, '${RESERVED_PAGE_SLUG_ERROR_MESSAGE}'); - END; - - CREATE TRIGGER IF NOT EXISTS trg_pages_validate_empty_slug_on_update - BEFORE UPDATE OF slug ON pages - FOR EACH ROW - WHEN trim(NEW.slug) = '' - BEGIN - SELECT RAISE(ABORT, '${EMPTY_PAGE_SLUG_ERROR_MESSAGE}'); - END; - - CREATE TRIGGER IF NOT EXISTS trg_pages_validate_reserved_slug_on_update - BEFORE UPDATE OF slug ON pages - FOR EACH ROW - WHEN lower(NEW.slug) IN (${RESERVED_PAGE_SLUGS_SQL}) - BEGIN - SELECT RAISE(ABORT, '${RESERVED_PAGE_SLUG_ERROR_MESSAGE}'); - END; - `); -} - -export function ensureSeedPages(db: Database.Database): void { - db.exec(` - INSERT OR IGNORE INTO pages (slug, content_md) - VALUES - ( - 'home', - '# Home\n\nWelcome to Rod Manager. This home page is stored in the database.' - ), - ( - 'about', - '# About\n\nThis page is stored in the database as Markdown content.' - ), - ( - 'rules', - '# Community Rules\n\n1. Be respectful.\n2. Keep discussions constructive.\n3. Follow project guidelines.' - ); `); } diff --git a/libs/server-platform/src/lib/plugins/database/types.ts b/libs/server-platform/src/lib/plugins/database/types.ts index aa4599e..9cd8686 100644 --- a/libs/server-platform/src/lib/plugins/database/types.ts +++ b/libs/server-platform/src/lib/plugins/database/types.ts @@ -160,7 +160,6 @@ declare module 'fastify' { interface FastifyInstance { authStore: AuthStore; userSettingsStore: UserSettingsStore; - pageStore: PageStore; db: ServerPlatformDbClient; } } From 5978c4f9ed464bf80c522e5fb480b130eceb905a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:12:21 +0000 Subject: [PATCH 07/11] feat: remove pageStore from core db plugin, keep auth/session in core Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pages/server/src/lib/migrations.spec.ts} | 42 +++++++++++++------ .../src/lib/plugins/database/pageStore.ts | 38 ----------------- .../src/lib/plugins/database/types.ts | 26 +----------- 3 files changed, 30 insertions(+), 76 deletions(-) rename libs/{server-platform/src/lib/plugins/database/init.spec.ts => plugins/pages/server/src/lib/migrations.spec.ts} (51%) delete mode 100644 libs/server-platform/src/lib/plugins/database/pageStore.ts diff --git a/libs/server-platform/src/lib/plugins/database/init.spec.ts b/libs/plugins/pages/server/src/lib/migrations.spec.ts similarity index 51% rename from libs/server-platform/src/lib/plugins/database/init.spec.ts rename to libs/plugins/pages/server/src/lib/migrations.spec.ts index 74b30bc..2fbb269 100644 --- a/libs/server-platform/src/lib/plugins/database/init.spec.ts +++ b/libs/plugins/pages/server/src/lib/migrations.spec.ts @@ -1,15 +1,32 @@ import Database from 'better-sqlite3'; import { afterEach, describe, expect, it } from 'vitest'; -import { ensurePageSlugValidationRules, initializeSchema } from './init.js'; +import { + pagesSchemaMigration, + pagesValidationRulesMigration, +} from './migrations.js'; +import type { ServerPlatformPluginContext } from '@rod-manager/server-platform'; const databases: Database.Database[] = []; -function createDatabase(): Database.Database { +function createTestContext(): { + db: Database.Database; + ctx: ServerPlatformPluginContext; +} { const db = new Database(':memory:'); - initializeSchema(db); - ensurePageSlugValidationRules(db); databases.push(db); - return db; + const ctx: ServerPlatformPluginContext = { + fastify: {} as ServerPlatformPluginContext['fastify'], + services: { + authStore: {} as ServerPlatformPluginContext['services']['authStore'], + sessionService: + {} as ServerPlatformPluginContext['services']['sessionService'], + db: db as unknown as ServerPlatformPluginContext['services']['db'], + logger: {} as ServerPlatformPluginContext['services']['logger'], + }, + }; + pagesSchemaMigration.up(ctx); + pagesValidationRulesMigration.up(ctx); + return { db, ctx }; } afterEach(() => { @@ -18,9 +35,9 @@ afterEach(() => { } }); -describe('page slug validation rules', () => { +describe('pages migrations', () => { it('rejects slug collision with reserved routes', () => { - const db = createDatabase(); + const { db } = createTestContext(); expect(() => { db.prepare(`INSERT INTO pages (slug, content_md) VALUES (?, ?)`).run( @@ -31,7 +48,7 @@ describe('page slug validation rules', () => { }); it('rejects empty slug values', () => { - const db = createDatabase(); + const { db } = createTestContext(); expect(() => { db.prepare(`INSERT INTO pages (slug, content_md) VALUES (?, ?)`).run( @@ -42,7 +59,7 @@ describe('page slug validation rules', () => { }); it('accepts non-reserved slugs', () => { - const db = createDatabase(); + const { db } = createTestContext(); db.prepare(`INSERT INTO pages (slug, content_md) VALUES (?, ?)`).run( 'community-news', @@ -50,10 +67,9 @@ describe('page slug validation rules', () => { ); const row = db - .prepare< - [string], - { slug: string; content_md: string } - >(`SELECT slug, content_md FROM pages WHERE slug = ?`) + .prepare<[string], { slug: string; content_md: string }>( + `SELECT slug, content_md FROM pages WHERE slug = ?`, + ) .get('community-news'); expect(row).toEqual({ diff --git a/libs/server-platform/src/lib/plugins/database/pageStore.ts b/libs/server-platform/src/lib/plugins/database/pageStore.ts deleted file mode 100644 index 5549e60..0000000 --- a/libs/server-platform/src/lib/plugins/database/pageStore.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type Database from 'better-sqlite3'; -import type { - ContentPage, - ContentPageRow, - ContentPageSummary, - ContentPageSummaryRow, - PageStore, -} from './types.js'; - -export function createPageStore(db: Database.Database): PageStore { - const listPagesStatement = db.prepare<[], ContentPageSummaryRow>( - `SELECT slug FROM pages ORDER BY slug ASC`, - ); - - const findPageBySlugStatement = db.prepare<[string], ContentPageRow>( - `SELECT slug, content_md FROM pages WHERE slug = ?`, - ); - - return { - listPages(): ContentPageSummary[] { - return listPagesStatement.all().map((row) => ({ - slug: row.slug, - })); - }, - findPageBySlug(slug: string): ContentPage | undefined { - const row = findPageBySlugStatement.get(slug); - - if (row === undefined) { - return undefined; - } - - return { - slug: row.slug, - contentMd: row.content_md, - }; - }, - }; -} diff --git a/libs/server-platform/src/lib/plugins/database/types.ts b/libs/server-platform/src/lib/plugins/database/types.ts index 9cd8686..476c51f 100644 --- a/libs/server-platform/src/lib/plugins/database/types.ts +++ b/libs/server-platform/src/lib/plugins/database/types.ts @@ -3,6 +3,7 @@ import type { UserLanguage, UserRole, } from '@rod-manager/shared'; +import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; export type { OAuthProviderType }; @@ -91,20 +92,6 @@ export interface UserSettingsStore { updateUserPreferredLanguage(userId: string, language: UserLanguage): void; } -export interface ContentPageSummary { - slug: string; -} - -export interface ContentPage { - slug: string; - contentMd: string; -} - -export interface PageStore { - listPages(): ContentPageSummary[]; - findPageBySlug(slug: string): ContentPage | undefined; -} - export interface UserRow { id: string; email: string; @@ -145,17 +132,6 @@ export interface OAuthProviderRow { created_at: number; } -export interface ContentPageSummaryRow { - slug: string; -} - -export interface ContentPageRow { - slug: string; - content_md: string; -} - -import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; - declare module 'fastify' { interface FastifyInstance { authStore: AuthStore; From d0b9db9bd5b1e0bcf4a1cc7e8d34f4dd18cb681b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:14:39 +0000 Subject: [PATCH 08/11] test: add contract tests for pages plugin DTOs and error responses Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pages/server/src/lib/routes.spec.ts | 115 ++++++++++++++++++ libs/plugins/pages/server/vitest.config.mts | 16 +++ libs/server-platform/src/index.ts | 1 + 3 files changed, 132 insertions(+) create mode 100644 libs/plugins/pages/server/src/lib/routes.spec.ts diff --git a/libs/plugins/pages/server/src/lib/routes.spec.ts b/libs/plugins/pages/server/src/lib/routes.spec.ts new file mode 100644 index 0000000..b4bc7c3 --- /dev/null +++ b/libs/plugins/pages/server/src/lib/routes.spec.ts @@ -0,0 +1,115 @@ +import Fastify from 'fastify'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; +import { + createServerPlatform, + SESSION_COOKIE_NAME, +} from '@rod-manager/server-platform'; +import { pagesServerPlugin } from './plugin.js'; + +describe('pages plugin contract tests', () => { + beforeEach(() => { + process.env.AUTH_DB_PATH = ':memory:'; + process.env.AUTH_SEED_INITIAL_USER = 'true'; + process.env.AUTH_INITIAL_USER_EMAIL = 'admin@rod-manager.local'; + process.env.AUTH_INITIAL_USER_PASSWORD = 'admin1234'; + }); + + afterEach(() => { + delete process.env.AUTH_DB_PATH; + delete process.env.AUTH_SEED_INITIAL_USER; + delete process.env.AUTH_INITIAL_USER_EMAIL; + delete process.env.AUTH_INITIAL_USER_PASSWORD; + }); + + it('GET /api/pages returns { pages: [...] } envelope for authenticated users', async () => { + const server = Fastify(); + await server.register(async (instance) => { + await createServerPlatform(instance, { plugins: [pagesServerPlugin()] }); + }); + + const loginResponse = await server.inject({ + method: 'POST', + url: '/api/auth/login', + payload: { + email: 'admin@rod-manager.local', + password: 'admin1234', + }, + }); + + const sessionCookie = loginResponse.cookies.find( + (cookie) => cookie.name === SESSION_COOKIE_NAME, + ); + + const pagesResponse = await server.inject({ + method: 'GET', + url: '/api/pages', + cookies: { + [SESSION_COOKIE_NAME]: sessionCookie?.value ?? '', + }, + }); + + expect(pagesResponse.statusCode).toBe(200); + const body = pagesResponse.json<{ pages: Array<{ slug: string }> }>(); + expect(body).toHaveProperty('pages'); + expect(Array.isArray(body.pages)).toBe(true); + expect(body.pages.every((p) => typeof p.slug === 'string')).toBe(true); + + await server.close(); + }); + + it('GET /api/pages/:slug returns { page: ... } envelope', async () => { + const server = Fastify(); + await server.register(async (instance) => { + await createServerPlatform(instance, { plugins: [pagesServerPlugin()] }); + }); + + const response = await server.inject({ + method: 'GET', + url: '/api/pages/about', + }); + + expect(response.statusCode).toBe(200); + const body = response.json<{ page: { slug: string; contentMd: string } }>(); + expect(body).toHaveProperty('page'); + expect(body.page).toHaveProperty('slug', 'about'); + expect(body.page).toHaveProperty('contentMd'); + expect(typeof body.page.contentMd).toBe('string'); + + await server.close(); + }); + + it('GET /api/pages/:slug returns 404 with { message } for missing slug', async () => { + const server = Fastify(); + await server.register(async (instance) => { + await createServerPlatform(instance, { plugins: [pagesServerPlugin()] }); + }); + + const response = await server.inject({ + method: 'GET', + url: '/api/pages/nonexistent-slug', + }); + + expect(response.statusCode).toBe(404); + const body = response.json<{ message: string }>(); + expect(body).toHaveProperty('message'); + expect(typeof body.message).toBe('string'); + + await server.close(); + }); + + it('GET /api/pages returns 401 for unauthenticated requests', async () => { + const server = Fastify(); + await server.register(async (instance) => { + await createServerPlatform(instance, { plugins: [pagesServerPlugin()] }); + }); + + const response = await server.inject({ + method: 'GET', + url: '/api/pages', + }); + + expect(response.statusCode).toBe(401); + + await server.close(); + }); +}); diff --git a/libs/plugins/pages/server/vitest.config.mts b/libs/plugins/pages/server/vitest.config.mts index a15ad69..f00ec6c 100644 --- a/libs/plugins/pages/server/vitest.config.mts +++ b/libs/plugins/pages/server/vitest.config.mts @@ -1,6 +1,22 @@ import { defineConfig } from 'vitest/config'; +import { resolve } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = fileURLToPath(new URL('.', import.meta.url)); export default defineConfig({ + resolve: { + alias: { + '@rod-manager/server-platform': resolve( + __dirname, + '../../../../libs/server-platform/src/index.ts', + ), + '@rod-manager/shared': resolve( + __dirname, + '../../../../libs/shared/src/index.ts', + ), + }, + }, test: { globals: true, environment: 'node', diff --git a/libs/server-platform/src/index.ts b/libs/server-platform/src/index.ts index 4c361df..cf5a729 100644 --- a/libs/server-platform/src/index.ts +++ b/libs/server-platform/src/index.ts @@ -16,3 +16,4 @@ export type { JsonPrimitive, } from './lib/contracts/plugin.contract.js'; export type { ServerPlatformCapability } from './lib/contracts/capability.contract.js'; +export { SESSION_COOKIE_NAME } from './lib/plugins/session/index.js'; From 27b27564c7b0195b45ea83ef8c9b3cdd0626d6a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 21:16:40 +0000 Subject: [PATCH 09/11] fix: set passWithNoTests in apps/api vitest config after tests moved to server-platform --- apps/api/vitest.config.mts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/api/vitest.config.mts b/apps/api/vitest.config.mts index a15ad69..9f0d347 100644 --- a/apps/api/vitest.config.mts +++ b/apps/api/vitest.config.mts @@ -5,5 +5,6 @@ export default defineConfig({ globals: true, environment: 'node', include: ['src/**/*.{spec,test}.ts'], + passWithNoTests: true, }, }); From 06a28aef9eb264d5048afa2103db858c697c8118 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 15 Apr 2026 10:05:07 +0000 Subject: [PATCH 10/11] fix: remove .js extensions from relative imports, use bundler moduleResolution for node libs, loosen version pins Agent-Logs-Url: https://github.com/ksojecki/rod-manager/sessions/9295c7b9-718f-478c-9bbd-a6465778ea12 --- apps/api/package.json | 2 +- libs/plugins/pages/server/package.json | 13 ++-- libs/plugins/pages/server/src/index.ts | 2 +- .../pages/server/src/lib/migrations.spec.ts | 13 ++-- libs/plugins/pages/server/src/lib/plugin.ts | 6 +- .../pages/server/src/lib/routes.spec.ts | 2 +- libs/plugins/pages/server/src/lib/routes.ts | 2 +- libs/plugins/pages/server/tsconfig.lib.json | 2 + libs/server-platform/package.json | 20 +++--- libs/server-platform/src/index.ts | 10 +-- .../src/lib/createServerPlatform.ts | 22 +++---- .../src/lib/plugins/database/index.ts | 12 ++-- .../src/lib/plugins/database/init.ts | 4 +- .../src/lib/plugins/database/store.ts | 4 +- .../src/lib/plugins/database/types.ts | 2 +- .../lib/plugins/database/userSettingsStore.ts | 2 +- .../src/lib/plugins/oauth/index.ts | 8 +-- .../src/lib/plugins/oauth/providers.ts | 2 +- .../src/lib/plugins/oauth/service.ts | 4 +- .../src/lib/plugins/session/checkSession.ts | 4 +- .../src/lib/plugins/session/index.spec.ts | 4 +- .../src/lib/plugins/session/index.ts | 10 +-- .../src/lib/plugins/session/mutateSession.ts | 6 +- .../src/lib/plugins/session/types.ts | 2 +- .../src/lib/routes/auth.spec.ts | 8 +-- .../src/lib/routes/oauth.spec.ts | 10 +-- libs/server-platform/src/lib/routes/oauth.ts | 4 +- .../src/lib/routes/user-settings.spec.ts | 8 +-- .../src/lib/runtime/context.ts | 2 +- .../src/lib/serverPluginRegistry.ts | 62 +++++++++---------- libs/server-platform/tsconfig.lib.json | 6 +- package-lock.json | 9 ++- 32 files changed, 137 insertions(+), 130 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 679f544..2b37152 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -125,6 +125,6 @@ "@rod-manager/plugin-pages-server": "0.0.1", "@rod-manager/server-platform": "0.0.1", "dotenv": "^16.4.7", - "fastify": "5.8.3" + "fastify": "^5.8.3" } } diff --git a/libs/plugins/pages/server/package.json b/libs/plugins/pages/server/package.json index 06e6285..cbe269a 100644 --- a/libs/plugins/pages/server/package.json +++ b/libs/plugins/pages/server/package.json @@ -1,20 +1,21 @@ { "name": "@rod-manager/plugin-pages-server", "version": "0.0.1", - "type": "module", "main": "./dist/index.js", - "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "@rod-manager/source": "./src/index.ts", "types": "./dist/index.d.ts", - "import": "./dist/index.js", + "require": "./dist/index.js", "default": "./dist/index.js" } }, - "files": ["dist", "!**/*.tsbuildinfo"], + "files": [ + "dist", + "!**/*.tsbuildinfo" + ], "dependencies": { "@rod-manager/server-platform": "0.0.1", "@rod-manager/shared": "0.0.1" @@ -30,7 +31,9 @@ }, "build": { "executor": "nx:run-commands", - "outputs": ["{projectRoot}/dist"], + "outputs": [ + "{projectRoot}/dist" + ], "options": { "cwd": "libs/plugins/pages/server", "command": "node ../../../../node_modules/typescript/bin/tsc --build tsconfig.lib.json" diff --git a/libs/plugins/pages/server/src/index.ts b/libs/plugins/pages/server/src/index.ts index 4b6a8fc..82b3836 100644 --- a/libs/plugins/pages/server/src/index.ts +++ b/libs/plugins/pages/server/src/index.ts @@ -1 +1 @@ -export { pagesServerPlugin } from './lib/plugin.js'; +export { pagesServerPlugin } from './lib/plugin'; diff --git a/libs/plugins/pages/server/src/lib/migrations.spec.ts b/libs/plugins/pages/server/src/lib/migrations.spec.ts index 2fbb269..551d349 100644 --- a/libs/plugins/pages/server/src/lib/migrations.spec.ts +++ b/libs/plugins/pages/server/src/lib/migrations.spec.ts @@ -3,7 +3,7 @@ import { afterEach, describe, expect, it } from 'vitest'; import { pagesSchemaMigration, pagesValidationRulesMigration, -} from './migrations.js'; +} from './migrations'; import type { ServerPlatformPluginContext } from '@rod-manager/server-platform'; const databases: Database.Database[] = []; @@ -24,8 +24,8 @@ function createTestContext(): { logger: {} as ServerPlatformPluginContext['services']['logger'], }, }; - pagesSchemaMigration.up(ctx); - pagesValidationRulesMigration.up(ctx); + void pagesSchemaMigration.up(ctx); + void pagesValidationRulesMigration.up(ctx); return { db, ctx }; } @@ -67,9 +67,10 @@ describe('pages migrations', () => { ); const row = db - .prepare<[string], { slug: string; content_md: string }>( - `SELECT slug, content_md FROM pages WHERE slug = ?`, - ) + .prepare< + [string], + { slug: string; content_md: string } + >(`SELECT slug, content_md FROM pages WHERE slug = ?`) .get('community-news'); expect(row).toEqual({ diff --git a/libs/plugins/pages/server/src/lib/plugin.ts b/libs/plugins/pages/server/src/lib/plugin.ts index 1561849..a7a1476 100644 --- a/libs/plugins/pages/server/src/lib/plugin.ts +++ b/libs/plugins/pages/server/src/lib/plugin.ts @@ -1,11 +1,11 @@ import type { ServerPlatformPlugin } from '@rod-manager/server-platform'; -import { createPageStore } from './store.js'; -import { registerPagesRoutes } from './routes.js'; +import { createPageStore } from './store'; +import { registerPagesRoutes } from './routes'; import { pagesSchemaMigration, pagesValidationRulesMigration, pagesSeedMigration, -} from './migrations.js'; +} from './migrations'; /** Creates the pages server plugin descriptor for use with createServerPlatform. */ export function pagesServerPlugin(): ServerPlatformPlugin { diff --git a/libs/plugins/pages/server/src/lib/routes.spec.ts b/libs/plugins/pages/server/src/lib/routes.spec.ts index b4bc7c3..edc0897 100644 --- a/libs/plugins/pages/server/src/lib/routes.spec.ts +++ b/libs/plugins/pages/server/src/lib/routes.spec.ts @@ -4,7 +4,7 @@ import { createServerPlatform, SESSION_COOKIE_NAME, } from '@rod-manager/server-platform'; -import { pagesServerPlugin } from './plugin.js'; +import { pagesServerPlugin } from './plugin'; describe('pages plugin contract tests', () => { beforeEach(() => { diff --git a/libs/plugins/pages/server/src/lib/routes.ts b/libs/plugins/pages/server/src/lib/routes.ts index 69080e9..f30c060 100644 --- a/libs/plugins/pages/server/src/lib/routes.ts +++ b/libs/plugins/pages/server/src/lib/routes.ts @@ -3,7 +3,7 @@ import type { ContentPageListResponseBody, ContentPageResponseBody, } from '@rod-manager/shared'; -import type { PageStore } from './store.js'; +import type { PageStore } from './store'; /** Registers pages API routes on the given Fastify instance. */ export function registerPagesRoutes( diff --git a/libs/plugins/pages/server/tsconfig.lib.json b/libs/plugins/pages/server/tsconfig.lib.json index d47cadc..da9d5e7 100644 --- a/libs/plugins/pages/server/tsconfig.lib.json +++ b/libs/plugins/pages/server/tsconfig.lib.json @@ -7,6 +7,8 @@ "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", "emitDeclarationOnly": true, "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "bundler", "types": ["node"] }, "include": ["src/**/*.ts"], diff --git a/libs/server-platform/package.json b/libs/server-platform/package.json index ae530b6..7b1a28a 100644 --- a/libs/server-platform/package.json +++ b/libs/server-platform/package.json @@ -1,31 +1,31 @@ { "name": "@rod-manager/server-platform", "version": "0.0.1", - "type": "module", "main": "./dist/index.js", - "module": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "@rod-manager/source": "./src/index.ts", "types": "./dist/index.d.ts", - "import": "./dist/index.js", + "require": "./dist/index.js", "default": "./dist/index.js" } }, - "files": ["dist", "!**/*.tsbuildinfo"], + "files": [ + "dist", + "!**/*.tsbuildinfo" + ], "dependencies": { - "@fastify/autoload": "~6.0.3", "@fastify/cookie": "^11.0.2", "@fastify/middie": "^9.0.3", - "@fastify/sensible": "~6.0.2", + "@fastify/sensible": "^6.0.2", "@fastify/static": "^8.3.0", "@rod-manager/shared": "0.0.1", "better-sqlite3": "^12.9.0", "dotenv": "^16.4.7", - "fastify": "5.8.3", - "fastify-plugin": "~5.0.1" + "fastify": "^5.8.3", + "fastify-plugin": "^5.0.1" }, "nx": { "targets": { @@ -38,7 +38,9 @@ }, "build": { "executor": "nx:run-commands", - "outputs": ["{projectRoot}/dist"], + "outputs": [ + "{projectRoot}/dist" + ], "options": { "cwd": "libs/server-platform", "command": "node ../../node_modules/typescript/bin/tsc --build tsconfig.lib.json" diff --git a/libs/server-platform/src/index.ts b/libs/server-platform/src/index.ts index cf5a729..86d99be 100644 --- a/libs/server-platform/src/index.ts +++ b/libs/server-platform/src/index.ts @@ -1,5 +1,5 @@ -export { createServerPlatform } from './lib/createServerPlatform.js'; -export type { ServerPlatformOptions } from './lib/createServerPlatform.js'; +export { createServerPlatform } from './lib/createServerPlatform'; +export type { ServerPlatformOptions } from './lib/createServerPlatform'; export type { ServerPlatformPlugin, ServerPlatformPluginMeta, @@ -14,6 +14,6 @@ export type { ApiErrorResponse, JsonValue, JsonPrimitive, -} from './lib/contracts/plugin.contract.js'; -export type { ServerPlatformCapability } from './lib/contracts/capability.contract.js'; -export { SESSION_COOKIE_NAME } from './lib/plugins/session/index.js'; +} from './lib/contracts/plugin.contract'; +export type { ServerPlatformCapability } from './lib/contracts/capability.contract'; +export { SESSION_COOKIE_NAME } from './lib/plugins/session/index'; diff --git a/libs/server-platform/src/lib/createServerPlatform.ts b/libs/server-platform/src/lib/createServerPlatform.ts index c6f5bc9..379af4f 100644 --- a/libs/server-platform/src/lib/createServerPlatform.ts +++ b/libs/server-platform/src/lib/createServerPlatform.ts @@ -1,15 +1,15 @@ import type { FastifyInstance } from 'fastify'; -import type { ServerPlatformPlugin } from './contracts/plugin.contract.js'; -import { createPluginRegistrar } from './serverPluginRegistry.js'; -import databasePlugin from './plugins/database/index.js'; -import sessionPlugin from './plugins/session/index.js'; -import oauthPlugin from './plugins/oauth/index.js'; -import sensiblePlugin from './plugins/sensible.js'; -import authRoutes from './routes/auth.js'; -import oauthRoutes from './routes/oauth.js'; -import rootRoute from './routes/root.js'; -import ssrRoute from './routes/ssr.js'; -import userSettingsRoutes from './routes/user-settings.js'; +import type { ServerPlatformPlugin } from './contracts/plugin.contract'; +import { createPluginRegistrar } from './serverPluginRegistry'; +import databasePlugin from './plugins/database/index'; +import sessionPlugin from './plugins/session/index'; +import oauthPlugin from './plugins/oauth/index'; +import sensiblePlugin from './plugins/sensible'; +import authRoutes from './routes/auth'; +import oauthRoutes from './routes/oauth'; +import rootRoute from './routes/root'; +import ssrRoute from './routes/ssr'; +import userSettingsRoutes from './routes/user-settings'; export interface ServerPlatformOptions { logLevel?: string; diff --git a/libs/server-platform/src/lib/plugins/database/index.ts b/libs/server-platform/src/lib/plugins/database/index.ts index c5862e1..b2ad6cf 100644 --- a/libs/server-platform/src/lib/plugins/database/index.ts +++ b/libs/server-platform/src/lib/plugins/database/index.ts @@ -10,10 +10,10 @@ import { seedInitialUser, shouldSeedInitialUser, ensureAdministratorExists, -} from './init.js'; -import { createStore } from './store.js'; -import { createUserSettingsStore } from './userSettingsStore.js'; -import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; +} from './init'; +import { createStore } from './store'; +import { createUserSettingsStore } from './userSettingsStore'; +import type { ServerPlatformDbClient } from '../../contracts/plugin.contract'; export type { AuthStore, @@ -22,8 +22,8 @@ export type { OAuthProviderData, OAuthProviderType, UserSettingsStore, -} from './types.js'; -export { createSessionExpiration } from './types.js'; +} from './types'; +export { createSessionExpiration } from './types'; /** * Registers SQLite-backed store for authentication and session persistence. diff --git a/libs/server-platform/src/lib/plugins/database/init.ts b/libs/server-platform/src/lib/plugins/database/init.ts index 301b655..e2ab876 100644 --- a/libs/server-platform/src/lib/plugins/database/init.ts +++ b/libs/server-platform/src/lib/plugins/database/init.ts @@ -2,8 +2,8 @@ import { mkdirSync } from 'node:fs'; import { dirname, resolve } from 'node:path'; import type { UserRole } from '@rod-manager/shared'; import type Database from 'better-sqlite3'; -import type { CountRow } from './types.js'; -import { hashPassword } from './store.js'; +import type { CountRow } from './types'; +import { hashPassword } from './store'; export function getDatabasePath(): string { const configuredPath = process.env.AUTH_DB_PATH ?? 'tmp/auth.sqlite'; diff --git a/libs/server-platform/src/lib/plugins/database/store.ts b/libs/server-platform/src/lib/plugins/database/store.ts index ae9c29a..07a7e15 100644 --- a/libs/server-platform/src/lib/plugins/database/store.ts +++ b/libs/server-platform/src/lib/plugins/database/store.ts @@ -13,8 +13,8 @@ import type { OAuthProviderRow, SessionRow, UserRow, -} from './types.js'; -import { createSessionExpiration } from './types.js'; +} from './types'; +import { createSessionExpiration } from './types'; export type { AuthStore }; diff --git a/libs/server-platform/src/lib/plugins/database/types.ts b/libs/server-platform/src/lib/plugins/database/types.ts index 476c51f..1493cc4 100644 --- a/libs/server-platform/src/lib/plugins/database/types.ts +++ b/libs/server-platform/src/lib/plugins/database/types.ts @@ -3,7 +3,7 @@ import type { UserLanguage, UserRole, } from '@rod-manager/shared'; -import type { ServerPlatformDbClient } from '../../contracts/plugin.contract.js'; +import type { ServerPlatformDbClient } from '../../contracts/plugin.contract'; export type { OAuthProviderType }; diff --git a/libs/server-platform/src/lib/plugins/database/userSettingsStore.ts b/libs/server-platform/src/lib/plugins/database/userSettingsStore.ts index bb2ce20..5fba527 100644 --- a/libs/server-platform/src/lib/plugins/database/userSettingsStore.ts +++ b/libs/server-platform/src/lib/plugins/database/userSettingsStore.ts @@ -1,6 +1,6 @@ import type { UserLanguage } from '@rod-manager/shared'; import type Database from 'better-sqlite3'; -import type { UserSettingsStore } from './types.js'; +import type { UserSettingsStore } from './types'; export function createUserSettingsStore( db: Database.Database, diff --git a/libs/server-platform/src/lib/plugins/oauth/index.ts b/libs/server-platform/src/lib/plugins/oauth/index.ts index aebd618..8283814 100644 --- a/libs/server-platform/src/lib/plugins/oauth/index.ts +++ b/libs/server-platform/src/lib/plugins/oauth/index.ts @@ -1,10 +1,10 @@ import type { FastifyInstance } from 'fastify'; import fp from 'fastify-plugin'; -import { createOAuthConfigs } from './providers.js'; -import { generatePKCE } from './pkce.js'; -import { createOAuthService } from './service.js'; +import { createOAuthConfigs } from './providers'; +import { generatePKCE } from './pkce'; +import { createOAuthService } from './service'; -export type { OAuthConfig, OAuthService, OAuthState } from './types.js'; +export type { OAuthConfig, OAuthService, OAuthState } from './types'; export { generatePKCE }; /** diff --git a/libs/server-platform/src/lib/plugins/oauth/providers.ts b/libs/server-platform/src/lib/plugins/oauth/providers.ts index 9030765..53a8bcb 100644 --- a/libs/server-platform/src/lib/plugins/oauth/providers.ts +++ b/libs/server-platform/src/lib/plugins/oauth/providers.ts @@ -1,5 +1,5 @@ import type { OAuthProviderType } from '@rod-manager/shared'; -import type { OAuthConfig } from './types.js'; +import type { OAuthConfig } from './types'; /** * Build OAuth provider configuration map from environment variables. diff --git a/libs/server-platform/src/lib/plugins/oauth/service.ts b/libs/server-platform/src/lib/plugins/oauth/service.ts index 10760e4..ebbdb5c 100644 --- a/libs/server-platform/src/lib/plugins/oauth/service.ts +++ b/libs/server-platform/src/lib/plugins/oauth/service.ts @@ -1,10 +1,10 @@ import type { OAuthProviderType, OAuthUserInfo } from '@rod-manager/shared'; -import type { OAuthConfig, OAuthService, ProviderTokenResponse } from './types.js'; +import type { OAuthConfig, OAuthService, ProviderTokenResponse } from './types'; import { buildOAuthUserInfo, decodeJwtPayload, normalizeValue, -} from './userInfo.js'; +} from './userInfo'; /** * Create OAuth service instance backed by provider configurations. diff --git a/libs/server-platform/src/lib/plugins/session/checkSession.ts b/libs/server-platform/src/lib/plugins/session/checkSession.ts index a02406b..0d2a69c 100644 --- a/libs/server-platform/src/lib/plugins/session/checkSession.ts +++ b/libs/server-platform/src/lib/plugins/session/checkSession.ts @@ -1,6 +1,6 @@ import type { FastifyRequest } from 'fastify'; -import type { AuthStore, AuthStoreSession } from '../database/index.js'; -import { SESSION_COOKIE_NAME } from './types.js'; +import type { AuthStore, AuthStoreSession } from '../database/index'; +import { SESSION_COOKIE_NAME } from './types'; /** * Reads the session token from request cookies. diff --git a/libs/server-platform/src/lib/plugins/session/index.spec.ts b/libs/server-platform/src/lib/plugins/session/index.spec.ts index 6a3bc46..112b436 100644 --- a/libs/server-platform/src/lib/plugins/session/index.spec.ts +++ b/libs/server-platform/src/lib/plugins/session/index.spec.ts @@ -1,7 +1,7 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from './index.js'; -import databasePlugin from '../database/index.js'; +import sessionPlugin, { SESSION_COOKIE_NAME } from './index'; +import databasePlugin from '../database/index'; describe('session plugin', () => { beforeEach(() => { diff --git a/libs/server-platform/src/lib/plugins/session/index.ts b/libs/server-platform/src/lib/plugins/session/index.ts index 3c5cd91..a2c2606 100644 --- a/libs/server-platform/src/lib/plugins/session/index.ts +++ b/libs/server-platform/src/lib/plugins/session/index.ts @@ -1,19 +1,19 @@ import type { FastifyInstance } from 'fastify'; import fp from 'fastify-plugin'; import cookie from '@fastify/cookie'; -import './types.js'; +import './types'; import { createGetSessionDecorator, getSessionToken, hasSession, -} from './checkSession.js'; +} from './checkSession'; import { createRemoveSessionDecorator, createStartSessionDecorator, -} from './mutateSession.js'; -import { requireAuthenticatedSession } from './requireAuthenticatedSession.js'; +} from './mutateSession'; +import { requireAuthenticatedSession } from './requireAuthenticatedSession'; -export { SESSION_COOKIE_NAME } from './types.js'; +export { SESSION_COOKIE_NAME } from './types'; /** * Registers cookie parsing plus session helpers for request and reply lifecycle. diff --git a/libs/server-platform/src/lib/plugins/session/mutateSession.ts b/libs/server-platform/src/lib/plugins/session/mutateSession.ts index 931de24..1fa9ab6 100644 --- a/libs/server-platform/src/lib/plugins/session/mutateSession.ts +++ b/libs/server-platform/src/lib/plugins/session/mutateSession.ts @@ -1,7 +1,7 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; -import { SESSION_COOKIE_NAME } from './types.js'; -import type { AuthStore, AuthStoreSession } from '../database/index.js'; -import { resolveSessionFromRequest } from './checkSession.js'; +import { SESSION_COOKIE_NAME } from './types'; +import type { AuthStore, AuthStoreSession } from '../database/index'; +import { resolveSessionFromRequest } from './checkSession'; export const COOKIE_OPTIONS = { path: '/', diff --git a/libs/server-platform/src/lib/plugins/session/types.ts b/libs/server-platform/src/lib/plugins/session/types.ts index f2a38bb..1af4c6f 100644 --- a/libs/server-platform/src/lib/plugins/session/types.ts +++ b/libs/server-platform/src/lib/plugins/session/types.ts @@ -1,5 +1,5 @@ import type { preHandlerAsyncHookHandler } from 'fastify'; -import type { AuthStoreSession } from '../database/index.js'; +import type { AuthStoreSession } from '../database/index'; export type RequireAuthenticatedSessionHook = preHandlerAsyncHookHandler; diff --git a/libs/server-platform/src/lib/routes/auth.spec.ts b/libs/server-platform/src/lib/routes/auth.spec.ts index b0761d6..2883de7 100644 --- a/libs/server-platform/src/lib/routes/auth.spec.ts +++ b/libs/server-platform/src/lib/routes/auth.spec.ts @@ -1,10 +1,10 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import type { SessionResponse } from '@rod-manager/shared'; -import databasePlugin from '../plugins/database/index.js'; -import sessionPlugin from '../plugins/session/index.js'; -import authRoutes from './auth.js'; -import { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; +import databasePlugin from '../plugins/database/index'; +import sessionPlugin from '../plugins/session/index'; +import authRoutes from './auth'; +import { SESSION_COOKIE_NAME } from '../plugins/session/index'; describe('auth routes', () => { beforeEach(() => { diff --git a/libs/server-platform/src/lib/routes/oauth.spec.ts b/libs/server-platform/src/lib/routes/oauth.spec.ts index 3921694..284d607 100644 --- a/libs/server-platform/src/lib/routes/oauth.spec.ts +++ b/libs/server-platform/src/lib/routes/oauth.spec.ts @@ -1,11 +1,11 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import type { OAuthProviderType } from '@rod-manager/shared'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; -import databasePlugin from '../plugins/database/index.js'; -import type { OAuthService } from '../plugins/oauth/index.js'; -import authRoutes from './auth.js'; -import oauthRoutes from './oauth.js'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index'; +import databasePlugin from '../plugins/database/index'; +import type { OAuthService } from '../plugins/oauth/index'; +import authRoutes from './auth'; +import oauthRoutes from './oauth'; function createOAuthService(): OAuthService { return { diff --git a/libs/server-platform/src/lib/routes/oauth.ts b/libs/server-platform/src/lib/routes/oauth.ts index 85441da..7b3e685 100644 --- a/libs/server-platform/src/lib/routes/oauth.ts +++ b/libs/server-platform/src/lib/routes/oauth.ts @@ -7,8 +7,8 @@ import type { OAuthProviderType, OAuthProvidersResponseBody, } from '@rod-manager/shared'; -import type { AuthStoreSession } from '../plugins/database/index.js'; -import { generatePKCE } from '../plugins/oauth/index.js'; +import type { AuthStoreSession } from '../plugins/database/index'; +import { generatePKCE } from '../plugins/oauth/index'; interface OAuthStateData { provider: OAuthProviderType; diff --git a/libs/server-platform/src/lib/routes/user-settings.spec.ts b/libs/server-platform/src/lib/routes/user-settings.spec.ts index a5b83a6..ef3b6b7 100644 --- a/libs/server-platform/src/lib/routes/user-settings.spec.ts +++ b/libs/server-platform/src/lib/routes/user-settings.spec.ts @@ -1,9 +1,9 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index.js'; -import databasePlugin from '../plugins/database/index.js'; -import authRoutes from './auth.js'; -import userSettingsRoutes from './user-settings.js'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index'; +import databasePlugin from '../plugins/database/index'; +import authRoutes from './auth'; +import userSettingsRoutes from './user-settings'; describe('user settings routes', () => { beforeEach(() => { diff --git a/libs/server-platform/src/lib/runtime/context.ts b/libs/server-platform/src/lib/runtime/context.ts index ee017da..0ff3b2f 100644 --- a/libs/server-platform/src/lib/runtime/context.ts +++ b/libs/server-platform/src/lib/runtime/context.ts @@ -1 +1 @@ -export type { ServerPlatformPluginContext } from '../contracts/plugin.contract.js'; +export type { ServerPlatformPluginContext } from '../contracts/plugin.contract'; diff --git a/libs/server-platform/src/lib/serverPluginRegistry.ts b/libs/server-platform/src/lib/serverPluginRegistry.ts index 9177074..0a25af6 100644 --- a/libs/server-platform/src/lib/serverPluginRegistry.ts +++ b/libs/server-platform/src/lib/serverPluginRegistry.ts @@ -5,8 +5,37 @@ import type { ServerPlatformPlugin, ServerPlatformPluginContext, ServerPlatformSessionService, -} from './contracts/plugin.contract.js'; -import type { AuthStore } from './plugins/database/types.js'; +} from './contracts/plugin.contract'; +import type { AuthStore } from './plugins/database/types'; + +/** Creates a Fastify plugin that registers the given ServerPlatformPlugin list in order. */ +export function createPluginRegistrar(plugins: ServerPlatformPlugin[]) { + return fp(async function serverPluginRegistrar(fastify: FastifyInstance) { + for (const plugin of plugins) { + await fastify.register( + fp(async function serverPlugin(instance: FastifyInstance) { + const ctx: ServerPlatformPluginContext = { + fastify: instance, + services: { + authStore: createAuthStoreAdapter(instance.authStore), + sessionService: createSessionServiceAdapter(instance.authStore), + db: instance.db, + logger: instance.log, + }, + }; + + if (plugin.migrations) { + for (const migration of plugin.migrations) { + await migration.up(ctx); + } + } + + await plugin.register(ctx); + }), + ); + } + }); +} function createSessionServiceAdapter( authStore: AuthStore, @@ -55,32 +84,3 @@ function createAuthStoreAdapter(authStore: AuthStore): ServerPlatformAuthStore { }, }; } - -/** Creates a Fastify plugin that registers the given ServerPlatformPlugin list in order. */ -export function createPluginRegistrar(plugins: ServerPlatformPlugin[]) { - return fp(async function serverPluginRegistrar(fastify: FastifyInstance) { - for (const plugin of plugins) { - await fastify.register( - fp(async function serverPlugin(instance: FastifyInstance) { - const ctx: ServerPlatformPluginContext = { - fastify: instance, - services: { - authStore: createAuthStoreAdapter(instance.authStore), - sessionService: createSessionServiceAdapter(instance.authStore), - db: instance.db, - logger: instance.log, - }, - }; - - if (plugin.migrations) { - for (const migration of plugin.migrations) { - await migration.up(ctx); - } - } - - await plugin.register(ctx); - }), - ); - } - }); -} diff --git a/libs/server-platform/tsconfig.lib.json b/libs/server-platform/tsconfig.lib.json index 29d699d..b304429 100644 --- a/libs/server-platform/tsconfig.lib.json +++ b/libs/server-platform/tsconfig.lib.json @@ -7,11 +7,11 @@ "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", "emitDeclarationOnly": true, "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "bundler", "types": ["node"] }, "include": ["src/**/*.ts"], "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"], - "references": [ - { "path": "../../libs/shared/tsconfig.lib.json" } - ] + "references": [{ "path": "../../libs/shared/tsconfig.lib.json" }] } diff --git a/package-lock.json b/package-lock.json index 7dc99d3..fef821c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -102,7 +102,7 @@ "@rod-manager/plugin-pages-server": "0.0.1", "@rod-manager/server-platform": "0.0.1", "dotenv": "^16.4.7", - "fastify": "5.8.3" + "fastify": "^5.8.3" } }, "apps/web": { @@ -133,16 +133,15 @@ "name": "@rod-manager/server-platform", "version": "0.0.1", "dependencies": { - "@fastify/autoload": "~6.0.3", "@fastify/cookie": "^11.0.2", "@fastify/middie": "^9.0.3", - "@fastify/sensible": "~6.0.2", + "@fastify/sensible": "^6.0.2", "@fastify/static": "^8.3.0", "@rod-manager/shared": "0.0.1", "better-sqlite3": "^12.9.0", "dotenv": "^16.4.7", - "fastify": "5.8.3", - "fastify-plugin": "~5.0.1" + "fastify": "^5.8.3", + "fastify-plugin": "^5.0.1" } }, "libs/shared": { From 766325e76bc2014b13a80d18ab4df41f4f562334 Mon Sep 17 00:00:00 2001 From: Kamil Sojecki Date: Wed, 15 Apr 2026 20:35:06 +0200 Subject: [PATCH 11/11] fix: Fixed broken relaitons and module settings --- .github/workflows/dependencies-ci.yml | 2 +- .github/workflows/publish-libs.yml | 0 apps/api/tsconfig.app.json | 3 --- libs/plugins/pages/server/tsconfig.lib.json | 14 +++++++++----- libs/plugins/pages/ui/tsconfig.lib.json | 4 +++- libs/server-platform/src/index.ts | 2 +- .../src/lib/createServerPlatform.ts | 6 +++--- .../src/lib/plugins/session/checkSession.ts | 2 +- .../src/lib/plugins/session/index.spec.ts | 2 +- .../src/lib/plugins/session/mutateSession.ts | 2 +- .../src/lib/plugins/session/types.ts | 2 +- libs/server-platform/src/lib/routes/auth.spec.ts | 6 +++--- libs/server-platform/src/lib/routes/oauth.spec.ts | 6 +++--- libs/server-platform/src/lib/routes/oauth.ts | 4 ++-- .../src/lib/routes/user-settings.spec.ts | 4 ++-- .../src/lib/serverPluginRegistry.ts | 2 +- libs/server-platform/tsconfig.lib.json | 12 ++++++++---- libs/ui/src/index.ts | 4 ++-- tsconfig.base.json | 1 + tsconfig.json | 9 +++++++++ 20 files changed, 52 insertions(+), 35 deletions(-) delete mode 100644 .github/workflows/publish-libs.yml diff --git a/.github/workflows/dependencies-ci.yml b/.github/workflows/dependencies-ci.yml index a74eccb..fd1a269 100644 --- a/.github/workflows/dependencies-ci.yml +++ b/.github/workflows/dependencies-ci.yml @@ -1,4 +1,4 @@ -\name: Dependencies CI +name: Dependencies CI on: pull_request: diff --git a/.github/workflows/publish-libs.yml b/.github/workflows/publish-libs.yml deleted file mode 100644 index e69de29..0000000 diff --git a/apps/api/tsconfig.app.json b/apps/api/tsconfig.app.json index 811a7b9..6ee375e 100644 --- a/apps/api/tsconfig.app.json +++ b/apps/api/tsconfig.app.json @@ -15,9 +15,6 @@ "eslint.config.mjs" ], "references": [ - { - "path": "../../libs/shared/tsconfig.lib.json" - }, { "path": "../../libs/server-platform/tsconfig.lib.json" }, diff --git a/libs/plugins/pages/server/tsconfig.lib.json b/libs/plugins/pages/server/tsconfig.lib.json index da9d5e7..3d3d185 100644 --- a/libs/plugins/pages/server/tsconfig.lib.json +++ b/libs/plugins/pages/server/tsconfig.lib.json @@ -5,16 +5,20 @@ "rootDir": "src", "outDir": "dist", "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", - "emitDeclarationOnly": true, + "emitDeclarationOnly": false, "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "bundler", + "module": "nodenext", + "moduleResolution": "nodenext", "types": ["node"] }, "include": ["src/**/*.ts"], "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"], "references": [ - { "path": "../../../../libs/shared/tsconfig.lib.json" }, - { "path": "../../../../libs/server-platform/tsconfig.lib.json" } + { + "path": "../../../shared/tsconfig.lib.json" + }, + { + "path": "../../../server-platform/tsconfig.lib.json" + } ] } diff --git a/libs/plugins/pages/ui/tsconfig.lib.json b/libs/plugins/pages/ui/tsconfig.lib.json index d31b520..8f675b7 100644 --- a/libs/plugins/pages/ui/tsconfig.lib.json +++ b/libs/plugins/pages/ui/tsconfig.lib.json @@ -17,6 +17,8 @@ "src/**/*.test.tsx" ], "references": [ - { "path": "../../../../libs/shared/tsconfig.lib.json" } + { + "path": "../../../shared/tsconfig.lib.json" + } ] } diff --git a/libs/server-platform/src/index.ts b/libs/server-platform/src/index.ts index 86d99be..c0d3de3 100644 --- a/libs/server-platform/src/index.ts +++ b/libs/server-platform/src/index.ts @@ -16,4 +16,4 @@ export type { JsonPrimitive, } from './lib/contracts/plugin.contract'; export type { ServerPlatformCapability } from './lib/contracts/capability.contract'; -export { SESSION_COOKIE_NAME } from './lib/plugins/session/index'; +export { SESSION_COOKIE_NAME } from './lib/plugins/session'; diff --git a/libs/server-platform/src/lib/createServerPlatform.ts b/libs/server-platform/src/lib/createServerPlatform.ts index 379af4f..6bed09a 100644 --- a/libs/server-platform/src/lib/createServerPlatform.ts +++ b/libs/server-platform/src/lib/createServerPlatform.ts @@ -1,9 +1,9 @@ import type { FastifyInstance } from 'fastify'; import type { ServerPlatformPlugin } from './contracts/plugin.contract'; import { createPluginRegistrar } from './serverPluginRegistry'; -import databasePlugin from './plugins/database/index'; -import sessionPlugin from './plugins/session/index'; -import oauthPlugin from './plugins/oauth/index'; +import databasePlugin from './plugins/database'; +import sessionPlugin from './plugins/session'; +import oauthPlugin from './plugins/oauth'; import sensiblePlugin from './plugins/sensible'; import authRoutes from './routes/auth'; import oauthRoutes from './routes/oauth'; diff --git a/libs/server-platform/src/lib/plugins/session/checkSession.ts b/libs/server-platform/src/lib/plugins/session/checkSession.ts index 0d2a69c..c85170b 100644 --- a/libs/server-platform/src/lib/plugins/session/checkSession.ts +++ b/libs/server-platform/src/lib/plugins/session/checkSession.ts @@ -1,5 +1,5 @@ import type { FastifyRequest } from 'fastify'; -import type { AuthStore, AuthStoreSession } from '../database/index'; +import type { AuthStore, AuthStoreSession } from '../database'; import { SESSION_COOKIE_NAME } from './types'; /** diff --git a/libs/server-platform/src/lib/plugins/session/index.spec.ts b/libs/server-platform/src/lib/plugins/session/index.spec.ts index 112b436..e866ffa 100644 --- a/libs/server-platform/src/lib/plugins/session/index.spec.ts +++ b/libs/server-platform/src/lib/plugins/session/index.spec.ts @@ -1,7 +1,7 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import sessionPlugin, { SESSION_COOKIE_NAME } from './index'; -import databasePlugin from '../database/index'; +import databasePlugin from '../database'; describe('session plugin', () => { beforeEach(() => { diff --git a/libs/server-platform/src/lib/plugins/session/mutateSession.ts b/libs/server-platform/src/lib/plugins/session/mutateSession.ts index 1fa9ab6..35d0019 100644 --- a/libs/server-platform/src/lib/plugins/session/mutateSession.ts +++ b/libs/server-platform/src/lib/plugins/session/mutateSession.ts @@ -1,6 +1,6 @@ import type { FastifyReply, FastifyRequest } from 'fastify'; import { SESSION_COOKIE_NAME } from './types'; -import type { AuthStore, AuthStoreSession } from '../database/index'; +import type { AuthStore, AuthStoreSession } from '../database'; import { resolveSessionFromRequest } from './checkSession'; export const COOKIE_OPTIONS = { diff --git a/libs/server-platform/src/lib/plugins/session/types.ts b/libs/server-platform/src/lib/plugins/session/types.ts index 1af4c6f..6cbf6cc 100644 --- a/libs/server-platform/src/lib/plugins/session/types.ts +++ b/libs/server-platform/src/lib/plugins/session/types.ts @@ -1,5 +1,5 @@ import type { preHandlerAsyncHookHandler } from 'fastify'; -import type { AuthStoreSession } from '../database/index'; +import type { AuthStoreSession } from '../database'; export type RequireAuthenticatedSessionHook = preHandlerAsyncHookHandler; diff --git a/libs/server-platform/src/lib/routes/auth.spec.ts b/libs/server-platform/src/lib/routes/auth.spec.ts index 2883de7..ab3e152 100644 --- a/libs/server-platform/src/lib/routes/auth.spec.ts +++ b/libs/server-platform/src/lib/routes/auth.spec.ts @@ -1,10 +1,10 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import type { SessionResponse } from '@rod-manager/shared'; -import databasePlugin from '../plugins/database/index'; -import sessionPlugin from '../plugins/session/index'; +import databasePlugin from '../plugins/database'; +import sessionPlugin from '../plugins/session'; import authRoutes from './auth'; -import { SESSION_COOKIE_NAME } from '../plugins/session/index'; +import { SESSION_COOKIE_NAME } from '../plugins/session'; describe('auth routes', () => { beforeEach(() => { diff --git a/libs/server-platform/src/lib/routes/oauth.spec.ts b/libs/server-platform/src/lib/routes/oauth.spec.ts index 284d607..4baeb2c 100644 --- a/libs/server-platform/src/lib/routes/oauth.spec.ts +++ b/libs/server-platform/src/lib/routes/oauth.spec.ts @@ -1,9 +1,9 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import type { OAuthProviderType } from '@rod-manager/shared'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index'; -import databasePlugin from '../plugins/database/index'; -import type { OAuthService } from '../plugins/oauth/index'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session'; +import databasePlugin from '../plugins/database'; +import type { OAuthService } from '../plugins/oauth'; import authRoutes from './auth'; import oauthRoutes from './oauth'; diff --git a/libs/server-platform/src/lib/routes/oauth.ts b/libs/server-platform/src/lib/routes/oauth.ts index 7b3e685..d61eba2 100644 --- a/libs/server-platform/src/lib/routes/oauth.ts +++ b/libs/server-platform/src/lib/routes/oauth.ts @@ -7,8 +7,8 @@ import type { OAuthProviderType, OAuthProvidersResponseBody, } from '@rod-manager/shared'; -import type { AuthStoreSession } from '../plugins/database/index'; -import { generatePKCE } from '../plugins/oauth/index'; +import type { AuthStoreSession } from '../plugins/database'; +import { generatePKCE } from '../plugins/oauth'; interface OAuthStateData { provider: OAuthProviderType; diff --git a/libs/server-platform/src/lib/routes/user-settings.spec.ts b/libs/server-platform/src/lib/routes/user-settings.spec.ts index ef3b6b7..b9de8b7 100644 --- a/libs/server-platform/src/lib/routes/user-settings.spec.ts +++ b/libs/server-platform/src/lib/routes/user-settings.spec.ts @@ -1,7 +1,7 @@ import Fastify from 'fastify'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session/index'; -import databasePlugin from '../plugins/database/index'; +import sessionPlugin, { SESSION_COOKIE_NAME } from '../plugins/session'; +import databasePlugin from '../plugins/database'; import authRoutes from './auth'; import userSettingsRoutes from './user-settings'; diff --git a/libs/server-platform/src/lib/serverPluginRegistry.ts b/libs/server-platform/src/lib/serverPluginRegistry.ts index 0a25af6..ff46931 100644 --- a/libs/server-platform/src/lib/serverPluginRegistry.ts +++ b/libs/server-platform/src/lib/serverPluginRegistry.ts @@ -6,7 +6,7 @@ import type { ServerPlatformPluginContext, ServerPlatformSessionService, } from './contracts/plugin.contract'; -import type { AuthStore } from './plugins/database/types'; +import type { AuthStore } from './plugins/database'; /** Creates a Fastify plugin that registers the given ServerPlatformPlugin list in order. */ export function createPluginRegistrar(plugins: ServerPlatformPlugin[]) { diff --git a/libs/server-platform/tsconfig.lib.json b/libs/server-platform/tsconfig.lib.json index b304429..7a473ce 100644 --- a/libs/server-platform/tsconfig.lib.json +++ b/libs/server-platform/tsconfig.lib.json @@ -5,13 +5,17 @@ "rootDir": "src", "outDir": "dist", "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", - "emitDeclarationOnly": true, + "emitDeclarationOnly": false, "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "bundler", + "module": "nodenext", + "moduleResolution": "nodenext", "types": ["node"] }, "include": ["src/**/*.ts"], "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"], - "references": [{ "path": "../../libs/shared/tsconfig.lib.json" }] + "references": [ + { + "path": "../shared/tsconfig.lib.json" + } + ] } diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts index b897b5a..3380e28 100644 --- a/libs/ui/src/index.ts +++ b/libs/ui/src/index.ts @@ -8,5 +8,5 @@ export { } from './lib/Typography'; export * from './lib/Link'; export { Page } from './lib/Page'; -export { ModalWindow, type ModalWindowProps } from './lib/ModalWindow/index'; -export type { ModalWindowApi } from './lib/ModalWindow/index'; +export { ModalWindow, type ModalWindowProps } from './lib/ModalWindow'; +export type { ModalWindowApi } from './lib/ModalWindow'; diff --git a/tsconfig.base.json b/tsconfig.base.json index 3ed515a..ff25ea6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -3,6 +3,7 @@ "composite": true, "declarationMap": true, "emitDeclarationOnly": true, + "outDir": "./dist", "importHelpers": true, "isolatedModules": true, "lib": ["es2022"], diff --git a/tsconfig.json b/tsconfig.json index 4127f04..67d253b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,15 @@ }, { "path": "./libs/ui" + }, + { + "path": "./libs/plugins/pages/server" + }, + { + "path": "./libs/plugins/pages/ui" + }, + { + "path": "./libs/server-platform" } ] }