create-m5kdev 0.35.0 → 0.36.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 (47) hide show
  1. package/package.json +1 -1
  2. package/templates/minimal-app/apps/e2e/servers.ts.tpl +3 -0
  3. package/templates/minimal-app/apps/e2e/tests/expo-helpers.ts.tpl +2 -34
  4. package/templates/minimal-app/apps/e2e/tests/expo-standard.spec.ts.tpl +225 -5
  5. package/templates/minimal-app/apps/e2e/tests/helpers.ts.tpl +47 -1
  6. package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +290 -8
  7. package/templates/minimal-app/apps/expo/app.json.tpl +1 -0
  8. package/templates/minimal-app/apps/expo/package.json.tpl +4 -0
  9. package/templates/minimal-app/apps/expo/src/app/_layout.tsx.tpl +4 -1
  10. package/templates/minimal-app/apps/expo/src/app/notifications.tsx.tpl +70 -0
  11. package/templates/minimal-app/apps/expo/src/app/organization/accept-invitation.tsx.tpl +8 -16
  12. package/templates/minimal-app/apps/expo/src/app/posts.tsx.tpl +4 -1
  13. package/templates/minimal-app/apps/expo/src/lib/trpc.ts.tpl +11 -2
  14. package/templates/minimal-app/apps/landing/vite.config.ts.tpl +2 -3
  15. package/templates/minimal-app/apps/server/db.e2e.ts.tpl +5 -40
  16. package/templates/minimal-app/apps/server/db.ts.tpl +5 -42
  17. package/templates/minimal-app/apps/server/drizzle/{0000_wealthy_absorbing_man.sql → 0000_whole_echo.sql} +102 -3
  18. package/templates/minimal-app/apps/server/drizzle/0001_yummy_spectrum.sql +26 -0
  19. package/templates/minimal-app/apps/server/drizzle/0002_crazy_lilith.sql +47 -0
  20. package/templates/minimal-app/apps/server/drizzle/0003_freezing_fantastic_four.sql +4 -0
  21. package/templates/minimal-app/apps/server/drizzle/0004_slow_ezekiel_stane.sql +99 -0
  22. package/templates/minimal-app/apps/server/drizzle/0005_wonderful_the_professor.sql +2 -0
  23. package/templates/minimal-app/apps/server/drizzle/meta/0000_snapshot.json.tpl +2625 -1780
  24. package/templates/minimal-app/apps/server/drizzle/meta/0001_snapshot.json.tpl +2671 -1796
  25. package/templates/minimal-app/apps/server/drizzle/meta/0002_snapshot.json.tpl +2716 -1818
  26. package/templates/minimal-app/apps/server/drizzle/meta/0003_snapshot.json.tpl +715 -164
  27. package/templates/minimal-app/apps/server/drizzle/meta/0004_snapshot.json.tpl +2498 -0
  28. package/templates/minimal-app/apps/server/drizzle/meta/0005_snapshot.json.tpl +2484 -0
  29. package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +23 -9
  30. package/templates/minimal-app/apps/server/src/app.ts.tpl +4 -1
  31. package/templates/minimal-app/apps/server/src/modules/posts/posts.db.ts.tpl +2 -2
  32. package/templates/minimal-app/apps/server/src/modules/posts/posts.service.ts.tpl +1 -1
  33. package/templates/minimal-app/apps/server/src/modules/test-harness/test-harness.module.ts.tpl +13 -36
  34. package/templates/minimal-app/apps/server/src/schema.ts.tpl +3 -2
  35. package/templates/minimal-app/apps/shared/package.json.tpl +1 -0
  36. package/templates/minimal-app/apps/shared/src/modules/app/app.constants.ts.tpl +1 -1
  37. package/templates/minimal-app/apps/shared/src/modules/notification/notification.constants.ts.tpl +5 -0
  38. package/templates/minimal-app/apps/shared/src/modules/notification/notification.schema.ts.tpl +1 -1
  39. package/templates/minimal-app/apps/webapp/src/Layout.tsx.tpl +17 -1
  40. package/templates/minimal-app/apps/webapp/src/Router.tsx.tpl +10 -8
  41. package/templates/minimal-app/apps/webapp/src/modules/notification/NotificationPreferencesRoute.tsx.tpl +29 -0
  42. package/templates/minimal-app/apps/webapp/src/modules/notification/StarterWebPush.tsx.tpl +44 -0
  43. package/templates/minimal-app/apps/webapp/translations/en/{{APP_SLUG}}.json.tpl +8 -0
  44. package/templates/minimal-app/pnpm-workspace.yaml.tpl +12 -11
  45. package/templates/minimal-app/apps/server/drizzle/0001_third_ink.sql +0 -1
  46. package/templates/minimal-app/apps/server/drizzle/0002_concerned_proudstar.sql +0 -3
  47. package/templates/minimal-app/apps/server/drizzle/0003_silly_the_liberteens.sql +0 -27
@@ -5,30 +5,44 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "6",
8
- "when": 1783931793066,
9
- "tag": "0000_wealthy_absorbing_man",
8
+ "when": 1787759631723,
9
+ "tag": "0000_whole_echo",
10
10
  "breakpoints": true
11
11
  },
12
12
  {
13
13
  "idx": 1,
14
14
  "version": "6",
15
- "when": 1785171166863,
16
- "tag": "0001_third_ink",
15
+ "when": 1788197370969,
16
+ "tag": "0001_yummy_spectrum",
17
17
  "breakpoints": true
18
18
  },
19
19
  {
20
20
  "idx": 2,
21
21
  "version": "6",
22
- "when": 1786951360347,
23
- "tag": "0002_concerned_proudstar",
22
+ "when": 1788197859737,
23
+ "tag": "0002_crazy_lilith",
24
24
  "breakpoints": true
25
25
  },
26
26
  {
27
27
  "idx": 3,
28
28
  "version": "6",
29
- "when": 1787145865079,
30
- "tag": "0003_silly_the_liberteens",
29
+ "when": 1788198203749,
30
+ "tag": "0003_freezing_fantastic_four",
31
+ "breakpoints": true
32
+ },
33
+ {
34
+ "idx": 4,
35
+ "version": "6",
36
+ "when": 1788198833389,
37
+ "tag": "0004_slow_ezekiel_stane",
38
+ "breakpoints": true
39
+ },
40
+ {
41
+ "idx": 5,
42
+ "version": "6",
43
+ "when": 1788253467922,
44
+ "tag": "0005_wonderful_the_professor",
31
45
  "breakpoints": true
32
46
  }
33
47
  ]
34
- }
48
+ }
@@ -23,6 +23,9 @@ import {
23
23
  APP_NAME,
24
24
  APP_ROLES_CONFIG,
25
25
  } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
26
+ // m5k:notifications:start
27
+ import { NOTIFICATION_KINDS } from "{{PACKAGE_SCOPE}}/shared/modules/notification/notification.constants";
28
+ // m5k:notifications:end
26
29
  // m5k:ai:start
27
30
  import { mastra, openrouter } from "./modules/conversation/conversation.mastra";
28
31
  // m5k:ai:end
@@ -138,7 +141,7 @@ export const builtBackendApp = createBackendApp(
138
141
  new DemoWorkflowModule(),
139
142
  // m5k:workflows:end
140
143
  // m5k:notifications:start
141
- new NotificationModule(),
144
+ new NotificationModule({ kinds: NOTIFICATION_KINDS }),
142
145
  // m5k:notifications:end
143
146
  new PostsModule(),
144
147
  new EmailPreviewModule({ allowDelete: process.env.NODE_ENV !== "production" }),
@@ -1,4 +1,4 @@
1
- import { members, organizations, teams, users } from "@m5kdev/backend/modules/auth/auth.db";
1
+ import { members, organizations, users } from "@m5kdev/backend/modules/auth/auth.db";
2
2
  import { integer, sqliteTable as table, text } from "drizzle-orm/sqlite-core";
3
3
  import { v4 as uuidv4 } from "uuid";
4
4
 
@@ -13,7 +13,7 @@ export const posts = table("posts", {
13
13
  organizationId: text("organization_id").references(() => organizations.id, {
14
14
  onDelete: "set null",
15
15
  }),
16
- teamId: text("team_id").references(() => teams.id, { onDelete: "set null" }),
16
+ teamId: text("team_id"),
17
17
  title: text("title").notNull(),
18
18
  slug: text("slug").notNull().unique(),
19
19
  excerpt: text("excerpt"),
@@ -50,7 +50,7 @@ export class PostsService extends BasePermissionService<
50
50
  authorUserId: ctx.user.id,
51
51
  memberId: ctx.actor.memberId ?? null,
52
52
  organizationId: ctx.session.activeOrganizationId ?? null,
53
- teamId: ctx.session.activeTeamId ?? null,
53
+ teamId: null,
54
54
  title: input.title.trim(),
55
55
  slug: uniqueSlug.value,
56
56
  excerpt: createExcerpt(input.excerpt, input.content),
@@ -3,7 +3,7 @@ import {
3
3
  type ModuleExpressContext,
4
4
  type TableMap,
5
5
  } from "@m5kdev/backend/modules/base/base.module";
6
- import { and, desc, eq, isNotNull } from "drizzle-orm";
6
+ import { and, desc, eq, isNotNull, isNull } from "drizzle-orm";
7
7
  import type { Request, Response } from "express";
8
8
  import { v4 as uuidv4 } from "uuid";
9
9
 
@@ -77,13 +77,14 @@ export class TestHarnessModule extends BaseModule<
77
77
  type: db.schema.organizations.type,
78
78
  parentId: db.schema.organizations.parentId,
79
79
  role: db.schema.members.role,
80
+ memberId: db.schema.members.id,
80
81
  })
81
82
  .from(db.schema.members)
82
83
  .innerJoin(
83
84
  db.schema.organizations,
84
85
  eq(db.schema.members.organizationId, db.schema.organizations.id)
85
86
  )
86
- .where(eq(db.schema.members.userId, user.id));
87
+ .where(and(eq(db.schema.members.userId, user.id), isNull(db.schema.members.deletedAt)));
87
88
 
88
89
  const [credentialAccount] = await db.orm
89
90
  .select({ id: db.schema.accounts.id })
@@ -99,21 +100,16 @@ export class TestHarnessModule extends BaseModule<
99
100
 
100
101
  const accountClaims = await db.orm
101
102
  .select({
102
- id: db.schema.waitlist.id,
103
- status: db.schema.waitlist.status,
104
- claimUserId: db.schema.waitlist.claimUserId,
105
- claimedEmail: db.schema.waitlist.claimedEmail,
106
- claimedAt: db.schema.waitlist.claimedAt,
107
- expiresAt: db.schema.waitlist.expiresAt,
103
+ id: db.schema.accountClaims.id,
104
+ status: db.schema.accountClaims.status,
105
+ claimUserId: db.schema.accountClaims.claimUserId,
106
+ claimedEmail: db.schema.accountClaims.claimedEmail,
107
+ claimedAt: db.schema.accountClaims.claimedAt,
108
+ expiresAt: db.schema.accountClaims.expiresAt,
108
109
  })
109
- .from(db.schema.waitlist)
110
- .where(
111
- and(
112
- eq(db.schema.waitlist.type, "ACCOUNT_CLAIM"),
113
- eq(db.schema.waitlist.claimUserId, user.id)
114
- )
115
- )
116
- .orderBy(desc(db.schema.waitlist.createdAt));
110
+ .from(db.schema.accountClaims)
111
+ .where(eq(db.schema.accountClaims.claimUserId, user.id))
112
+ .orderBy(desc(db.schema.accountClaims.createdAt));
117
113
 
118
114
  const [latestSession] = await db.orm
119
115
  .select({
@@ -123,7 +119,6 @@ export class TestHarnessModule extends BaseModule<
123
119
  activeOrganizationRole: db.schema.sessions.activeOrganizationRole,
124
120
  activeOrganizationMemberId: db.schema.sessions.activeOrganizationMemberId,
125
121
  activeOrganizationType: db.schema.sessions.activeOrganizationType,
126
- activeTeamId: db.schema.sessions.activeTeamId,
127
122
  impersonatedBy: db.schema.sessions.impersonatedBy,
128
123
  createdAt: db.schema.sessions.createdAt,
129
124
  })
@@ -172,7 +167,6 @@ export class TestHarnessModule extends BaseModule<
172
167
  const now = new Date();
173
168
  const userId = uuidv4();
174
169
  const organizationId = uuidv4();
175
- const teamId = uuidv4();
176
170
  const claimId = createClaim ? uuidv4() : null;
177
171
 
178
172
  await db.orm.transaction(async (tx) => {
@@ -202,26 +196,9 @@ export class TestHarnessModule extends BaseModule<
202
196
  createdAt: now,
203
197
  });
204
198
 
205
- await tx.insert(db.schema.teams).values({
206
- id: teamId,
207
- name: "Editorial",
208
- organizationId,
209
- createdAt: now,
210
- updatedAt: now,
211
- });
212
-
213
- await tx.insert(db.schema.teamMembers).values({
214
- id: uuidv4(),
215
- teamId,
216
- userId,
217
- role: "owner",
218
- createdAt: now,
219
- });
220
-
221
199
  if (claimId) {
222
- await tx.insert(db.schema.waitlist).values({
200
+ await tx.insert(db.schema.accountClaims).values({
223
201
  id: claimId,
224
- type: "ACCOUNT_CLAIM",
225
202
  status: "INVITED",
226
203
  claimUserId: userId,
227
204
  code: uuidv4(),
@@ -1,14 +1,13 @@
1
1
  // biome-ignore-all assist/source/organizeImports: feature-gated table exports stay in marker blocks
2
2
  export {
3
3
  accountClaimMagicLinks,
4
+ accountClaims,
4
5
  accounts,
5
6
  apikeys,
6
7
  invitations,
7
8
  members,
8
9
  organizations,
9
10
  sessions,
10
- teamMembers,
11
- teams,
12
11
  users,
13
12
  verifications,
14
13
  waitlist,
@@ -22,7 +21,9 @@ export { files } from "@m5kdev/backend/modules/file/file.db";
22
21
  // m5k:notifications:start
23
22
  export {
24
23
  notificationDevices,
24
+ notificationPreferences,
25
25
  notificationSendLogs,
26
+ notifications,
26
27
  } from "@m5kdev/backend/modules/notification/notification.db";
27
28
  // m5k:notifications:end
28
29
  // m5k:workflows:start
@@ -21,6 +21,7 @@
21
21
  "./modules/app/*": "./src/modules/app/*.ts",
22
22
  "./modules/conversation/*": "./src/modules/conversation/*.ts",
23
23
  "./modules/demo-workflow/*": "./src/modules/demo-workflow/*.ts",
24
+ "./modules/notification/*": "./src/modules/notification/*.ts",
24
25
  "./modules/posts/*": "./src/modules/posts/*.ts"
25
26
  }
26
27
  }
@@ -18,7 +18,7 @@ export const APP_ROLES_CONFIG = defineAuthRoles({
18
18
  organization: {
19
19
  roles: ["member", "admin", "owner"],
20
20
  managerRoles: ["admin", "owner"],
21
- assignableRoles: ["member", "admin", "owner"],
21
+ assignableRoles: ["member", "admin"],
22
22
  defaultRole: "member",
23
23
  },
24
24
  team: {
@@ -0,0 +1,5 @@
1
+ import type { NotificationKind } from "@m5kdev/commons/modules/notification/notification.constants";
2
+
3
+ export const NOTIFICATION_KINDS = [
4
+ { id: "notification.test", defaultChannels: ["in-app"] },
5
+ ] as const satisfies readonly NotificationKind[];
@@ -1,3 +1,3 @@
1
- /** Re-export shared push-notification contracts from commons. */
1
+ /** Re-export shared Notification contracts from commons. */
2
2
  export * from "@m5kdev/commons/modules/notification/notification.constants";
3
3
  export * from "@m5kdev/commons/modules/notification/notification.schema";
@@ -7,8 +7,11 @@ import { AppSidebarHeader } from "@m5kdev/web-ui/modules/app/components/AppSideb
7
7
  import { AppSidebarUser } from "@m5kdev/web-ui/modules/app/components/AppSidebarUser";
8
8
  import { AuthOrganizationSelect } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationSelect";
9
9
  import { AuthUtilityImpersonationBanner } from "@m5kdev/web-ui/modules/auth/components/AuthUtilityImpersonationBanner";
10
+ // m5k:notifications:start
11
+ import { NotificationSidebarInbox } from "@m5kdev/web-ui/modules/notification/components/NotificationSidebarInbox";
12
+ // m5k:notifications:end
10
13
  import { APP_NAME } from "{{PACKAGE_SCOPE}}/shared/modules/app/app.constants";
11
- import { FileIcon, UsersIcon } from "lucide-react";
14
+ import { BellIcon, FileIcon, UsersIcon } from "lucide-react";
12
15
  // m5k:ai:start
13
16
  import { SparklesIcon } from "lucide-react";
14
17
  // m5k:ai:end
@@ -21,6 +24,9 @@ import { WorkflowIcon } from "lucide-react";
21
24
  import { useState } from "react";
22
25
  import { useTranslation } from "react-i18next";
23
26
  import { useNavigate } from "react-router";
27
+ // m5k:notifications:start
28
+ import { StarterWebPush } from "./modules/notification/StarterWebPush";
29
+ // m5k:notifications:end
24
30
 
25
31
  export function Layout() {
26
32
  const { data: session } = useSession();
@@ -42,6 +48,9 @@ export function Layout() {
42
48
  header: <AppSidebarHeader logo={{ src: "/logo.svg", alt: APP_NAME }} title={APP_NAME} />,
43
49
  content: (
44
50
  <>
51
+ {/* m5k:notifications:start */}
52
+ <NotificationSidebarInbox enablePush={<StarterWebPush />} />
53
+ {/* m5k:notifications:end */}
45
54
  <div className="px-2 group-data-[collapsible=icon]:hidden">
46
55
  <AuthOrganizationSelect />
47
56
  </div>
@@ -73,6 +82,13 @@ export function Layout() {
73
82
  link: "/workflows",
74
83
  },
75
84
  // m5k:workflows:end
85
+ // m5k:notifications:start
86
+ {
87
+ label: t("layout.navigation.notifications"),
88
+ icon: <BellIcon />,
89
+ link: "/notifications",
90
+ },
91
+ // m5k:notifications:end
76
92
  {
77
93
  label: t("layout.navigation.members"),
78
94
  icon: <UsersIcon />,
@@ -1,7 +1,6 @@
1
1
  // biome-ignore-all assist/source/organizeImports: feature-gated route imports stay in marker blocks
2
2
  import { syncI18nLocale } from "@m5kdev/frontend/modules/app/utils/locale";
3
3
  import { AuthAdminRouter } from "@m5kdev/web-ui/modules/auth/components/AuthAdminRouter";
4
- import { AuthOrganizationAcceptInvitationRoute } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationAcceptInvitationRoute";
5
4
  import { AuthOrganizationRouter } from "@m5kdev/web-ui/modules/auth/components/AuthOrganizationRouter";
6
5
  import { AuthPublicRouter } from "@m5kdev/web-ui/modules/auth/components/AuthPublicRouter";
7
6
  import { AuthUserRouter } from "@m5kdev/web-ui/modules/auth/components/AuthUserRouter";
@@ -20,6 +19,9 @@ import { FilesRoute } from "@/modules/files/FilesRoute";
20
19
  // m5k:workflows:start
21
20
  import { WorkflowsRoute } from "@/modules/workflows/WorkflowsRoute";
22
21
  // m5k:workflows:end
22
+ // m5k:notifications:start
23
+ import { NotificationPreferencesRoute } from "@/modules/notification/NotificationPreferencesRoute";
24
+ // m5k:notifications:end
23
25
  import { Layout } from "./Layout";
24
26
 
25
27
  const preferenceSchema = z.object({
@@ -68,13 +70,6 @@ export function Router() {
68
70
  onLocaleChange: syncI18nLocale,
69
71
  })}
70
72
 
71
- {/* public: a logged-out invitee must reach this route so it can send
72
- them to signup — inside ProtectedRoutes they'd bounce to /login */}
73
- <Route
74
- path="/organization/accept-invitation"
75
- element={<AuthOrganizationAcceptInvitationRoute />}
76
- />
77
-
78
73
  <Route
79
74
  element={
80
75
  <ProtectedRoutes>
@@ -93,6 +88,13 @@ export function Router() {
93
88
  // m5k:workflows:start
94
89
  <Route key="workflows" path="workflows" element={<WorkflowsRoute />} />,
95
90
  // m5k:workflows:end
91
+ // m5k:notifications:start
92
+ <Route
93
+ key="notifications"
94
+ path="notifications"
95
+ element={<NotificationPreferencesRoute />}
96
+ />,
97
+ // m5k:notifications:end
96
98
  ]}
97
99
 
98
100
  {AuthUserRouter({
@@ -0,0 +1,29 @@
1
+ import { Card } from "@heroui/react";
2
+ import { NotificationPreferences } from "@m5kdev/web-ui/modules/notification/components/NotificationPreferences";
3
+ import { NOTIFICATION_KINDS } from "{{PACKAGE_SCOPE}}/shared/modules/notification/notification.constants";
4
+ import { useTranslation } from "react-i18next";
5
+ import { StarterWebPush } from "./StarterWebPush";
6
+
7
+ export function NotificationPreferencesRoute() {
8
+ const { t } = useTranslation("{{APP_SLUG}}");
9
+
10
+ return (
11
+ <div className="grid gap-6 p-4" data-testid="notification-preferences-route">
12
+ <div className="justify-self-start">
13
+ <StarterWebPush />
14
+ </div>
15
+ <Card>
16
+ <Card.Header className="flex flex-col gap-1">
17
+ <p className="text-xs font-medium uppercase tracking-wide text-muted">
18
+ {t("notifications.hero.eyebrow")}
19
+ </p>
20
+ <Card.Title>{t("notifications.hero.title")}</Card.Title>
21
+ <Card.Description>{t("notifications.hero.body")}</Card.Description>
22
+ </Card.Header>
23
+ <Card.Content>
24
+ <NotificationPreferences kinds={NOTIFICATION_KINDS} offerEmail />
25
+ </Card.Content>
26
+ </Card>
27
+ </div>
28
+ );
29
+ }
@@ -0,0 +1,44 @@
1
+ import { Button } from "@heroui/react";
2
+ import { useSession } from "@m5kdev/frontend/modules/auth/hooks/useSession";
3
+ import { useWebPush } from "@m5kdev/web-ui/hooks/useWebPush";
4
+ import { BellRingIcon } from "lucide-react";
5
+ import type { ReactNode } from "react";
6
+ import { useTranslation } from "react-i18next";
7
+ import { useTRPC } from "../../utils/trpc";
8
+
9
+ export function StarterWebPush(): ReactNode {
10
+ const { t } = useTranslation("{{APP_SLUG}}");
11
+ const { data: session } = useSession();
12
+ const trpc = useTRPC();
13
+ const webPush = useWebPush({
14
+ enabled: Boolean(session?.user?.id),
15
+ messages: {
16
+ unsupported: t("layout.push.unsupported"),
17
+ denied: t("layout.push.denied"),
18
+ noVapid: t("layout.push.noVapid"),
19
+ badSubscription: t("layout.push.badSubscription"),
20
+ failed: t("layout.push.failed"),
21
+ enabled: t("layout.push.enabled"),
22
+ },
23
+ vapidPublicKeyQuery: trpc.notification.vapidPublicKey.queryOptions(),
24
+ registerDeviceMutation: trpc.notification.registerDevice.mutationOptions(),
25
+ });
26
+
27
+ if (!session?.user?.id || webPush.flowStatus === "done") return null;
28
+
29
+ const label = webPush.permission === "denied" ? t("layout.push.denied") : t("layout.push.cta");
30
+
31
+ return (
32
+ <Button
33
+ size="sm"
34
+ variant="secondary"
35
+ isDisabled={!webPush.canSubscribe || webPush.isWorking}
36
+ onPress={() => {
37
+ void webPush.subscribe();
38
+ }}
39
+ >
40
+ <BellRingIcon className="size-4" />
41
+ {label}
42
+ </Button>
43
+ );
44
+ }
@@ -19,6 +19,7 @@
19
19
  "organization": "Organization",
20
20
  "account": "Account",
21
21
  "members": "Members",
22
+ "notifications": "Notifications",
22
23
  "childOrgs": "Child orgs",
23
24
  "orgPreferences": "Org preferences",
24
25
  "profile": "Profile",
@@ -184,6 +185,13 @@
184
185
  "failed": "Upload failed."
185
186
  }
186
187
  },
188
+ "notifications": {
189
+ "hero": {
190
+ "eyebrow": "This membership",
191
+ "title": "Notification preferences",
192
+ "body": "Mute channels for this organization. Other organizations keep their own settings."
193
+ }
194
+ },
187
195
  "auth": {
188
196
  "header": {
189
197
  "eyebrow": "Framework Starter",
@@ -7,17 +7,18 @@ catalogs:
7
7
  "@heroui/react": 3.0.3
8
8
  "@heroui/styles": 3.0.3
9
9
  "@libsql/client": 0.17.4
10
- "@m5kdev/backend": 0.35.0
11
- "@m5kdev/commons": 0.35.0
12
- "@m5kdev/config": 0.35.0
13
- "@m5kdev/email": 0.35.0
14
- "@m5kdev/frontend": 0.35.0
15
- "@m5kdev/module-clay": 0.35.0
16
- "@m5kdev/module-docx": 0.35.0
17
- "@m5kdev/module-pdf": 0.35.0
18
- "@m5kdev/module-social": 0.35.0
19
- "@m5kdev/module-video": 0.35.0
20
- "@m5kdev/web-ui": 0.35.0
10
+ "@m5kdev/backend": 0.36.0
11
+ "@m5kdev/commons": 0.36.0
12
+ "@m5kdev/config": 0.36.0
13
+ "@m5kdev/email": 0.36.0
14
+ "@m5kdev/expo": 0.36.0
15
+ "@m5kdev/frontend": 0.36.0
16
+ "@m5kdev/module-clay": 0.36.0
17
+ "@m5kdev/module-docx": 0.36.0
18
+ "@m5kdev/module-pdf": 0.36.0
19
+ "@m5kdev/module-social": 0.36.0
20
+ "@m5kdev/module-video": 0.36.0
21
+ "@m5kdev/web-ui": 0.36.0
21
22
  "@mastra/core": 1.51.0
22
23
  "@openrouter/ai-sdk-provider": 3.0.0
23
24
  "@opentelemetry/api": 1.9.0
@@ -1 +0,0 @@
1
- ALTER TABLE `posts` ADD `member_id` text REFERENCES members(id);
@@ -1,3 +0,0 @@
1
- ALTER TABLE `members` ADD `name` text DEFAULT '' NOT NULL;--> statement-breakpoint
2
- ALTER TABLE `members` ADD `image` text;--> statement-breakpoint
3
- ALTER TABLE `members` ADD `deleted_at` integer;
@@ -1,27 +0,0 @@
1
- CREATE TABLE `files` (
2
- `id` text PRIMARY KEY NOT NULL,
3
- `created_at` integer NOT NULL,
4
- `updated_at` integer,
5
- `deleted_at` integer,
6
- `user_id` text,
7
- `member_id` text,
8
- `organization_id` text,
9
- `team_id` text,
10
- `bucket` text NOT NULL,
11
- `key` text NOT NULL,
12
- `original_name` text NOT NULL,
13
- `original_extension` text,
14
- `content_type` text NOT NULL,
15
- `size_bytes` integer,
16
- `etag` text,
17
- `checksum_sha256` text,
18
- `metadata` text,
19
- `status` text NOT NULL,
20
- `uploaded_at` integer,
21
- FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE set null,
22
- FOREIGN KEY (`member_id`) REFERENCES `members`(`id`) ON UPDATE no action ON DELETE set null,
23
- FOREIGN KEY (`organization_id`) REFERENCES `organizations`(`id`) ON UPDATE no action ON DELETE set null,
24
- FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON UPDATE no action ON DELETE set null
25
- );
26
- --> statement-breakpoint
27
- CREATE UNIQUE INDEX `files_bucket_key_unique` ON `files` (`bucket`,`key`);