create-m5kdev 0.21.3 → 0.21.5

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 (40) hide show
  1. package/package.json +1 -1
  2. package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +18 -20
  3. package/templates/minimal-app/AGENTS.md.tpl +4 -3
  4. package/templates/minimal-app/apps/email/src/emails/accountDeletionEmail.tsx.tpl +8 -4
  5. package/templates/minimal-app/apps/server/AGENTS.md.tpl +6 -5
  6. package/templates/minimal-app/apps/server/drizzle/generate-schema.ts.tpl +1 -5
  7. package/templates/minimal-app/apps/server/drizzle/seed.ts.tpl +96 -27
  8. package/templates/minimal-app/apps/server/src/app.ts.tpl +84 -64
  9. package/templates/minimal-app/apps/server/src/generated/schema.ts.tpl +13 -0
  10. package/templates/minimal-app/apps/server/src/index.ts.tpl +4 -1
  11. package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +7 -1
  12. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +1 -1
  13. package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +40 -40
  14. package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +5 -11
  15. package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +2 -2
  16. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +4 -4
  17. package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +1 -1
  18. package/templates/minimal-app/apps/server/src/types.ts.tpl +1 -3
  19. package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -0
  20. package/templates/minimal-app/apps/shared/.env.example.tpl +1 -0
  21. package/templates/minimal-app/apps/shared/.env.tpl +1 -0
  22. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +2 -0
  23. package/templates/minimal-app/apps/webapp/package.json.tpl +0 -1
  24. package/templates/minimal-app/apps/webapp/public/push-sw.js +2 -2
  25. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +51 -40
  26. package/templates/minimal-app/apps/webapp/src/Providers.tsx.tpl +21 -11
  27. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +76 -20
  28. package/templates/minimal-app/apps/webapp/src/modules/notification/PushNotificationsPanel.tsx.tpl +12 -16
  29. package/templates/minimal-app/apps/webapp/src/modules/posts/PostsRoute.tsx.tpl +121 -119
  30. package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +5 -3
  31. package/templates/minimal-app/apps/webapp/translations/en/blog-app.json.tpl +13 -1
  32. package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +1 -0
  33. package/templates/minimal-app/pnpm-workspace.yaml.tpl +8 -8
  34. package/templates/minimal-app/apps/server/src/modules.ts.tpl +0 -33
  35. package/templates/minimal-app/apps/server/src/repository.ts.tpl +0 -6
  36. package/templates/minimal-app/apps/server/src/schema-modules.ts.tpl +0 -19
  37. package/templates/minimal-app/apps/server/src/service.ts.tpl +0 -7
  38. package/templates/minimal-app/apps/server/src/trpc.ts.tpl +0 -6
  39. package/templates/minimal-app/apps/server/src/workflow.ts.tpl +0 -4
  40. package/templates/minimal-app/apps/webapp/src/components/TrpcQueryProvider.tsx.tpl +0 -61
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-m5kdev",
3
- "version": "0.21.3",
3
+ "version": "0.21.5",
4
4
  "license": "GPL-3.0-only",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,7 +18,7 @@ apps/<app>/server/src/modules/<module>/<module>.module.ts
18
18
  - Extend `BaseModule<Deps, Tables, Repositories, Services, Routers>` from `@m5kdev/backend/modules/base/base.module`.
19
19
  - Declare local type aliases for each generic slot (`*ModuleDeps`, `*ModuleTables`, `*ModuleRepositories`, `*ModuleServices`, `*ModuleRouters`).
20
20
  - Set `readonly id` to match the module directory name (e.g. `readonly id = "posts"`).
21
- - Declare `dependsOn` as a `readonly` tuple with `as const`. App modules typically depend on framework modules like `AuthModule`.
21
+ - Declare `dbDependsOn` or `dependsOn` as a `readonly` tuple with `as const`. App modules that reference auth tables typically use `dbDependsOn: ["auth"]`.
22
22
  - Override only the hooks the module actually needs; omit the rest.
23
23
  - Keep hook bodies as pure wiring — no business logic.
24
24
  - Use `createBackendRouterMap(namespace, router)` from `@m5kdev/backend/app` in the `trpc()` hook.
@@ -117,35 +117,33 @@ export const postsModule = new PostsModule();
117
117
 
118
118
  ## Registration
119
119
 
120
- Register the module in `apps/<app>/server/src/modules.ts`:
120
+ Register the module in `apps/<app>/server/src/app.ts`:
121
121
 
122
122
  ```typescript
123
- import { defineBackendModules } from "@m5kdev/backend/app";
123
+ import { createBackendApp } from "@m5kdev/backend/app";
124
124
  import { PostsModule } from "./modules/posts/posts.module";
125
125
 
126
- export const postsBackendModule = new PostsModule();
127
-
128
- export const backendModules = defineBackendModules([
129
- emailBackendModule,
130
- authBackendModule,
131
- workflowBackendModule,
132
- notificationBackendModule,
133
- postsBackendModule,
134
- ] as const);
126
+ export const builtBackendApp = createBackendApp(
127
+ {
128
+ // db, express, schema, app, email, auth, ...
129
+ },
130
+ [
131
+ new EmailModule(templates),
132
+ new AuthModule(),
133
+ new WorkflowModule({ /* ... */ }),
134
+ new NotificationModule(),
135
+ new PostsModule(),
136
+ ] as const
137
+ );
135
138
  ```
136
139
 
137
- Then chain `.use(postsBackendModule)` in `apps/<app>/server/src/app.ts`.
138
-
139
140
  ## Accessing Module Output
140
141
 
141
- After building, module repositories and services are re-exported from composition root files:
142
+ After building, module repositories and services are available on `builtBackendApp.modules`:
142
143
 
143
144
  ```typescript
144
- // apps/<app>/server/src/repository.ts
145
- export const postsRepository = builtBackendApp.modules.posts.repositories.posts;
146
-
147
- // apps/<app>/server/src/service.ts
148
- export const postsService = builtBackendApp.modules.posts.services.posts;
145
+ const postsService = builtBackendApp.modules.posts.services.posts;
146
+ const postsRepository = builtBackendApp.modules.posts.repositories.posts;
149
147
  ```
150
148
 
151
149
  ## Patterns
@@ -15,9 +15,9 @@
15
15
 
16
16
  ## Backend Conventions
17
17
 
18
- - Keep repository, service, and transport layers separate.
19
- - Instantiate repositories in `apps/server/src/repository.ts`.
20
- - Instantiate services in `apps/server/src/service.ts`.
18
+ - Keep repository, service, and transport layers separate inside each module.
19
+ - Register modules in `apps/server/src/app.ts` via `createBackendApp(config, [modules])`.
20
+ - Use `BaseModule` subclasses for app modules; the kernel wires repositories, services, and tRPC.
21
21
  - Keep tRPC files focused on input/output wiring and delegate logic to services.
22
22
  - Do not create Drizzle migrations by hand. Use the scaffolded config and your project migration workflow later if you need generated migrations.
23
23
 
@@ -25,5 +25,6 @@
25
25
 
26
26
  - Keep the app shell shape: `NuqsAdapter` + `BrowserRouter` + `Providers`.
27
27
  - Compose global providers only in `apps/webapp/src/Providers.tsx`.
28
+ - Use `AppConfigProvider` and `AppTrpcQueryProvider` from `@m5kdev/frontend`; use `@m5kdev/web-ui` for web-only UI.
28
29
  - Use `nuqs` for URL state, React Router for routing, HeroUI for UI primitives, and Tailwind v4 for styling.
29
30
  - Prefer shared framework utilities from `@m5kdev/frontend` and `@m5kdev/web-ui` before adding local duplicates.
@@ -16,10 +16,14 @@ export default function AccountDeletionEmail({
16
16
  ctaLabel="Review deletion"
17
17
  ctaUrl={url}
18
18
  body={
19
- <Text style={{ margin: "0" }}>
20
- We received a request to delete your {{APP_NAME}} account. If that was you, use the link
21
- below to confirm the action.
22
- </Text>
19
+ <>
20
+ <Text style={{ margin: "0" }}>
21
+ We received a request to delete your {{APP_NAME}} account.
22
+ </Text>
23
+ <Text style={{ margin: "0" }}>
24
+ If that was you, use the link below to confirm the action.
25
+ </Text>
26
+ </>
23
27
  }
24
28
  />
25
29
  );
@@ -19,7 +19,8 @@ apps/server/src/modules/<module>/
19
19
  ├── <module>.db.ts
20
20
  ├── <module>.repository.ts
21
21
  ├── <module>.service.ts
22
- └── <module>.trpc.ts
22
+ ├── <module>.trpc.ts
23
+ └── <module>.module.ts
23
24
  ```
24
25
 
25
26
  ## Layer Boundaries
@@ -27,12 +28,12 @@ apps/server/src/modules/<module>/
27
28
  - Repositories own persistence and query construction.
28
29
  - Services own business rules, orchestration, and context-aware defaults.
29
30
  - tRPC files own transport only and must delegate to services.
30
- - Keep composition explicit in `db.ts`, `repository.ts`, `service.ts`, and `trpc.ts`.
31
+ - Register modules in `apps/server/src/app.ts`; use `db.ts` and `lib/auth.ts` only for scripts that need direct DB or auth access.
31
32
 
32
33
  ## Workflow, Redis, and push notifications
33
34
 
34
- - `workflow.ts` wires BullMQ via **Redis** (`REDIS_URL`). Start Redis locally before `pnpm dev` on the server, or jobs will not run.
35
- - `index.ts` calls `workflowRegistry.registerService(notificationService)` and `await workflowRegistry.start()` before listening.
36
- - After changing Drizzle tables (including `notification_*` tables merged in `db.ts`), run your project’s **Drizzle generate / migrate** command — do not hand-edit SQL migrations in this repo.
35
+ - `WorkflowModule` and `NotificationModule` are registered in `app.ts`. Start **Redis** locally (`REDIS_URL`) before `pnpm dev` on the server, or background jobs will not run.
36
+ - `index.ts` calls `builtBackendApp.start()` before listening and `builtBackendApp.shutdown()` on exit.
37
+ - After changing Drizzle tables, run `pnpm --filter ./apps/server sync` — do not hand-edit SQL migrations in this repo.
37
38
 
38
39
  Push-related server env vars are documented in `apps/shared/.env.example`.
@@ -1,9 +1,5 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import * as authTables from "@m5kdev/backend/modules/auth/auth.db";
4
- import * as notificationTables from "@m5kdev/backend/modules/notification/notification.db";
5
- import * as workflowTables from "@m5kdev/backend/modules/workflow/workflow.db";
6
- import * as postsTables from "../src/modules/posts/posts.db";
7
3
 
8
4
  export async function generateSchema(): Promise<void> {
9
5
  const outputDirectory = path.resolve(process.cwd(), "src/generated");
@@ -24,7 +20,7 @@ export async function generateSchema(): Promise<void> {
24
20
  "",
25
21
  "export type AppDbSchema = typeof schema;",
26
22
  "",
27
- ].join("\n");
23
+ ].join("\r\n");
28
24
 
29
25
  await fs.mkdir(outputDirectory, { recursive: true });
30
26
  await fs.writeFile(outputPath, source, "utf8");
@@ -1,10 +1,13 @@
1
+ import { hashPassword } from "better-auth/crypto";
1
2
  import { eq } from "drizzle-orm";
3
+ import { v4 as uuidv4 } from "uuid";
2
4
  import { orm, schema } from "../src/db";
3
- import { auth } from "../src/lib/auth";
4
5
  import { syncDatabase } from "./sync";
5
6
 
6
7
  const DEMO_EMAIL = "admin@{{APP_SLUG}}.local";
7
8
  const DEMO_PASSWORD = "password1234";
9
+ const ORGANIZATION_ID = "{{APP_SLUG}}-org";
10
+ const TEAM_ID = "{{APP_SLUG}}-team";
8
11
 
9
12
  async function ensureDemoUser() {
10
13
  const [existingUser] = await orm
@@ -17,26 +20,103 @@ async function ensureDemoUser() {
17
20
  return existingUser;
18
21
  }
19
22
 
20
- await auth.api.createUser({
21
- body: {
23
+ const userId = uuidv4();
24
+ const [user] = await orm
25
+ .insert(schema.users)
26
+ .values({
27
+ id: userId,
22
28
  name: "Demo Editor",
23
29
  email: DEMO_EMAIL,
24
- password: DEMO_PASSWORD,
30
+ emailVerified: true,
25
31
  role: "admin",
26
- },
27
- } as never);
32
+ createdAt: new Date(),
33
+ updatedAt: new Date(),
34
+ })
35
+ .returning();
36
+
37
+ if (!user) {
38
+ throw new Error("Failed to create demo user.");
39
+ }
40
+
41
+ await orm.insert(schema.accounts).values({
42
+ id: uuidv4(),
43
+ accountId: user.id,
44
+ providerId: "credential",
45
+ userId: user.id,
46
+ password: await hashPassword(DEMO_PASSWORD),
47
+ createdAt: new Date(),
48
+ updatedAt: new Date(),
49
+ });
28
50
 
29
- const [createdUser] = await orm
51
+ return user;
52
+ }
53
+
54
+ async function ensureOrganization(userId: string) {
55
+ const [existingOrganization] = await orm
30
56
  .select()
31
- .from(schema.users)
32
- .where(eq(schema.users.email, DEMO_EMAIL))
57
+ .from(schema.organizations)
58
+ .where(eq(schema.organizations.id, ORGANIZATION_ID))
33
59
  .limit(1);
34
60
 
35
- if (!createdUser) {
36
- throw new Error("Failed to create demo user.");
61
+ if (!existingOrganization) {
62
+ await orm.insert(schema.organizations).values({
63
+ id: ORGANIZATION_ID,
64
+ name: "{{APP_NAME}}",
65
+ slug: "{{APP_SLUG}}",
66
+ type: "enterprise",
67
+ createdAt: new Date(),
68
+ });
37
69
  }
38
70
 
39
- return createdUser;
71
+ const [existingMember] = await orm
72
+ .select()
73
+ .from(schema.members)
74
+ .where(eq(schema.members.userId, userId))
75
+ .limit(1);
76
+
77
+ if (!existingMember) {
78
+ await orm.insert(schema.members).values({
79
+ id: uuidv4(),
80
+ organizationId: ORGANIZATION_ID,
81
+ userId,
82
+ role: "owner",
83
+ createdAt: new Date(),
84
+ });
85
+ }
86
+
87
+ const [existingTeam] = await orm
88
+ .select()
89
+ .from(schema.teams)
90
+ .where(eq(schema.teams.id, TEAM_ID))
91
+ .limit(1);
92
+
93
+ if (!existingTeam) {
94
+ await orm.insert(schema.teams).values({
95
+ id: TEAM_ID,
96
+ name: "Editorial",
97
+ organizationId: ORGANIZATION_ID,
98
+ createdAt: new Date(),
99
+ updatedAt: new Date(),
100
+ });
101
+ }
102
+
103
+ const [existingTeamMember] = await orm
104
+ .select()
105
+ .from(schema.teamMembers)
106
+ .where(eq(schema.teamMembers.userId, userId))
107
+ .limit(1);
108
+
109
+ if (!existingTeamMember) {
110
+ await orm.insert(schema.teamMembers).values({
111
+ id: uuidv4(),
112
+ teamId: TEAM_ID,
113
+ userId,
114
+ role: "owner",
115
+ createdAt: new Date(),
116
+ });
117
+ }
118
+
119
+ return { organizationId: ORGANIZATION_ID, teamId: TEAM_ID };
40
120
  }
41
121
 
42
122
  async function seedPosts(userId: string, organizationId: string | null, teamId: string | null) {
@@ -64,7 +144,7 @@ async function seedPosts(userId: string, organizationId: string | null, teamId:
64
144
  organizationId,
65
145
  teamId,
66
146
  title: "Three habits that keep CRUD apps from drifting into chaos",
67
- slug: "three-habits-that-keep-crud-apps-focused",
147
+ slug: "three-habits-that-keep-crud{{PACKAGE_SCOPE}}s-focused",
68
148
  excerpt: "A draft about explicit composition, typed contracts, and URL state.",
69
149
  content:
70
150
  "Keep your composition explicit, keep your schemas honest, and keep the URL involved in user intent. Those three choices do most of the architectural work in a small product.",
@@ -90,22 +170,11 @@ async function seedPosts(userId: string, organizationId: string | null, teamId:
90
170
  async function seed() {
91
171
  await syncDatabase();
92
172
  const user = await ensureDemoUser();
173
+ const { organizationId, teamId } = await ensureOrganization(user.id);
93
174
 
94
- const [member] = await orm
95
- .select()
96
- .from(schema.members)
97
- .where(eq(schema.members.userId, user.id))
98
- .limit(1);
99
-
100
- const [teamMember] = await orm
101
- .select()
102
- .from(schema.teamMembers)
103
- .where(eq(schema.teamMembers.userId, user.id))
104
- .limit(1);
105
-
106
- await seedPosts(user.id, member?.organizationId ?? null, teamMember?.teamId ?? null);
175
+ await seedPosts(user.id, organizationId, teamId);
107
176
 
108
177
  console.info(`Seed completed. Demo login: ${DEMO_EMAIL} / ${DEMO_PASSWORD}`);
109
178
  }
110
179
 
111
- seed();
180
+ void seed();
@@ -1,35 +1,25 @@
1
- import { createBackendApp, type InferBackendAppRouter } from "@m5kdev/backend/app";
1
+ import { createBackendApp } from "@m5kdev/backend/app";
2
2
  import { createBetterAuth } from "@m5kdev/backend/modules/auth/auth.lib";
3
- import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server";
3
+ import { AuthModule } from "@m5kdev/backend/modules/auth/auth.module";
4
+ import { EmailModule } from "@m5kdev/backend/modules/email/email.module";
5
+ import { NotificationModule } from "@m5kdev/backend/modules/notification/notification.module";
6
+ import { WorkflowModule } from "@m5kdev/backend/modules/workflow/workflow.module";
7
+ import { templates } from "{{PACKAGE_SCOPE}}/email";
8
+ import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
4
9
  import cors from "cors";
5
10
  import express from "express";
6
- import {
7
- authBackendModule,
8
- emailBackendModule,
9
- notificationBackendModule,
10
- postsBackendModule,
11
- workflowBackendModule,
12
- } from "./modules";
11
+ import { schema } from "./generated/schema";
12
+ import { PostsModule } from "./modules/posts/posts.module";
13
13
 
14
14
  const app = express();
15
15
  const appUrl = process.env.VITE_APP_URL ?? "http://localhost:5173";
16
16
  const serverUrl = process.env.VITE_SERVER_URL ?? "http://localhost:8080";
17
-
18
- app.use(express.json());
19
- app.use(
20
- cors({
21
- origin: [appUrl],
22
- credentials: true,
23
- methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
24
- allowedHeaders: ["Content-Type", "Authorization"],
25
- })
26
- );
27
-
28
17
  const databaseUrl = process.env.DATABASE_URL ?? "file:./local.db";
29
18
  const syncUrl = process.env.TURSO_DATABASE_URL;
30
19
  const authToken = process.env.TURSO_AUTH_TOKEN;
31
20
  const redisUrl = process.env.REDIS_URL ?? "redis://127.0.0.1:6379";
32
21
  const resendApiKey = process.env.RESEND_API_KEY;
22
+ const enableWaitlist = process.env.VITE_ENABLE_WAITLIST === "true";
33
23
 
34
24
  const connection =
35
25
  syncUrl && authToken
@@ -44,52 +34,82 @@ const connection =
44
34
  url: databaseUrl,
45
35
  };
46
36
 
47
- export const backendApp = createBackendApp({
48
- db: connection,
49
- express: app,
50
- app: {
51
- name: "{{APP_NAME}}",
52
- urls: {
53
- web: appUrl,
54
- api: serverUrl,
37
+ app.use(express.json());
38
+ app.use(
39
+ cors({
40
+ origin: [appUrl],
41
+ credentials: true,
42
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
43
+ allowedHeaders: [
44
+ "Content-Type",
45
+ "Authorization",
46
+ "Waitlist-Invitation-Code",
47
+ "Organization-Invitation-Code",
48
+ "Admin-Create-Verified-User",
49
+ ],
50
+ })
51
+ );
52
+
53
+ export const builtBackendApp = createBackendApp(
54
+ {
55
+ db: connection,
56
+ express: app,
57
+ schema: schema as never,
58
+ app: {
59
+ name: APP_NAME,
60
+ urls: {
61
+ web: appUrl,
62
+ api: serverUrl,
63
+ },
55
64
  },
56
- },
57
- redis: {
58
- url: redisUrl,
59
- options: { maxRetriesPerRequest: null },
60
- },
61
- resend: resendApiKey ? { apiKey: resendApiKey } : undefined,
62
- email: {
63
- mode: resendApiKey ? "send" : "store",
64
- from: "no-reply@local.m5kdev.test",
65
- systemNotificationEmail: "ops@local.m5kdev.test",
66
- outputDirectory: ".emails",
67
- },
68
- auth: {
69
- factory({ db, services, appConfig }) {
70
- return createBetterAuth({
71
- orm: db.orm as never,
72
- schema: db.schema as never,
73
- services: {
74
- email: services.email.email,
75
- },
76
- app: appConfig,
77
- config: {
78
- waitlist: false,
79
- },
80
- });
65
+ redis: {
66
+ url: redisUrl,
67
+ options: { maxRetriesPerRequest: null },
68
+ },
69
+ resend: resendApiKey ? { apiKey: resendApiKey } : undefined,
70
+ email: {
71
+ mode: resendApiKey ? "send" : "store",
72
+ from: "no-reply@local.m5kdev.test",
73
+ systemNotificationEmail: "ops@local.m5kdev.test",
74
+ outputDirectory: ".emails",
75
+ },
76
+ auth: {
77
+ factory({ db, services, appConfig }) {
78
+ return createBetterAuth({
79
+ orm: db.orm as never,
80
+ schema: db.schema as never,
81
+ services: {
82
+ email: services.email.email,
83
+ },
84
+ app: appConfig,
85
+ config: {
86
+ waitlist: enableWaitlist,
87
+ provisionedAccountEmailDomain: "provisioned.{{APP_SLUG}}.local",
88
+ },
89
+ options: {
90
+ secret: process.env.BETTER_AUTH_SECRET ?? "QJvCmK9UBuHAWbNr2vV3ROVt8XrBYV5d",
91
+ },
92
+ });
93
+ },
81
94
  },
82
95
  },
83
- })
84
- .use(emailBackendModule)
85
- .use(authBackendModule)
86
- .use(workflowBackendModule)
87
- .use(notificationBackendModule)
88
- .use(postsBackendModule);
96
+ [
97
+ new EmailModule(templates as never),
98
+ new AuthModule(),
99
+ new WorkflowModule({
100
+ queues: {
101
+ fast: { concurrency: 5 },
102
+ },
103
+ defaultQueue: "fast",
104
+ defaults: {
105
+ timeout: 60_000,
106
+ jobOptions: { removeOnComplete: { age: 3600 } },
107
+ },
108
+ }),
109
+ new NotificationModule(),
110
+ new PostsModule(),
111
+ ] as const
112
+ );
89
113
 
90
- export const builtBackendApp = backendApp.build();
91
114
  export const appRouter = builtBackendApp.trpc.router;
92
-
93
- export type AppRouter = InferBackendAppRouter<typeof backendApp>;
94
- export type RouterInputs = inferRouterInputs<AppRouter>;
95
- export type RouterOutputs = inferRouterOutputs<AppRouter>;
115
+ export type AppRouter = typeof appRouter;
@@ -0,0 +1,13 @@
1
+ import * as authTables from "@m5kdev/backend/modules/auth/auth.db";
2
+ import * as notificationTables from "@m5kdev/backend/modules/notification/notification.db";
3
+ import * as workflowTables from "@m5kdev/backend/modules/workflow/workflow.db";
4
+ import * as postsTables from "../modules/posts/posts.db";
5
+
6
+ export const schema = {
7
+ ...authTables,
8
+ ...workflowTables,
9
+ ...notificationTables,
10
+ ...postsTables,
11
+ };
12
+
13
+ export type AppDbSchema = typeof schema;
@@ -1,5 +1,6 @@
1
1
  import type { Server } from "node:http";
2
2
  import { builtBackendApp } from "./app";
3
+
3
4
  const port = process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 8080;
4
5
 
5
6
  let httpServer: Server | undefined;
@@ -38,7 +39,9 @@ async function start(): Promise<void> {
38
39
  await builtBackendApp.start();
39
40
  await new Promise<void>((resolve, reject) => {
40
41
  httpServer = builtBackendApp.express.app.listen(port, () => {
41
- console.info(`Server running at ${process.env.VITE_SERVER_URL ?? `http://localhost:${port}`}`);
42
+ console.info(
43
+ `Server running at ${process.env.VITE_SERVER_URL ?? `http://localhost:${port}`}`
44
+ );
42
45
  resolve();
43
46
  });
44
47
  httpServer.on("error", reject);
@@ -1,6 +1,12 @@
1
1
  import { builtBackendApp } from "../app";
2
2
 
3
- export const auth = builtBackendApp.auth!.instance;
3
+ const authInstance = builtBackendApp.auth?.instance;
4
+
5
+ if (!authInstance) {
6
+ throw new Error("Auth is not configured on the backend app.");
7
+ }
8
+
9
+ export const auth = authInstance;
4
10
 
5
11
  export type Session = typeof auth.$Infer.Session;
6
12
  export type User = Session["user"];
@@ -1,5 +1,5 @@
1
- import { type AnySQLiteColumn, integer, sqliteTable as table, text } from "drizzle-orm/sqlite-core";
2
1
  import { organizations, teams, users } from "@m5kdev/backend/modules/auth/auth.db";
2
+ import { integer, sqliteTable as table, text } from "drizzle-orm/sqlite-core";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
 
5
5
  export const posts = table("posts", {
@@ -1,40 +1,40 @@
1
- import type { ResourceGrant } from "@m5kdev/backend/modules/base/base.grants";
2
-
3
- export const postsGrants: ResourceGrant[] = [
4
- {
5
- action: "write",
6
- level: "team",
7
- role: "owner",
8
- access: "own",
9
- },
10
- {
11
- action: "publish",
12
- level: "team",
13
- role: "owner",
14
- access: "own",
15
- },
16
- {
17
- action: "delete",
18
- level: "team",
19
- role: "owner",
20
- access: "own",
21
- },
22
- {
23
- action: "write",
24
- level: "organization",
25
- role: "owner",
26
- access: "own",
27
- },
28
- {
29
- action: "publish",
30
- level: "organization",
31
- role: "owner",
32
- access: "own",
33
- },
34
- {
35
- action: "delete",
36
- level: "organization",
37
- role: "owner",
38
- access: "own",
39
- },
40
- ];
1
+ import type { ResourceGrant } from "@m5kdev/backend/modules/base/base.grants";
2
+
3
+ export const postsGrants: ResourceGrant[] = [
4
+ {
5
+ action: "write",
6
+ level: "team",
7
+ role: "owner",
8
+ access: "own",
9
+ },
10
+ {
11
+ action: "publish",
12
+ level: "team",
13
+ role: "owner",
14
+ access: "own",
15
+ },
16
+ {
17
+ action: "delete",
18
+ level: "team",
19
+ role: "owner",
20
+ access: "own",
21
+ },
22
+ {
23
+ action: "write",
24
+ level: "organization",
25
+ role: "owner",
26
+ access: "own",
27
+ },
28
+ {
29
+ action: "publish",
30
+ level: "organization",
31
+ role: "owner",
32
+ access: "own",
33
+ },
34
+ {
35
+ action: "delete",
36
+ level: "organization",
37
+ role: "owner",
38
+ access: "own",
39
+ },
40
+ ];