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.
- package/package.json +1 -1
- package/templates/minimal-app/.cursor/rules/module-module-guide.mdc +18 -20
- package/templates/minimal-app/AGENTS.md.tpl +4 -3
- package/templates/minimal-app/apps/email/src/emails/accountDeletionEmail.tsx.tpl +8 -4
- package/templates/minimal-app/apps/server/AGENTS.md.tpl +6 -5
- package/templates/minimal-app/apps/server/drizzle/generate-schema.ts.tpl +1 -5
- package/templates/minimal-app/apps/server/drizzle/seed.ts.tpl +96 -27
- package/templates/minimal-app/apps/server/src/app.ts.tpl +84 -64
- package/templates/minimal-app/apps/server/src/generated/schema.ts.tpl +13 -0
- package/templates/minimal-app/apps/server/src/index.ts.tpl +4 -1
- package/templates/minimal-app/apps/server/src/lib/auth.ts.tpl +7 -1
- package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +1 -1
- package/templates/minimal-app/apps/server/src/modules/posts/posts.grants.ts.tpl +40 -40
- package/templates/minimal-app/apps/server/src/modules/posts/posts.module.ts.tpl +5 -11
- package/templates/minimal-app/apps/server/src/modules/posts/posts.repository.ts.tpl +2 -2
- package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +4 -4
- package/templates/minimal-app/apps/server/src/modules/posts/posts.trpc.ts.tpl +1 -1
- package/templates/minimal-app/apps/server/src/types.ts.tpl +1 -3
- package/templates/minimal-app/apps/server/tsconfig.json.tpl +1 -0
- package/templates/minimal-app/apps/shared/.env.example.tpl +1 -0
- package/templates/minimal-app/apps/shared/.env.tpl +1 -0
- package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +2 -0
- package/templates/minimal-app/apps/webapp/package.json.tpl +0 -1
- package/templates/minimal-app/apps/webapp/public/push-sw.js +2 -2
- package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +51 -40
- package/templates/minimal-app/apps/webapp/src/Providers.tsx.tpl +21 -11
- package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +76 -20
- package/templates/minimal-app/apps/webapp/src/modules/notification/PushNotificationsPanel.tsx.tpl +12 -16
- package/templates/minimal-app/apps/webapp/src/modules/posts/PostsRoute.tsx.tpl +121 -119
- package/templates/minimal-app/apps/webapp/src/utils/trpc.ts.tpl +5 -3
- package/templates/minimal-app/apps/webapp/translations/en/blog-app.json.tpl +13 -1
- package/templates/minimal-app/apps/webapp/tsconfig.json.tpl +1 -0
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +8 -8
- package/templates/minimal-app/apps/server/src/modules.ts.tpl +0 -33
- package/templates/minimal-app/apps/server/src/repository.ts.tpl +0 -6
- package/templates/minimal-app/apps/server/src/schema-modules.ts.tpl +0 -19
- package/templates/minimal-app/apps/server/src/service.ts.tpl +0 -7
- package/templates/minimal-app/apps/server/src/trpc.ts.tpl +0 -6
- package/templates/minimal-app/apps/server/src/workflow.ts.tpl +0 -4
- package/templates/minimal-app/apps/webapp/src/components/TrpcQueryProvider.tsx.tpl +0 -61
package/package.json
CHANGED
|
@@ -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
|
|
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/
|
|
120
|
+
Register the module in `apps/<app>/server/src/app.ts`:
|
|
121
121
|
|
|
122
122
|
```typescript
|
|
123
|
-
import {
|
|
123
|
+
import { createBackendApp } from "@m5kdev/backend/app";
|
|
124
124
|
import { PostsModule } from "./modules/posts/posts.module";
|
|
125
125
|
|
|
126
|
-
export const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
142
|
+
After building, module repositories and services are available on `builtBackendApp.modules`:
|
|
142
143
|
|
|
143
144
|
```typescript
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
-
|
|
20
|
-
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
- `
|
|
35
|
-
- `index.ts` calls `
|
|
36
|
-
- After changing Drizzle tables
|
|
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
|
-
|
|
21
|
-
|
|
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
|
-
|
|
30
|
+
emailVerified: true,
|
|
25
31
|
role: "admin",
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
51
|
+
return user;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function ensureOrganization(userId: string) {
|
|
55
|
+
const [existingOrganization] = await orm
|
|
30
56
|
.select()
|
|
31
|
-
.from(schema.
|
|
32
|
-
.where(eq(schema.
|
|
57
|
+
.from(schema.organizations)
|
|
58
|
+
.where(eq(schema.organizations.id, ORGANIZATION_ID))
|
|
33
59
|
.limit(1);
|
|
34
60
|
|
|
35
|
-
if (!
|
|
36
|
-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
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
|
|
1
|
+
import { createBackendApp } from "@m5kdev/backend/app";
|
|
2
2
|
import { createBetterAuth } from "@m5kdev/backend/modules/auth/auth.lib";
|
|
3
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
+
];
|