@percepta/create 3.1.4 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +8 -8
  2. package/dist/git-ops-C2CIjuce.js +51 -0
  3. package/dist/git-ops-C2CIjuce.js.map +1 -0
  4. package/dist/index.js +1085 -1072
  5. package/dist/index.js.map +1 -0
  6. package/dist/init-CtCp7Tv2.js +52 -0
  7. package/dist/init-CtCp7Tv2.js.map +1 -0
  8. package/dist/status-CKe4aKso.js +48 -0
  9. package/dist/status-CKe4aKso.js.map +1 -0
  10. package/dist/sync-D1vkoofl.js +101 -0
  11. package/dist/sync-D1vkoofl.js.map +1 -0
  12. package/dist/upstream-D-LH_1z4.js +85 -0
  13. package/dist/upstream-D-LH_1z4.js.map +1 -0
  14. package/package.json +23 -24
  15. package/template-versions.json +1 -1
  16. package/templates/monorepo/.github/workflows/access-control.yml +38 -0
  17. package/templates/monorepo/README.md +41 -2
  18. package/templates/monorepo/access/README.md +39 -0
  19. package/templates/monorepo/access/bootstrap-grants.yaml.example +9 -0
  20. package/templates/monorepo/access/dev-grants.yaml.example +19 -0
  21. package/templates/monorepo/access/dev-groups.yaml.example +8 -0
  22. package/templates/monorepo/access/reconcile.yaml.example +11 -0
  23. package/templates/monorepo/auth/README.md +26 -0
  24. package/templates/monorepo/auth/drizzle.config.ts +13 -0
  25. package/templates/monorepo/auth/package.json +32 -0
  26. package/templates/monorepo/auth/scripts/setup-database.ts +57 -0
  27. package/templates/monorepo/auth/src/auth.ts +77 -0
  28. package/templates/monorepo/auth/src/config/database.ts +31 -0
  29. package/templates/monorepo/auth/src/drizzle/db.ts +9 -0
  30. package/templates/monorepo/auth/src/drizzle/migrations/0000_shared_auth.sql +89 -0
  31. package/templates/monorepo/auth/src/drizzle/migrations/meta/_journal.json +13 -0
  32. package/templates/{webapp → monorepo/auth}/src/drizzle/schema/auth/accounts.ts +1 -6
  33. package/templates/{webapp → monorepo/auth}/src/drizzle/schema/auth/sessions.ts +1 -5
  34. package/templates/{webapp → monorepo/auth}/src/drizzle/schema/auth/verifications.ts +0 -4
  35. package/templates/monorepo/auth/src/drizzle/schema/groups.ts +16 -0
  36. package/templates/monorepo/auth/src/drizzle/schema/index.ts +5 -0
  37. package/templates/monorepo/auth/src/drizzle/schema/users.ts +6 -0
  38. package/templates/monorepo/auth/src/index.ts +1 -0
  39. package/templates/monorepo/auth/src/scim/README.md +6 -0
  40. package/templates/monorepo/auth/tsconfig.json +12 -0
  41. package/templates/monorepo/package.json.template +18 -6
  42. package/templates/monorepo/pnpm-workspace.yaml +1 -0
  43. package/templates/webapp/AGENTS.md +13 -6
  44. package/templates/webapp/README.md +34 -18
  45. package/templates/webapp/agent-skills/access-control.md +301 -0
  46. package/templates/webapp/agent-skills/database.md +1 -1
  47. package/templates/webapp/docker-compose.yml +16 -0
  48. package/templates/webapp/env.example.template +9 -0
  49. package/templates/webapp/next.config.ts +1 -0
  50. package/templates/webapp/package.json.template +8 -4
  51. package/templates/webapp/scripts/seed.ts +87 -36
  52. package/templates/webapp/scripts/setup-database.ts +7 -1
  53. package/templates/webapp/scripts/start.sh +0 -9
  54. package/templates/webapp/src/access/access.manifest.ts +15 -0
  55. package/templates/webapp/src/access/schema.zed +7 -0
  56. package/templates/webapp/src/app/(app)/admin/_lib/PrincipalRoleTable.tsx +113 -0
  57. package/templates/webapp/src/app/(app)/admin/_lib/accessAdmin.ts +85 -0
  58. package/templates/webapp/src/app/(app)/admin/groups/page.tsx +117 -0
  59. package/templates/webapp/src/app/(app)/admin/users/page.tsx +79 -0
  60. package/templates/webapp/src/app/(app)/layout.tsx +16 -2
  61. package/templates/webapp/src/app/(app)/page.tsx +1 -12
  62. package/templates/webapp/src/app/(auth)/auth/signin/page.tsx +2 -5
  63. package/templates/webapp/src/app/(auth)/auth/signup/page.tsx +2 -5
  64. package/templates/webapp/src/config/getEnvConfig.ts +8 -0
  65. package/templates/webapp/src/drizzle/db.ts +3 -4
  66. package/templates/webapp/src/drizzle/migrations/0000_eager_grandmaster.sql +1 -57
  67. package/templates/webapp/src/drizzle/migrations/meta/0000_snapshot.json +1 -347
  68. package/templates/webapp/src/drizzle/schema/index.ts +3 -4
  69. package/templates/webapp/src/lib/auth/index.ts +6 -81
  70. package/templates/webapp/src/server/api/root.ts +4 -1
  71. package/templates/webapp/src/server/api/routers/access.ts +13 -0
  72. package/templates/webapp/src/server/trpc.ts +42 -8
  73. package/templates/webapp/src/services/DatabaseService.ts +4 -5
  74. package/templates/webapp/src/services/access/AppAccessControl.ts +39 -0
  75. package/dist/chunk-CO3YWUD6.js +0 -139
  76. package/dist/chunk-DCM7JOSC.js +0 -49
  77. package/dist/chunk-V5EJIUBJ.js +0 -60
  78. package/dist/index.d.ts +0 -1
  79. package/dist/init-EQZ2TCSJ.js +0 -96
  80. package/dist/status-QW5TQDYY.js +0 -76
  81. package/dist/sync-RLBZDOFB.js +0 -136
  82. package/dist/upstream-TQFVPMEG.js +0 -144
  83. package/templates/webapp/scripts/create-user.ts +0 -47
  84. package/templates/webapp/src/drizzle/schema/auth/users.ts +0 -38
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@percepta/create",
3
- "version": "3.1.4",
3
+ "version": "3.2.0",
4
4
  "description": "Scaffold a new Mosaic package",
5
- "type": "module",
5
+ "keywords": [
6
+ "cli",
7
+ "create",
8
+ "mosaic",
9
+ "nextjs",
10
+ "percepta",
11
+ "scaffold",
12
+ "template"
13
+ ],
14
+ "license": "MIT",
6
15
  "bin": {
7
16
  "create": "./dist/index.js"
8
17
  },
@@ -11,6 +20,10 @@
11
20
  "templates",
12
21
  "template-versions.json"
13
22
  ],
23
+ "type": "module",
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
14
27
  "dependencies": {
15
28
  "chalk": "^5.4.1",
16
29
  "commander": "^13.1.0",
@@ -24,36 +37,22 @@
24
37
  "@types/fs-extra": "^11.0.4",
25
38
  "@types/node": "^24.1.0",
26
39
  "@types/validate-npm-package-name": "^4.0.2",
27
- "tsup": "^8.4.0",
28
- "typescript": "^5.7.3",
29
40
  "vitest": "^4.0.0",
30
- "@percepta/build": "0.4.1"
41
+ "@percepta/build": "0.5.1"
31
42
  },
32
43
  "engines": {
33
44
  "node": ">=18.0.0"
34
45
  },
35
- "publishConfig": {
36
- "access": "public"
37
- },
38
- "keywords": [
39
- "create",
40
- "template",
41
- "nextjs",
42
- "mosaic",
43
- "percepta",
44
- "scaffold",
45
- "cli"
46
- ],
47
- "license": "MIT",
48
46
  "scripts": {
49
- "build": "tsup src/index.ts --format esm --dts --clean",
50
- "create:local": "pnpm build && node dist/index.js",
51
- "dev": "tsup src/index.ts --format esm --watch",
47
+ "build": "tsdown",
48
+ "dev": "tsdown --watch",
49
+ "clean": "rimraf dist",
52
50
  "typecheck": "tsc --noEmit",
53
- "sync-template": "tsx scripts/sync-template.ts",
54
- "template:tag": "tsx scripts/template-tag.ts",
55
51
  "test": "vitest run",
56
52
  "test:watch": "vitest",
57
- "test:template": "bash scripts/test-template.sh"
53
+ "test:template": "bash scripts/test-template.sh",
54
+ "create:local": "pnpm build && node dist/index.js",
55
+ "sync-template": "tsx scripts/sync-template.ts",
56
+ "template:tag": "tsx scripts/template-tag.ts"
58
57
  }
59
58
  }
@@ -1,4 +1,4 @@
1
1
  {
2
- "webapp": "1.0.0",
2
+ "webapp": "1.1.0",
3
3
  "library": "1.0.0"
4
4
  }
@@ -0,0 +1,38 @@
1
+ name: Access Control
2
+
3
+ on:
4
+ pull_request: {}
5
+
6
+ env:
7
+ PNPM_VERSION: 10.x
8
+
9
+ jobs:
10
+ access-control:
11
+ name: Merge and Validate Access Schema
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout repository
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup PNPM
19
+ uses: pnpm/action-setup@v4
20
+ with:
21
+ version: ${{ env.PNPM_VERSION }}
22
+
23
+ - name: Setup Node.js
24
+ uses: actions/setup-node@v4
25
+ with:
26
+ node-version: 22
27
+ cache: pnpm
28
+
29
+ - name: Install dependencies
30
+ run: pnpm install --frozen-lockfile
31
+
32
+ - name: Merge and validate access schema
33
+ run: |
34
+ if find packages -path '*/src/access/access.manifest.ts' | grep -q .; then
35
+ pnpm access:validate
36
+ else
37
+ echo "No app access manifests found yet; skipping access schema validation."
38
+ fi
@@ -1,6 +1,6 @@
1
1
  # __APP_TITLE__
2
2
 
3
- A monorepo powered by [pnpm workspaces](https://pnpm.io/workspaces).
3
+ A customer monorepo powered by [pnpm workspaces](https://pnpm.io/workspaces).
4
4
 
5
5
  ## Getting Started
6
6
 
@@ -11,6 +11,12 @@ pnpm install
11
11
  # Run development mode for all packages
12
12
  pnpm dev
13
13
 
14
+ # Set up local services, access-control topology, databases, and seed users
15
+ pnpm run setup
16
+
17
+ # Merge and validate customer access-control schema
18
+ pnpm access:validate
19
+
14
20
  # Build all packages
15
21
  pnpm build
16
22
 
@@ -24,10 +30,43 @@ pnpm lint
24
30
  ## Structure
25
31
 
26
32
  ```
33
+ access/ # Customer-level SpiceDB fixtures and generated merge artifacts
34
+ auth/ # Shared Better Auth users/groups package for this customer
27
35
  packages/
28
- └── your-package/ # Add your packages here
36
+ └── your-package/ # Application and library packages
37
+ ```
38
+
39
+ ## Access Control
40
+
41
+ Application builders define app-local Zed schemas in each package's
42
+ `src/access/` directory. The root access scripts merge those schemas with the
43
+ shared `core/*` schema and apply customer-owned grants such
44
+ as application owners, application members, and bootstrap customer admins.
45
+
46
+ ```bash
47
+ pnpm access:merge
48
+ pnpm access:validate
49
+ pnpm access:apply
29
50
  ```
30
51
 
52
+ PR CI merges the customer schema and runs static schema/manifest validation.
53
+ `access:apply` is reserved for trusted deploy jobs and should run once per
54
+ target environment with that environment's SpiceDB credentials.
55
+
56
+ For local development, copy the example fixture files in `access/`, fill in
57
+ customer-global user/group IDs from `auth/`, then run:
58
+
59
+ ```bash
60
+ pnpm access:seed-grants
61
+ pnpm access:bootstrap-customer-admin -- --subject core/user:<user-id>
62
+ ```
63
+
64
+ ## Shared Auth
65
+
66
+ The `auth/` workspace owns the customer-global Better Auth schema, including
67
+ `users`, `groups`, and `group_members`. Apps should consume this shared
68
+ identity layer instead of creating app-local users or groups.
69
+
31
70
  ## Adding a new package
32
71
 
33
72
  Create a new directory in `packages/` with its own `package.json`:
@@ -0,0 +1,39 @@
1
+ # Customer Access Control
2
+
3
+ This directory owns the customer-level SpiceDB deployment surface:
4
+
5
+ - `pnpm access:merge` combines every app's `src/access/schema.zed` with the shared core schema.
6
+ - `pnpm access:validate` validates the merged schema and app manifests.
7
+ - `pnpm access:apply` writes the merged schema and stable application topology links to SpiceDB.
8
+ - `pnpm access:seed-grants` and `pnpm access:apply-bootstrap-grants` apply YAML fixture grants.
9
+ - `pnpm access:bootstrap-customer-admin` creates the first direct customer-admin grant.
10
+ - `pnpm access:reconcile` repairs SpiceDB from an explicit local projection.
11
+
12
+ The source of truth for app-specific permissions remains the app's authored Zed
13
+ file. This package owns only customer-level composition and customer-admin
14
+ bootstrap.
15
+
16
+ ## Local Bootstrap
17
+
18
+ ```bash
19
+ pnpm access:merge
20
+ pnpm access:validate
21
+ cp access/bootstrap-grants.yaml.example access/bootstrap-grants.yaml
22
+ pnpm access:apply-local
23
+ pnpm access:apply-bootstrap-grants -- --endpoint localhost:50051 --insecure --key dev-spicedb-token
24
+ ```
25
+
26
+ Use `core/user:<users.id>` and `core/group:<groups.id>#member` subjects. The IDs
27
+ come from the shared `auth/` package tables, not from per-app user tables.
28
+
29
+ ## Production Promotion
30
+
31
+ Run `pnpm access:validate` in PR CI. Run `pnpm access:apply` only from trusted deploy jobs
32
+ with the target environment's SpiceDB credentials. Promote the same merged
33
+ schema artifact through environments before app code that depends on new
34
+ relations or permissions.
35
+
36
+ Use expand/contract for destructive changes: add the new shape, deploy
37
+ dual-write/dual-read code, backfill with idempotent relationship writes,
38
+ reconcile, then remove old relationships and schema definitions in a later
39
+ deploy.
@@ -0,0 +1,9 @@
1
+ # Production bootstrap grants for first deploy / break-glass access.
2
+ # Copy to bootstrap-grants.yaml, replace IDs with auth.users.id values, then run:
3
+ # pnpm access:apply-bootstrap-grants
4
+ customerAdmins:
5
+ - userId: "00000000-0000-0000-0000-000000000000"
6
+
7
+ applications: []
8
+ appRoles: []
9
+ resourceRelations: []
@@ -0,0 +1,19 @@
1
+ # Local development grants. Copy to dev-grants.yaml and replace IDs with rows
2
+ # from the shared auth.users / auth.groups tables.
3
+ customerAdmins:
4
+ - userId: "00000000-0000-0000-0000-000000000000"
5
+
6
+ applications:
7
+ - appNamespace: "people_app"
8
+ owners:
9
+ - userId: "00000000-0000-0000-0000-000000000000"
10
+ members:
11
+ - groupId: "11111111-1111-1111-1111-111111111111"
12
+
13
+ appRoles:
14
+ - appNamespace: "people_app"
15
+ role: "admin"
16
+ subjects:
17
+ - groupId: "11111111-1111-1111-1111-111111111111"
18
+
19
+ resourceRelations: []
@@ -0,0 +1,8 @@
1
+ # Future fixture source for the auth-owned groupSync adapter.
2
+ # SCIM/JIT is the production source of truth; this file is only for local dev.
3
+ groups:
4
+ - externalId: "dev-group-admins"
5
+ name: "Development Admins"
6
+ source: "fixture"
7
+ members:
8
+ - userExternalId: "dev-admin"
@@ -0,0 +1,11 @@
1
+ # Explicit local projection for access:reconcile.
2
+ applications:
3
+ - appNamespace: "people_app"
4
+
5
+ groupMemberships:
6
+ - groupId: "11111111-1111-1111-1111-111111111111"
7
+ userIds:
8
+ - "00000000-0000-0000-0000-000000000000"
9
+
10
+ deletedUserIds: []
11
+ deletedGroupIds: []
@@ -0,0 +1,26 @@
1
+ # Shared Auth
2
+
3
+ This workspace owns the customer-global Better Auth database schema. Apps in the
4
+ customer monorepo should import this package for session validation and user /
5
+ group table references instead of creating app-local auth tables.
6
+
7
+ Import auth as `@__APP_NAME__/auth`, the database handle as
8
+ `@__APP_NAME__/auth/db`, and table definitions as `@__APP_NAME__/auth/schema`
9
+ from app packages.
10
+
11
+ The important identity invariant is:
12
+
13
+ - `core/user:<id>` uses `users.id` from this package.
14
+ - `core/group:<id>#member` uses `groups.id` from this package.
15
+ - `group_members` is the local projection that reconcile uses to repair
16
+ SpiceDB group membership relationships.
17
+
18
+ SCIM/JIT protocol handlers are intentionally not implemented here yet; they
19
+ should feed the access-control `groupSync` ingestion contract when that adapter
20
+ lands.
21
+
22
+ ## Database
23
+
24
+ By default this package uses the customer monorepo database name generated at
25
+ scaffold time. Override with `AUTH_DATABASE_NAME` or `AUTH_DATABASE_URL` when
26
+ the shared auth database lives somewhere else.
@@ -0,0 +1,13 @@
1
+ import type { Config } from "drizzle-kit";
2
+ import { getAuthDatabaseConnectionString } from "./src/config/database";
3
+
4
+ const config: Config = {
5
+ schema: "./src/drizzle/schema",
6
+ out: "./src/drizzle/migrations",
7
+ dialect: "postgresql",
8
+ dbCredentials: {
9
+ url: getAuthDatabaseConnectionString(),
10
+ },
11
+ };
12
+
13
+ export default config;
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@__APP_NAME__/auth",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "description": "Shared customer identity package.",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./db": "./src/drizzle/db.ts",
10
+ "./schema": "./src/drizzle/schema/index.ts"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc -p tsconfig.json",
14
+ "db:generate": "drizzle-kit generate",
15
+ "db:migrate": "drizzle-kit migrate",
16
+ "db:setup": "tsx ./scripts/setup-database.ts",
17
+ "db:setup-and-migrate": "pnpm db:setup && pnpm db:migrate"
18
+ },
19
+ "dependencies": {
20
+ "@percepta/access-control": "0.3.2",
21
+ "better-auth": "^1.6.4",
22
+ "drizzle-orm": "^0.45.2",
23
+ "pg": "^8.16.3"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^24.1.0",
27
+ "@types/pg": "^8.15.4",
28
+ "drizzle-kit": "^0.31.4",
29
+ "tsx": "^4.20.3",
30
+ "typescript": "^5.8.3"
31
+ }
32
+ }
@@ -0,0 +1,57 @@
1
+ import { Pool } from "pg";
2
+ import { getAuthDatabaseConfig } from "../src/config/database";
3
+
4
+ const SHARED_DATABASES = new Set(["demos", "internal_apps"]);
5
+
6
+ async function main(): Promise<void> {
7
+ const { database, host, password, port, url, user } = getAuthDatabaseConfig();
8
+
9
+ if (url != null && url.length > 0) {
10
+ console.log("AUTH_DATABASE_URL is set; skipping CREATE DATABASE.");
11
+ return;
12
+ }
13
+
14
+ console.log(`Setting up shared auth database: ${database}`);
15
+ console.log(`Host: ${host}:${port}`);
16
+ console.log(`User: ${user}`);
17
+
18
+ if (SHARED_DATABASES.has(database)) {
19
+ console.log(`${database} is a shared infra-managed database; skipping.`);
20
+ return;
21
+ }
22
+
23
+ const adminClient = new Pool({
24
+ database: "postgres",
25
+ host,
26
+ password,
27
+ port,
28
+ user,
29
+ });
30
+
31
+ try {
32
+ const result = await adminClient.query(
33
+ "SELECT 1 FROM pg_database WHERE datname = $1",
34
+ [database],
35
+ );
36
+
37
+ if (result.rows.length === 0) {
38
+ await adminClient.query(
39
+ `CREATE DATABASE ${escapePgIdentifier(database)}`,
40
+ );
41
+ console.log(`Database ${database} created successfully.`);
42
+ } else {
43
+ console.log(`Database ${database} already exists.`);
44
+ }
45
+ } finally {
46
+ await adminClient.end();
47
+ }
48
+ }
49
+
50
+ function escapePgIdentifier(identifier: string): string {
51
+ return `"${identifier.replaceAll('"', '""')}"`;
52
+ }
53
+
54
+ void main().catch((error) => {
55
+ console.error("Shared auth database setup failed:", error);
56
+ process.exit(1);
57
+ });
@@ -0,0 +1,77 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
3
+ import { admin } from "better-auth/plugins";
4
+ import { db } from "./drizzle/db";
5
+ import { accounts } from "./drizzle/schema/auth/accounts";
6
+ import { sessions } from "./drizzle/schema/auth/sessions";
7
+ import { verifications } from "./drizzle/schema/auth/verifications";
8
+ import { users } from "./drizzle/schema/users";
9
+
10
+ // eslint-disable-next-line n/no-process-env -- detecting Next.js build phase
11
+ const isBuildPhase = process.env.NEXT_PHASE === "phase-production-build";
12
+
13
+ function requiredEnv(name: string): string {
14
+ const value = process.env[name];
15
+ if (value == null || value.length === 0) {
16
+ throw new Error(`${name} is required.`);
17
+ }
18
+ return value;
19
+ }
20
+
21
+ function getSecret(): string {
22
+ if (isBuildPhase) {
23
+ return "build-placeholder-not-used-at-runtime";
24
+ }
25
+
26
+ return requiredEnv("BETTER_AUTH_SECRET");
27
+ }
28
+
29
+ function createAuth() {
30
+ return betterAuth({
31
+ baseURL: process.env.BETTER_AUTH_URL ?? "http://localhost:3000",
32
+ secret: getSecret(),
33
+ database: drizzleAdapter(db, {
34
+ provider: "pg",
35
+ schema: {
36
+ user: users,
37
+ session: sessions,
38
+ account: accounts,
39
+ verification: verifications,
40
+ },
41
+ }),
42
+ emailAndPassword: {
43
+ enabled: true,
44
+ },
45
+ plugins: [admin()],
46
+ advanced: {
47
+ database: {
48
+ generateId: false,
49
+ },
50
+ },
51
+ });
52
+ }
53
+
54
+ type Auth = ReturnType<typeof createAuth>;
55
+
56
+ let authInstance: Auth | undefined;
57
+
58
+ function getAuth(): Auth {
59
+ authInstance ??= createAuth();
60
+ return authInstance;
61
+ }
62
+
63
+ /**
64
+ * Lazy proxy so app builds can import the shared auth package without requiring
65
+ * runtime secrets until Better Auth is actually used.
66
+ */
67
+ export const auth: Auth = new Proxy({} as Auth, {
68
+ get(_target, prop, receiver) {
69
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
70
+ return Reflect.get(getAuth(), prop, receiver);
71
+ },
72
+ has(_target, prop) {
73
+ return Reflect.has(getAuth(), prop);
74
+ },
75
+ });
76
+
77
+ export type BetterAuthSession = typeof auth.$Infer.Session;
@@ -0,0 +1,31 @@
1
+ export interface AuthDatabaseConfig {
2
+ readonly database: string;
3
+ readonly host: string;
4
+ readonly password: string;
5
+ readonly port: number;
6
+ readonly url?: string;
7
+ readonly user: string;
8
+ }
9
+
10
+ export function getAuthDatabaseConfig(): AuthDatabaseConfig {
11
+ return {
12
+ database: process.env.AUTH_DATABASE_NAME ?? "__DB_NAME__",
13
+ host: process.env.DATABASE_HOST ?? "localhost",
14
+ password: process.env.DATABASE_PASSWORD ?? "postgres",
15
+ port: Number(process.env.DATABASE_PORT ?? 5434),
16
+ url: process.env.AUTH_DATABASE_URL,
17
+ user: process.env.DATABASE_USERNAME ?? "postgres",
18
+ };
19
+ }
20
+
21
+ export function getAuthDatabaseConnectionString(): string {
22
+ const { database, host, password, port, url, user } = getAuthDatabaseConfig();
23
+ if (url != null && url.length > 0) {
24
+ return url;
25
+ }
26
+
27
+ return (
28
+ `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}` +
29
+ `@${host}:${port}/${encodeURIComponent(database)}`
30
+ );
31
+ }
@@ -0,0 +1,9 @@
1
+ import { type NodePgDatabase, drizzle } from "drizzle-orm/node-postgres";
2
+ import { Pool } from "pg";
3
+ import { getAuthDatabaseConnectionString } from "../config/database";
4
+ import * as schema from "./schema";
5
+
6
+ export const client = new Pool({
7
+ connectionString: getAuthDatabaseConnectionString(),
8
+ });
9
+ export const db: NodePgDatabase<typeof schema> = drizzle(client, { schema });
@@ -0,0 +1,89 @@
1
+ CREATE TABLE "users" (
2
+ "id" uuid PRIMARY KEY NOT NULL,
3
+ "external_id" text,
4
+ "name" text NOT NULL,
5
+ "email" text NOT NULL,
6
+ "email_verified" boolean DEFAULT false NOT NULL,
7
+ "image" text,
8
+ "role" text DEFAULT 'user' NOT NULL,
9
+ "banned" boolean DEFAULT false,
10
+ "ban_reason" text,
11
+ "ban_expires" timestamp,
12
+ "created_at" timestamp DEFAULT now() NOT NULL,
13
+ "updated_at" timestamp DEFAULT now() NOT NULL,
14
+ CONSTRAINT "users_email_unique" UNIQUE("email")
15
+ );
16
+ --> statement-breakpoint
17
+ CREATE TABLE "account" (
18
+ "id" text PRIMARY KEY NOT NULL,
19
+ "user_id" uuid NOT NULL,
20
+ "account_id" text NOT NULL,
21
+ "provider_id" text NOT NULL,
22
+ "access_token" text,
23
+ "refresh_token" text,
24
+ "expires_at" integer,
25
+ "access_token_expires_at" timestamp,
26
+ "refresh_token_expires_at" timestamp,
27
+ "scope" text,
28
+ "id_token" text,
29
+ "password" text,
30
+ "created_at" timestamp NOT NULL,
31
+ "updated_at" timestamp NOT NULL
32
+ );
33
+ --> statement-breakpoint
34
+ CREATE TABLE "session" (
35
+ "id" text PRIMARY KEY NOT NULL,
36
+ "user_id" uuid NOT NULL,
37
+ "token" text NOT NULL,
38
+ "expires_at" timestamp NOT NULL,
39
+ "ip_address" text,
40
+ "user_agent" text,
41
+ "impersonated_by" text,
42
+ "created_at" timestamp NOT NULL,
43
+ "updated_at" timestamp NOT NULL,
44
+ CONSTRAINT "session_token_unique" UNIQUE("token")
45
+ );
46
+ --> statement-breakpoint
47
+ CREATE TABLE "verification" (
48
+ "id" text PRIMARY KEY NOT NULL,
49
+ "identifier" text NOT NULL,
50
+ "value" text NOT NULL,
51
+ "expires_at" timestamp NOT NULL,
52
+ "created_at" timestamp,
53
+ "updated_at" timestamp
54
+ );
55
+ --> statement-breakpoint
56
+ CREATE TABLE "groups" (
57
+ "id" uuid PRIMARY KEY NOT NULL,
58
+ "external_id" text NOT NULL,
59
+ "name" text NOT NULL,
60
+ "source" text NOT NULL,
61
+ "deleted_at" timestamp,
62
+ "created_at" timestamp DEFAULT now() NOT NULL,
63
+ "updated_at" timestamp DEFAULT now() NOT NULL
64
+ );
65
+ --> statement-breakpoint
66
+ CREATE TABLE "group_members" (
67
+ "group_id" uuid NOT NULL,
68
+ "user_id" uuid NOT NULL,
69
+ "created_at" timestamp DEFAULT now() NOT NULL,
70
+ CONSTRAINT "group_members_pkey" PRIMARY KEY("group_id","user_id")
71
+ );
72
+ --> statement-breakpoint
73
+ ALTER TABLE "account" ADD CONSTRAINT "account_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
74
+ --> statement-breakpoint
75
+ ALTER TABLE "session" ADD CONSTRAINT "session_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
76
+ --> statement-breakpoint
77
+ ALTER TABLE "group_members" ADD CONSTRAINT "group_members_group_id_groups_id_fk" FOREIGN KEY ("group_id") REFERENCES "groups"("id") ON DELETE cascade ON UPDATE no action;
78
+ --> statement-breakpoint
79
+ ALTER TABLE "group_members" ADD CONSTRAINT "group_members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE cascade ON UPDATE no action;
80
+ --> statement-breakpoint
81
+ CREATE UNIQUE INDEX "users_lower_email_index" ON "users" USING btree (lower("email"));
82
+ --> statement-breakpoint
83
+ CREATE UNIQUE INDEX "users_external_id_index" ON "users" USING btree ("external_id") WHERE "users"."external_id" IS NOT NULL;
84
+ --> statement-breakpoint
85
+ CREATE UNIQUE INDEX "groups_live_external_id_index" ON "groups" USING btree ("external_id") WHERE "groups"."deleted_at" IS NULL;
86
+ --> statement-breakpoint
87
+ CREATE INDEX "groups_source_index" ON "groups" USING btree ("source");
88
+ --> statement-breakpoint
89
+ CREATE INDEX "group_members_user_id_index" ON "group_members" USING btree ("user_id");
@@ -0,0 +1,13 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "postgresql",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "7",
8
+ "when": 1777511400000,
9
+ "tag": "0000_shared_auth",
10
+ "breakpoints": true
11
+ }
12
+ ]
13
+ }
@@ -1,11 +1,6 @@
1
1
  import { integer, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
2
- import { users } from "./users";
2
+ import { users } from "../users";
3
3
 
4
- /**
5
- * Better Auth account table.
6
- * Stores OAuth provider links and credential password hashes.
7
- * @see https://better-auth.com/docs/concepts/database
8
- */
9
4
  export const accounts = pgTable("account", {
10
5
  id: text("id")
11
6
  .$defaultFn(() => crypto.randomUUID())
@@ -1,10 +1,6 @@
1
1
  import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
2
- import { users } from "./users";
2
+ import { users } from "../users";
3
3
 
4
- /**
5
- * Better Auth session table.
6
- * @see https://better-auth.com/docs/concepts/database
7
- */
8
4
  export const sessions = pgTable("session", {
9
5
  id: text("id")
10
6
  .$defaultFn(() => crypto.randomUUID())
@@ -1,9 +1,5 @@
1
1
  import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
2
2
 
3
- /**
4
- * Better Auth verification table.
5
- * @see https://better-auth.com/docs/concepts/database
6
- */
7
3
  export const verifications = pgTable("verification", {
8
4
  id: text("id")
9
5
  .$defaultFn(() => crypto.randomUUID())