From 86595c763980472ec96a211a00fa01355f878a99 Mon Sep 17 00:00:00 2001 From: Timothy Yin Date: Mon, 17 Nov 2025 03:34:51 +0800 Subject: [PATCH] feat(csms): add deployment script and database schema for user authentication --- apps/csms/deploy/Dockerfile | 16 + apps/csms/deploy/docker-compose.yml | 12 + apps/csms/deploy/index.js | 1 + apps/csms/deploy/package.json | 13 + apps/csms/drizzle.config.ts | 10 + apps/csms/drizzle/0000_real_garia.sql | 51 + .../drizzle/0001_gorgeous_invisible_woman.sql | 3 + apps/csms/drizzle/meta/0000_snapshot.json | 333 +++++ apps/csms/drizzle/meta/0001_snapshot.json | 352 +++++ apps/csms/drizzle/meta/_journal.json | 20 + apps/csms/esbuild.config.js | 53 + apps/csms/package-lock.json | 1174 +++++++++++++++++ apps/csms/package.json | 23 +- apps/csms/scripts/deploy.js | 153 +++ apps/csms/src/db/auth-schema.ts | 71 + apps/csms/src/db/schema.ts | 1 + apps/csms/src/index.ts | 47 +- apps/csms/src/lib/auth.ts | 27 + apps/csms/src/lib/db.ts | 24 + apps/csms/src/types/db.ts | 0 apps/csms/tsconfig.json | 9 +- 21 files changed, 2377 insertions(+), 16 deletions(-) create mode 100644 apps/csms/deploy/Dockerfile create mode 100644 apps/csms/deploy/docker-compose.yml create mode 100644 apps/csms/deploy/index.js create mode 100644 apps/csms/deploy/package.json create mode 100644 apps/csms/drizzle.config.ts create mode 100644 apps/csms/drizzle/0000_real_garia.sql create mode 100644 apps/csms/drizzle/0001_gorgeous_invisible_woman.sql create mode 100644 apps/csms/drizzle/meta/0000_snapshot.json create mode 100644 apps/csms/drizzle/meta/0001_snapshot.json create mode 100644 apps/csms/drizzle/meta/_journal.json create mode 100644 apps/csms/esbuild.config.js create mode 100644 apps/csms/package-lock.json create mode 100755 apps/csms/scripts/deploy.js create mode 100644 apps/csms/src/db/auth-schema.ts create mode 100644 apps/csms/src/db/schema.ts create mode 100644 apps/csms/src/lib/auth.ts create mode 100644 apps/csms/src/lib/db.ts create mode 100644 apps/csms/src/types/db.ts diff --git a/apps/csms/deploy/Dockerfile b/apps/csms/deploy/Dockerfile new file mode 100644 index 0000000..d880a00 --- /dev/null +++ b/apps/csms/deploy/Dockerfile @@ -0,0 +1,16 @@ +FROM node:22-alpine + +WORKDIR /app + +# 复制应用文件 +COPY package.json . +COPY index.js . + +# 安装依赖 +RUN npm ci --omit=dev + +# 暴露端口 +EXPOSE 3001 + +# 启动应用 +CMD ["node", "index.js"] diff --git a/apps/csms/deploy/docker-compose.yml b/apps/csms/deploy/docker-compose.yml new file mode 100644 index 0000000..922abde --- /dev/null +++ b/apps/csms/deploy/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.8' + +services: + csms: + build: . + ports: + - "3001:3001" + environment: + NODE_ENV: production + # env_file: + # - .env + restart: unless-stopped diff --git a/apps/csms/deploy/index.js b/apps/csms/deploy/index.js new file mode 100644 index 0000000..7cc9bfe --- /dev/null +++ b/apps/csms/deploy/index.js @@ -0,0 +1 @@ +import{serve as c}from"@hono/node-server";import{Hono as a}from"hono";import{createNodeWebSocket as p}from"@hono/node-ws";import{getConnInfo as i}from"@hono/node-server/conninfo";import{cors as m}from"hono/cors";import{logger as l}from"hono/logger";import{showRoutes as g}from"hono/dev";import{readFileSync as d}from"fs";import{dirname as f,join as u}from"path";import{fileURLToPath as h}from"url";var S=f(h(import.meta.url)),s="1.0.0";try{let o=u(S,"../../package.json");s=JSON.parse(d(o,"utf-8")).version||"1.0.0"}catch{}var r=new a,{injectWebSocket:k,upgradeWebSocket:v}=p({app:r});r.use(l());r.use("/ocpp",m({origin:"*",allowMethods:["GET","POST","OPTIONS"],allowHeaders:["Content-Type","Authorization"],exposeHeaders:["Content-Length"],credentials:!0}));r.get("/",o=>o.json({platform:"Helios CSMS",version:s,message:"ok"}));r.get("/ocpp",v(o=>({onOpen(e,t){let n=i(o);console.log(`New connection from ${n.remote.address}:${n.remote.port}`)},onMessage(e,t){console.log(`Received message: ${e.data}`),t.send(`Echo: ${e.data}`)},onClose(e,t){console.log("Connection closed: ",e.code,e.reason)}})));g(r,{verbose:!0});var w=c({fetch:r.fetch,port:3001},o=>{console.log(`Server is running on http://localhost:${o.port}`)});k(w); diff --git a/apps/csms/deploy/package.json b/apps/csms/deploy/package.json new file mode 100644 index 0000000..8bb3195 --- /dev/null +++ b/apps/csms/deploy/package.json @@ -0,0 +1,13 @@ +{ + "name": "csms", + "type": "module", + "dependencies": { + "@hono/node-server": "^1.19.6", + "@hono/node-ws": "^1.2.0", + "better-auth": "^1.3.34", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.44.7", + "hono": "^4.10.6", + "pg": "^8.16.3" + } +} \ No newline at end of file diff --git a/apps/csms/drizzle.config.ts b/apps/csms/drizzle.config.ts new file mode 100644 index 0000000..7014447 --- /dev/null +++ b/apps/csms/drizzle.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'drizzle-kit' + +export default defineConfig({ + out: './drizzle', + schema: './src/db/schema.ts', + dialect: 'postgresql', + dbCredentials: { + url: process.env.DATABASE_CONNECTION_STRING!, + }, +}) diff --git a/apps/csms/drizzle/0000_real_garia.sql b/apps/csms/drizzle/0000_real_garia.sql new file mode 100644 index 0000000..1e592ec --- /dev/null +++ b/apps/csms/drizzle/0000_real_garia.sql @@ -0,0 +1,51 @@ +CREATE TABLE "account" ( + "id" text PRIMARY KEY NOT NULL, + "account_id" text NOT NULL, + "provider_id" text NOT NULL, + "user_id" text NOT NULL, + "access_token" text, + "refresh_token" text, + "id_token" text, + "access_token_expires_at" timestamp, + "refresh_token_expires_at" timestamp, + "scope" text, + "password" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp NOT NULL +); +--> statement-breakpoint +CREATE TABLE "session" ( + "id" text PRIMARY KEY NOT NULL, + "expires_at" timestamp NOT NULL, + "token" text NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp NOT NULL, + "ip_address" text, + "user_agent" text, + "user_id" text NOT NULL, + CONSTRAINT "session_token_unique" UNIQUE("token") +); +--> statement-breakpoint +CREATE TABLE "user" ( + "id" text PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "email" text NOT NULL, + "email_verified" boolean DEFAULT false NOT NULL, + "image" text, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + "role" text DEFAULT 'user', + CONSTRAINT "user_email_unique" UNIQUE("email") +); +--> statement-breakpoint +CREATE TABLE "verification" ( + "id" text PRIMARY KEY NOT NULL, + "identifier" text NOT NULL, + "value" text NOT NULL, + "expires_at" timestamp NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/apps/csms/drizzle/0001_gorgeous_invisible_woman.sql b/apps/csms/drizzle/0001_gorgeous_invisible_woman.sql new file mode 100644 index 0000000..122ae10 --- /dev/null +++ b/apps/csms/drizzle/0001_gorgeous_invisible_woman.sql @@ -0,0 +1,3 @@ +ALTER TABLE "user" ADD COLUMN "username" text;--> statement-breakpoint +ALTER TABLE "user" ADD COLUMN "display_username" text;--> statement-breakpoint +ALTER TABLE "user" ADD CONSTRAINT "user_username_unique" UNIQUE("username"); \ No newline at end of file diff --git a/apps/csms/drizzle/meta/0000_snapshot.json b/apps/csms/drizzle/meta/0000_snapshot.json new file mode 100644 index 0000000..f19c4fc --- /dev/null +++ b/apps/csms/drizzle/meta/0000_snapshot.json @@ -0,0 +1,333 @@ +{ + "id": "a406c0d9-3511-445d-a09c-aa5a409730d7", + "prevId": "00000000-0000-0000-0000-000000000000", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/csms/drizzle/meta/0001_snapshot.json b/apps/csms/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..80a03cf --- /dev/null +++ b/apps/csms/drizzle/meta/0001_snapshot.json @@ -0,0 +1,352 @@ +{ + "id": "0f080c45-a0f2-44c8-ad7b-9587335710d8", + "prevId": "a406c0d9-3511-445d-a09c-aa5a409730d7", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.account": { + "name": "account", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.session": { + "name": "session", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "session_token_unique": { + "name": "session_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user": { + "name": "user", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "display_username": { + "name": "display_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'user'" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + }, + "user_username_unique": { + "name": "user_username_unique", + "nullsNotDistinct": false, + "columns": [ + "username" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verification": { + "name": "verification", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/apps/csms/drizzle/meta/_journal.json b/apps/csms/drizzle/meta/_journal.json new file mode 100644 index 0000000..e6b2089 --- /dev/null +++ b/apps/csms/drizzle/meta/_journal.json @@ -0,0 +1,20 @@ +{ + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1763319356169, + "tag": "0000_real_garia", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1763319683557, + "tag": "0001_gorgeous_invisible_woman", + "breakpoints": true + } + ] +} \ No newline at end of file diff --git a/apps/csms/esbuild.config.js b/apps/csms/esbuild.config.js new file mode 100644 index 0000000..72c0379 --- /dev/null +++ b/apps/csms/esbuild.config.js @@ -0,0 +1,53 @@ +import esbuild from 'esbuild' +import fs from 'fs' +import path from 'path' + +const isProduction = process.env.NODE_ENV === 'production' + +// 读取 package.json 中的所有依赖 +const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8')) +const allDeps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, +} +const externalModules = Object.keys(allDeps) + +const config = { + entryPoints: ['src/index.ts'], + bundle: true, + platform: 'node', + target: 'esnext', + format: 'esm', + outfile: 'dist/index.js', + external: externalModules, + sourcemap: !isProduction, + minify: isProduction, + define: { + 'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`, + }, +} + +async function build() { + try { + console.log('Building with esbuild...') + await esbuild.build(config) + + // 生成 package.json + const packageJson = { type: 'module' } + fs.mkdirSync(path.dirname('dist/package.json'), { recursive: true }) + fs.writeFileSync('dist/package.json', JSON.stringify(packageJson, null, 2)) + + console.log('✓ Build complete!') + console.log(` Entry: ${config.entryPoints[0]}`) + console.log(` Output: ${config.outfile}`) + console.log(` Mode: ${isProduction ? 'production' : 'development'}`) + console.log( + ` Size: ${(fs.statSync(config.outfile).size / 1024).toFixed(1)}KB`, + ) + } catch (error) { + console.error('✗ Build failed:', error.message) + process.exit(1) + } +} + +build() diff --git a/apps/csms/package-lock.json b/apps/csms/package-lock.json new file mode 100644 index 0000000..26f1bc9 --- /dev/null +++ b/apps/csms/package-lock.json @@ -0,0 +1,1174 @@ +{ + "name": "csms", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "csms", + "dependencies": { + "@hono/node-server": "^1.19.6", + "@hono/node-ws": "^1.2.0", + "better-auth": "^1.3.34", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.44.7", + "hono": "^4.10.6", + "pg": "^8.16.3" + }, + "devDependencies": { + "@better-auth/cli": "^1.3.34", + "@types/node": "^20.11.17", + "@types/pg": "^8.15.6", + "drizzle-kit": "^0.31.7", + "esbuild": "^0.27.0", + "tsx": "^4.20.6", + "typescript": "^5.8.3" + } + }, + "../../node_modules/.pnpm/@better-auth+cli@1.3.34_@types+pg@8.15.6_@types+react@19.2.5_kysely@0.28.8_pg@8.16.3_react@19.2.0/node_modules/@better-auth/cli": { + "version": "1.3.34", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.4", + "@babel/preset-react": "^7.27.1", + "@babel/preset-typescript": "^7.27.1", + "@better-auth/utils": "0.3.0", + "@clack/prompts": "^0.11.0", + "@mrleebo/prisma-ast": "^0.13.0", + "@prisma/client": "^5.22.0", + "@types/better-sqlite3": "^7.6.13", + "@types/prompts": "^2.4.9", + "better-auth": "1.3.34", + "better-sqlite3": "^12.2.0", + "c12": "^3.2.0", + "chalk": "^5.6.2", + "commander": "^12.1.0", + "dotenv": "^17.2.2", + "drizzle-orm": "^0.33.0", + "get-tsconfig": "^4.10.1", + "jiti": "^2.6.0", + "open": "^10.2.0", + "prettier": "^3.6.2", + "prisma": "^5.22.0", + "prompts": "^2.4.2", + "semver": "^7.7.2", + "tinyexec": "^0.3.2", + "yocto-spinner": "^0.2.3", + "zod": "^4.1.5" + }, + "bin": { + "cli": "dist/index.mjs" + }, + "devDependencies": { + "@types/semver": "^7.7.1", + "tsx": "^4.20.5", + "typescript": "^5.9.2", + "unbuild": "3.6.1" + } + }, + "../../node_modules/.pnpm/@hono+node-server@1.19.6_hono@4.10.6/node_modules/@hono/node-server": { + "version": "1.19.6", + "license": "MIT", + "devDependencies": { + "@hono/eslint-config": "^1.0.1", + "@types/jest": "^29.5.3", + "@types/node": "^20.10.0", + "@types/supertest": "^2.0.12", + "@whatwg-node/fetch": "^0.9.14", + "eslint": "^9.10.0", + "hono": "^4.4.10", + "jest": "^29.6.1", + "np": "^7.7.0", + "prettier": "^3.2.4", + "publint": "^0.1.16", + "supertest": "^6.3.3", + "ts-jest": "^29.1.1", + "tsup": "^7.2.0", + "typescript": "^5.3.2" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "../../node_modules/.pnpm/@hono+node-ws@1.2.0_@hono+node-server@1.19.6_hono@4.10.6__hono@4.10.6/node_modules/@hono/node-ws": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "ws": "^8.17.0" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.17.4", + "@hono/node-server": "^1.11.1", + "publint": "^0.3.9", + "tsup": "^8.4.0", + "typescript": "^5.8.2", + "vitest": "^3.2.4" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@hono/node-server": "^1.11.1", + "hono": "^4.6.0" + } + }, + "../../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node": { + "version": "20.19.25", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "../../node_modules/.pnpm/@types+pg@8.15.6/node_modules/@types/pg": { + "version": "8.15.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, + "../../node_modules/.pnpm/better-auth@1.3.34_react@19.2.0/node_modules/better-auth": { + "version": "1.3.34", + "license": "MIT", + "dependencies": { + "@better-auth/core": "1.3.34", + "@better-auth/telemetry": "1.3.34", + "@better-auth/utils": "0.3.0", + "@better-fetch/fetch": "1.1.18", + "@noble/ciphers": "^2.0.0", + "@noble/hashes": "^2.0.0", + "@simplewebauthn/browser": "^13.1.2", + "@simplewebauthn/server": "^13.1.2", + "better-call": "1.0.19", + "defu": "^6.1.4", + "jose": "^6.1.0", + "kysely": "^0.28.5", + "nanostores": "^1.0.1", + "zod": "^4.1.5" + }, + "devDependencies": { + "@lynx-js/react": "^0.114.0", + "@prisma/client": "^5.22.0", + "@sveltejs/kit": "^2.37.1", + "@tanstack/react-start": "^1.131.3", + "@tanstack/start-server-core": "^1.131.36", + "@types/better-sqlite3": "^7.6.13", + "@types/bun": "^1.2.20", + "@types/keccak": "^3.0.5", + "@types/pg": "^8.15.5", + "@types/prompts": "^2.4.9", + "@types/react": "^18.3.23", + "better-sqlite3": "^12.2.0", + "concurrently": "^9.2.1", + "deepmerge": "^4.3.1", + "drizzle-kit": "^0.31.4", + "drizzle-orm": "^0.38.2", + "happy-dom": "^18.0.1", + "hono": "^4.9.7", + "listhen": "^1.9.0", + "mongodb": "^6.18.0", + "ms": "4.0.0-nightly.202508271359", + "mysql2": "^3.14.4", + "next": "^15.5.2", + "oauth2-mock-server": "^7.2.1", + "pg": "^8.16.3", + "prisma": "^5.22.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-native": "~0.80.2", + "solid-js": "^1.9.8", + "tarn": "^3.0.2", + "tedious": "^18.6.1", + "type-fest": "^4.41.0", + "typescript": "^5.9.2", + "unbuild": "3.6.1", + "vue": "^3.5.18" + }, + "peerDependenciesMeta": { + "@lynx-js/react": { + "optional": true + }, + "@sveltejs/kit": { + "optional": true + }, + "next": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "solid-js": { + "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv": { + "version": "17.2.3", + "license": "BSD-2-Clause", + "devDependencies": { + "@types/node": "^18.11.3", + "decache": "^4.6.2", + "sinon": "^14.0.1", + "standard": "^17.0.0", + "standard-version": "^9.5.0", + "tap": "^19.2.0", + "typescript": "^4.8.4" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "../../node_modules/.pnpm/drizzle-kit@0.31.7/node_modules/drizzle-kit": { + "version": "0.31.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "esbuild-register": "^3.5.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.15.3", + "@aws-sdk/client-rds-data": "^3.556.0", + "@cloudflare/workers-types": "^4.20230518.0", + "@electric-sql/pglite": "^0.2.12", + "@hono/node-server": "^1.9.0", + "@hono/zod-validator": "^0.2.1", + "@libsql/client": "^0.10.0", + "@neondatabase/serverless": "^0.9.1", + "@originjs/vite-plugin-commonjs": "^1.0.3", + "@planetscale/database": "^1.16.0", + "@types/better-sqlite3": "^7.6.13", + "@types/dockerode": "^3.3.28", + "@types/glob": "^8.1.0", + "@types/json-diff": "^1.0.3", + "@types/micromatch": "^4.0.9", + "@types/minimatch": "^5.1.2", + "@types/node": "^18.11.15", + "@types/pg": "^8.10.7", + "@types/pluralize": "^0.0.33", + "@types/semver": "^7.5.5", + "@types/uuid": "^9.0.8", + "@types/ws": "^8.5.10", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vercel/postgres": "^0.8.0", + "ava": "^5.1.0", + "better-sqlite3": "^11.9.1", + "bun-types": "^0.6.6", + "camelcase": "^7.0.1", + "chalk": "^5.2.0", + "commander": "^12.1.0", + "dockerode": "^4.0.6", + "dotenv": "^16.0.3", + "drizzle-kit": "0.25.0-b1faa33", + "drizzle-orm": "workspace:./drizzle-orm/dist", + "env-paths": "^3.0.0", + "esbuild-node-externals": "^1.9.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", + "gel": "^2.0.0", + "get-port": "^6.1.2", + "glob": "^8.1.0", + "hanji": "^0.0.5", + "hono": "^4.7.9", + "json-diff": "1.0.6", + "micromatch": "^4.0.8", + "minimatch": "^7.4.3", + "mysql2": "3.14.1", + "node-fetch": "^3.3.2", + "ohm-js": "^17.1.0", + "pg": "^8.11.5", + "pluralize": "^8.0.0", + "postgres": "^3.4.4", + "prettier": "^3.5.3", + "semver": "^7.7.2", + "superjson": "^2.2.1", + "tsup": "^8.3.5", + "tsx": "^3.12.1", + "typescript": "^5.6.3", + "uuid": "^9.0.1", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^3.1.3", + "ws": "^8.18.2", + "zod": "^3.20.2", + "zx": "^8.3.2" + } + }, + "../../node_modules/.pnpm/drizzle-orm@0.44.7_@prisma+client@5.22.0_prisma@5.22.0__@types+better-sqlite3@7.6.13_@t_481d83fd1b11a1ab1780a2bff5357ed2/node_modules/drizzle-orm": { + "version": "0.44.7", + "license": "Apache-2.0", + "devDependencies": { + "@aws-sdk/client-rds-data": "^3.549.0", + "@cloudflare/workers-types": "^4.20241112.0", + "@electric-sql/pglite": "^0.2.12", + "@libsql/client": "^0.10.0", + "@libsql/client-wasm": "^0.10.0", + "@miniflare/d1": "^2.14.4", + "@neondatabase/serverless": "^0.10.0", + "@op-engineering/op-sqlite": "^2.0.16", + "@opentelemetry/api": "^1.4.1", + "@originjs/vite-plugin-commonjs": "^1.0.3", + "@planetscale/database": "^1.16.0", + "@prisma/client": "5.14.0", + "@tidbcloud/serverless": "^0.1.1", + "@types/better-sqlite3": "^7.6.12", + "@types/node": "^20.2.5", + "@types/pg": "^8.10.1", + "@types/react": "^18.2.45", + "@types/sql.js": "^1.4.4", + "@upstash/redis": "^1.34.3", + "@vercel/postgres": "^0.8.0", + "@xata.io/client": "^0.29.3", + "better-sqlite3": "^11.9.1", + "bun-types": "^1.2.0", + "cpy": "^10.1.0", + "expo-sqlite": "^14.0.0", + "gel": "^2.0.0", + "glob": "^11.0.1", + "knex": "^2.4.2", + "kysely": "^0.25.0", + "mysql2": "^3.14.1", + "pg": "^8.11.0", + "postgres": "^3.3.5", + "prisma": "5.14.0", + "react": "^18.2.0", + "sql.js": "^1.8.0", + "sqlite3": "^5.1.2", + "ts-morph": "^25.0.1", + "tslib": "^2.5.2", + "tsx": "^3.12.7", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^3.1.3", + "zod": "^3.20.2", + "zx": "^7.2.2" + }, + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, + "../../node_modules/.pnpm/hono@4.10.6/node_modules/hono": { + "version": "4.10.6", + "license": "MIT", + "devDependencies": { + "@hono/eslint-config": "^2.0.3", + "@hono/node-server": "^1.13.5", + "@types/glob": "^9.0.0", + "@types/jsdom": "^21.1.7", + "@types/node": "^24.3.0", + "@typescript/native-preview": "7.0.0-dev.20250523.1", + "@vitest/coverage-v8": "^3.2.4", + "arg": "^5.0.2", + "bun-types": "^1.2.20", + "editorconfig-checker": "^6.1.0", + "esbuild": "^0.15.18", + "eslint": "^9.10.0", + "glob": "^11.0.0", + "jsdom": "22.1.0", + "msw": "^2.6.0", + "np": "10.2.0", + "oxc-parser": "^0.96.0", + "pkg-pr-new": "^0.0.53", + "prettier": "^2.6.2", + "publint": "^0.1.16", + "typescript": "^5.9.2", + "undici": "^6.21.3", + "vite-plugin-fastly-js-compute": "^0.4.2", + "vitest": "^3.2.4", + "wrangler": "4.12.0", + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=16.9.0" + } + }, + "../../node_modules/.pnpm/pg@8.16.3/node_modules/pg": { + "version": "8.16.3", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "devDependencies": { + "@cloudflare/vitest-pool-workers": "0.8.23", + "@cloudflare/workers-types": "^4.20230404.0", + "async": "2.6.4", + "bluebird": "3.7.2", + "co": "4.6.0", + "pg-copy-streams": "0.3.0", + "typescript": "^4.0.3", + "vitest": "~3.0.9", + "wrangler": "^3.x" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "../../node_modules/.pnpm/tsx@4.20.6/node_modules/tsx": { + "version": "4.20.6", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "devDependencies": { + "@dprint/formatter": "^0.4.1", + "@dprint/typescript": "0.93.4", + "@esfx/canceltoken": "^1.0.0", + "@eslint/js": "^9.20.0", + "@octokit/rest": "^21.1.1", + "@types/chai": "^4.3.20", + "@types/diff": "^7.0.1", + "@types/minimist": "^1.2.5", + "@types/mocha": "^10.0.10", + "@types/ms": "^0.7.34", + "@types/node": "latest", + "@types/source-map-support": "^0.5.10", + "@types/which": "^3.0.4", + "@typescript-eslint/rule-tester": "^8.24.1", + "@typescript-eslint/type-utils": "^8.24.1", + "@typescript-eslint/utils": "^8.24.1", + "azure-devops-node-api": "^14.1.0", + "c8": "^10.1.3", + "chai": "^4.5.0", + "chokidar": "^4.0.3", + "diff": "^7.0.0", + "dprint": "^0.49.0", + "esbuild": "^0.25.0", + "eslint": "^9.20.1", + "eslint-formatter-autolinkable-stylish": "^1.4.0", + "eslint-plugin-regexp": "^2.7.0", + "fast-xml-parser": "^4.5.2", + "glob": "^10.4.5", + "globals": "^15.15.0", + "hereby": "^1.10.0", + "jsonc-parser": "^3.3.1", + "knip": "^5.44.4", + "minimist": "^1.2.8", + "mocha": "^10.8.2", + "mocha-fivemat-progress-reporter": "^0.1.0", + "monocart-coverage-reports": "^2.12.1", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "playwright": "^1.50.1", + "source-map-support": "^0.5.21", + "tslib": "^2.8.1", + "typescript": "^5.7.3", + "typescript-eslint": "^8.24.1", + "which": "^3.0.1" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@better-auth/cli": { + "resolved": "../../node_modules/.pnpm/@better-auth+cli@1.3.34_@types+pg@8.15.6_@types+react@19.2.5_kysely@0.28.8_pg@8.16.3_react@19.2.0/node_modules/@better-auth/cli", + "link": true + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "resolved": "../../node_modules/.pnpm/@hono+node-server@1.19.6_hono@4.10.6/node_modules/@hono/node-server", + "link": true + }, + "node_modules/@hono/node-ws": { + "resolved": "../../node_modules/.pnpm/@hono+node-ws@1.2.0_@hono+node-server@1.19.6_hono@4.10.6__hono@4.10.6/node_modules/@hono/node-ws", + "link": true + }, + "node_modules/@types/node": { + "resolved": "../../node_modules/.pnpm/@types+node@20.19.25/node_modules/@types/node", + "link": true + }, + "node_modules/@types/pg": { + "resolved": "../../node_modules/.pnpm/@types+pg@8.15.6/node_modules/@types/pg", + "link": true + }, + "node_modules/better-auth": { + "resolved": "../../node_modules/.pnpm/better-auth@1.3.34_react@19.2.0/node_modules/better-auth", + "link": true + }, + "node_modules/dotenv": { + "resolved": "../../node_modules/.pnpm/dotenv@17.2.3/node_modules/dotenv", + "link": true + }, + "node_modules/drizzle-kit": { + "resolved": "../../node_modules/.pnpm/drizzle-kit@0.31.7/node_modules/drizzle-kit", + "link": true + }, + "node_modules/drizzle-orm": { + "resolved": "../../node_modules/.pnpm/drizzle-orm@0.44.7_@prisma+client@5.22.0_prisma@5.22.0__@types+better-sqlite3@7.6.13_@t_481d83fd1b11a1ab1780a2bff5357ed2/node_modules/drizzle-orm", + "link": true + }, + "node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, + "node_modules/hono": { + "resolved": "../../node_modules/.pnpm/hono@4.10.6/node_modules/hono", + "link": true + }, + "node_modules/pg": { + "resolved": "../../node_modules/.pnpm/pg@8.16.3/node_modules/pg", + "link": true + }, + "node_modules/tsx": { + "resolved": "../../node_modules/.pnpm/tsx@4.20.6/node_modules/tsx", + "link": true + }, + "node_modules/typescript": { + "resolved": "../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript", + "link": true + } + } +} diff --git a/apps/csms/package.json b/apps/csms/package.json index 73f562a..d928c46 100644 --- a/apps/csms/package.json +++ b/apps/csms/package.json @@ -3,17 +3,32 @@ "type": "module", "scripts": { "dev": "tsx watch src/index.ts", - "build": "tsc", - "start": "node dist/index.js" + "build": "node esbuild.config.js", + "build:prod": "NODE_ENV=production node esbuild.config.js", + "start": "node dist/index.js", + "deploy": "node scripts/deploy.js", + "deploy:docker": "node scripts/deploy.js --docker", + "db:gen:auth": "npx @better-auth/cli generate --output src/db/auth-schema.ts", + "db:gen": "drizzle-kit generate", + "db:migrate": "drizzle-kit migrate", + "db:push": "drizzle-kit push" }, "dependencies": { "@hono/node-server": "^1.19.6", "@hono/node-ws": "^1.2.0", - "hono": "^4.10.6" + "better-auth": "^1.3.34", + "dotenv": "^17.2.3", + "drizzle-orm": "^0.44.7", + "hono": "^4.10.6", + "pg": "^8.16.3" }, "devDependencies": { + "@better-auth/cli": "^1.3.34", "@types/node": "^20.11.17", - "tsx": "^4.7.1", + "@types/pg": "^8.15.6", + "drizzle-kit": "^0.31.7", + "esbuild": "^0.27.0", + "tsx": "^4.20.6", "typescript": "^5.8.3" } } \ No newline at end of file diff --git a/apps/csms/scripts/deploy.js b/apps/csms/scripts/deploy.js new file mode 100755 index 0000000..cb7d5b5 --- /dev/null +++ b/apps/csms/scripts/deploy.js @@ -0,0 +1,153 @@ +#!/usr/bin/env node + +/** + * 部署助手脚本 + * + * 使用: + * node scripts/deploy.js # 标准部署(生产构建 + 依赖) + * node scripts/deploy.js --docker # Docker 镜像 + * node scripts/deploy.js --help # 显示帮助 + */ + +import fs from 'fs' +import path from 'path' +import { execSync } from 'child_process' + +const args = process.argv.slice(2) +const help = args.includes('--help') || args.includes('-h') +const isDocker = args.includes('--docker') +const outDir = args.includes('--out') ? args[args.indexOf('--out') + 1] : 'dist' + +if (help) { + console.log(` +Usage: node scripts/deploy.js [options] + +Options: + --docker 生成 Dockerfile + --out PATH 输出目录(默认: dist) + --help 显示此帮助信息 + +Examples: + node scripts/deploy.js 生产构建 + node scripts/deploy.js --out build 输出到 build 目录 + node scripts/deploy.js --docker 生成 Docker 配置 +`) + process.exit(0) +} + +console.log('🚀 开始部署流程...\n') + +try { + // 1. 生产构建 + console.log('📦 构建应用...') + execSync('npm run build:prod', { stdio: 'inherit', cwd: process.cwd() }) + + // 2. 创建部署目录 + console.log('\n📁 准备部署目录...') + const deployDir = path.join(process.cwd(), 'deploy') + + if (fs.existsSync(deployDir)) { + fs.rmSync(deployDir, { recursive: true }) + } + fs.mkdirSync(deployDir, { recursive: true }) + + // 3. 复制必要文件 + console.log('📋 复制文件...') + + // 复制构建输出 + fs.copyFileSync( + path.join(process.cwd(), 'dist/index.js'), + path.join(deployDir, 'index.js'), + ) + fs.copyFileSync( + path.join(process.cwd(), 'dist/package.json'), + path.join(deployDir, 'package.json'), + ) + + // 复制 package.json(用于 npm install) + const srcPkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')) + const deployPkg = { + name: srcPkg.name, + version: srcPkg.version, + type: 'module', + dependencies: srcPkg.dependencies, + } + fs.writeFileSync( + path.join(deployDir, 'package.json'), + JSON.stringify(deployPkg, null, 2), + ) + + // 复制 .env 模板 + if (fs.existsSync('.env.example')) { + fs.copyFileSync('.env.example', path.join(deployDir, '.env.example')) + } + + console.log(`✅ 部署文件已生成到 ${path.relative(process.cwd(), deployDir)}/`) + + // 4. 可选:生成 Docker 配置 + if (isDocker) { + console.log('\n🐳 生成 Docker 配置...') + + const dockerfile = `FROM node:22-alpine + +WORKDIR /app + +# 复制应用文件 +COPY package.json . +COPY index.js . + +# 安装依赖 +RUN npm ci --omit=dev + +# 暴露端口 +EXPOSE 3001 + +# 启动应用 +CMD ["node", "index.js"] +` + + const dockerCompose = `version: '3.8' + +services: + csms: + build: . + ports: + - "3001:3001" + environment: + NODE_ENV: production + # env_file: + # - .env + restart: unless-stopped +` + + fs.writeFileSync(path.join(deployDir, 'Dockerfile'), dockerfile) + fs.writeFileSync(path.join(deployDir, 'docker-compose.yml'), dockerCompose) + + console.log('✅ Docker 文件已生成') + } + + // 5. 显示部署信息 + console.log('\n📊 部署信息:') + console.log(` 名称: ${deployPkg.name}`) + console.log(` 版本: ${deployPkg.version}`) + console.log(` 主文件: index.js`) + console.log(` 依赖数: ${Object.keys(deployPkg.dependencies).length}`) + + const indexSize = fs.statSync(path.join(deployDir, 'index.js')).size + console.log(` 代码大小: ${(indexSize / 1024).toFixed(1)}KB`) + + console.log('\n✨ 部署准备完成!\n') + console.log('下一步:') + console.log(` 1. cd ${path.relative(process.cwd(), deployDir)}`) + console.log(` 2. npm install --omit=dev`) + console.log(` 3. node index.js`) + + if (isDocker) { + console.log('\n或使用 Docker:') + console.log(` 1. cd ${path.relative(process.cwd(), deployDir)}`) + console.log(` 2. docker compose up`) + } +} catch (error) { + console.error('\n❌ 部署失败:', error.message) + process.exit(1) +} diff --git a/apps/csms/src/db/auth-schema.ts b/apps/csms/src/db/auth-schema.ts new file mode 100644 index 0000000..d0cc27c --- /dev/null +++ b/apps/csms/src/db/auth-schema.ts @@ -0,0 +1,71 @@ +import { pgTable, text, timestamp, boolean } from "drizzle-orm/pg-core"; + +export const user = pgTable("user", { + id: text("id").primaryKey(), + name: text("name").notNull(), + email: text("email").notNull().unique(), + emailVerified: boolean("email_verified").default(false).notNull(), + image: text("image"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + username: text("username").unique(), + displayUsername: text("display_username"), + role: text("role").default("user"), +}); + +export const session = pgTable("session", { + id: text("id").primaryKey(), + expiresAt: timestamp("expires_at").notNull(), + token: text("token").notNull().unique(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), + ipAddress: text("ip_address"), + userAgent: text("user_agent"), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), +}); + +export const account = pgTable("account", { + id: text("id").primaryKey(), + accountId: text("account_id").notNull(), + providerId: text("provider_id").notNull(), + userId: text("user_id") + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + accessToken: text("access_token"), + refreshToken: text("refresh_token"), + idToken: text("id_token"), + accessTokenExpiresAt: timestamp("access_token_expires_at"), + refreshTokenExpiresAt: timestamp("refresh_token_expires_at"), + scope: text("scope"), + password: text("password"), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), +}); + +export const verification = pgTable("verification", { + id: text("id").primaryKey(), + identifier: text("identifier").notNull(), + value: text("value").notNull(), + expiresAt: timestamp("expires_at").notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at") + .defaultNow() + .$onUpdate(() => /* @__PURE__ */ new Date()) + .notNull(), +}); + +export const jwks = pgTable("jwks", { + id: text("id").primaryKey(), + publicKey: text("public_key").notNull(), + privateKey: text("private_key").notNull(), + createdAt: timestamp("created_at").notNull(), +}); diff --git a/apps/csms/src/db/schema.ts b/apps/csms/src/db/schema.ts new file mode 100644 index 0000000..c3c6835 --- /dev/null +++ b/apps/csms/src/db/schema.ts @@ -0,0 +1 @@ +export * from './auth-schema.ts' diff --git a/apps/csms/src/index.ts b/apps/csms/src/index.ts index cd15be5..4d3ccd7 100644 --- a/apps/csms/src/index.ts +++ b/apps/csms/src/index.ts @@ -1,4 +1,4 @@ -import pkg from '../../../package.json' with { type: 'json' } +import 'dotenv/config' import { serve } from '@hono/node-server' import { Hono } from 'hono' import { createNodeWebSocket } from '@hono/node-ws' @@ -6,25 +6,48 @@ import { getConnInfo } from '@hono/node-server/conninfo' import { cors } from 'hono/cors' import { logger } from 'hono/logger' import { showRoutes } from 'hono/dev' +import { auth } from './lib/auth.ts' -const app = new Hono() +const app = new Hono<{ + Variables: { + user: typeof auth.$Infer.Session.user | null + session: typeof auth.$Infer.Session.session | null + } +}>() const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }) app.use(logger()) -app.use('/ocpp', cors({ - origin: '*', - allowMethods: ['GET', 'POST', 'OPTIONS'], - allowHeaders: ['Content-Type', 'Authorization'], - exposeHeaders: ['Content-Length'], - credentials: true, -})) +app.use('*', async (c, next) => { + const session = await auth.api.getSession({ headers: c.req.raw.headers }) + if (!session) { + c.set('user', null) + c.set('session', null) + await next() + return + } + c.set('user', session.user) + c.set('session', session.session) + await next() +}) + +app.use( + '/api/auth/*', + cors({ + origin: '*', + allowMethods: ['GET', 'POST', 'OPTIONS'], + allowHeaders: ['Content-Type', 'Authorization'], + exposeHeaders: ['Content-Length'], + credentials: true, + }), +) + +app.on(['POST', 'GET'], '/api/auth/*', (c) => auth.handler(c.req.raw)) app.get('/', (c) => { return c.json({ platform: 'Helios CSMS', - version: pkg.version, message: 'ok', }) }) @@ -35,7 +58,9 @@ app.get( return { onOpen(evt, ws) { const connInfo = getConnInfo(c) - console.log(`New connection from ${connInfo.remote.address}:${connInfo.remote.port}`) + console.log( + `New connection from ${connInfo.remote.address}:${connInfo.remote.port}`, + ) }, onMessage(evt, ws) { console.log(`Received message: ${evt.data}`) diff --git a/apps/csms/src/lib/auth.ts b/apps/csms/src/lib/auth.ts new file mode 100644 index 0000000..920af7d --- /dev/null +++ b/apps/csms/src/lib/auth.ts @@ -0,0 +1,27 @@ +import { betterAuth } from 'better-auth' +import { drizzleAdapter } from 'better-auth/adapters/drizzle' +import { useDrizzle } from './db.js' +import * as schema from '@/db/schema.ts' +import { bearer, jwt, username } from 'better-auth/plugins' + +export const auth = betterAuth({ + database: drizzleAdapter(useDrizzle(), { + provider: 'pg', + schema: { + ...schema, + }, + }), + user: { + additionalFields: { + role: { + type: 'string', + defaultValue: 'user', + input: false, + }, + }, + }, + emailAndPassword: { + enabled: true, + }, + plugins: [username(), bearer(), jwt()], +}) diff --git a/apps/csms/src/lib/db.ts b/apps/csms/src/lib/db.ts new file mode 100644 index 0000000..e0b5e7e --- /dev/null +++ b/apps/csms/src/lib/db.ts @@ -0,0 +1,24 @@ +import { drizzle } from 'drizzle-orm/node-postgres' +import { Pool } from 'pg' +import * as schema from '@/db/schema.js' + +let pgPoolInstance: Pool | null = null +let drizzleInstance: ReturnType | null = null + +export const useDrizzle = () => { + if (!pgPoolInstance || !drizzleInstance) { + pgPoolInstance = new Pool({ + connectionString: process.env.DATABASE_CONNECTION_STRING, + }) + drizzleInstance = drizzle({ client: pgPoolInstance, schema }) + } + return drizzleInstance +} + +export const closeDrizzle = () => { + if (pgPoolInstance) { + pgPoolInstance.end() + pgPoolInstance = null + drizzleInstance = null + } +} diff --git a/apps/csms/src/types/db.ts b/apps/csms/src/types/db.ts new file mode 100644 index 0000000..e69de29 diff --git a/apps/csms/tsconfig.json b/apps/csms/tsconfig.json index b55223e..9af5144 100644 --- a/apps/csms/tsconfig.json +++ b/apps/csms/tsconfig.json @@ -5,12 +5,19 @@ "strict": true, "verbatimModuleSyntax": true, "skipLibCheck": true, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "noEmit": true, "types": [ "node" ], "jsx": "react-jsx", "jsxImportSource": "hono/jsx", - "outDir": "./dist" + "outDir": "./dist", + "paths": { + "@/*": ["./src/*"] + } }, + "include": ["src/**/*", "drizzle.config.ts"], "exclude": ["node_modules"] }