create-fate 0.0.0 → 1.0.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 (266) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +24 -0
  3. package/bin/create-fate.mjs +412 -0
  4. package/package.json +26 -3
  5. package/templates/fate/README.md +8 -0
  6. package/templates/fate/drizzle/.github/workflows/push.yml +27 -0
  7. package/templates/fate/drizzle/.oxfmtrc.jsonc +23 -0
  8. package/templates/fate/drizzle/.vite-hooks/pre-commit +1 -0
  9. package/templates/fate/drizzle/.vscode/extensions.json +12 -0
  10. package/templates/fate/drizzle/.vscode/settings.json +25 -0
  11. package/templates/fate/drizzle/AGENTS.md +55 -0
  12. package/templates/fate/drizzle/README.md +112 -0
  13. package/templates/fate/drizzle/_gitignore +20 -0
  14. package/templates/fate/drizzle/client/package.json +58 -0
  15. package/templates/fate/drizzle/client/pages/index.tsx +96 -0
  16. package/templates/fate/drizzle/client/pages/layout.tsx +104 -0
  17. package/templates/fate/drizzle/client/pages/login.tsx +107 -0
  18. package/templates/fate/drizzle/client/pages/post/[id].tsx +22 -0
  19. package/templates/fate/drizzle/client/pages/search.tsx +95 -0
  20. package/templates/fate/drizzle/client/public/icon.svg +1 -0
  21. package/templates/fate/drizzle/client/src/App.css +153 -0
  22. package/templates/fate/drizzle/client/src/lib/AvailableLanguages.tsx +5 -0
  23. package/templates/fate/drizzle/client/src/lib/cx.tsx +6 -0
  24. package/templates/fate/drizzle/client/src/lib/env.tsx +5 -0
  25. package/templates/fate/drizzle/client/src/ui/Badge.tsx +31 -0
  26. package/templates/fate/drizzle/client/src/ui/Button.tsx +108 -0
  27. package/templates/fate/drizzle/client/src/ui/Card.tsx +32 -0
  28. package/templates/fate/drizzle/client/src/ui/CommentCard.tsx +75 -0
  29. package/templates/fate/drizzle/client/src/ui/Error.tsx +19 -0
  30. package/templates/fate/drizzle/client/src/ui/H2.tsx +15 -0
  31. package/templates/fate/drizzle/client/src/ui/H3.tsx +20 -0
  32. package/templates/fate/drizzle/client/src/ui/Header.tsx +96 -0
  33. package/templates/fate/drizzle/client/src/ui/Input.tsx +17 -0
  34. package/templates/fate/drizzle/client/src/ui/Link.tsx +11 -0
  35. package/templates/fate/drizzle/client/src/ui/LocaleSwitcher.tsx +52 -0
  36. package/templates/fate/drizzle/client/src/ui/PostCard.tsx +284 -0
  37. package/templates/fate/drizzle/client/src/ui/Section.tsx +23 -0
  38. package/templates/fate/drizzle/client/src/ui/UserCard.tsx +131 -0
  39. package/templates/fate/drizzle/client/src/user/AuthClient.tsx +6 -0
  40. package/templates/fate/drizzle/client/translations/de_DE.json +689 -0
  41. package/templates/fate/drizzle/client/translations/ja_JP.json +689 -0
  42. package/templates/fate/drizzle/client/vite.config.ts +39 -0
  43. package/templates/fate/drizzle/client/void.json +29 -0
  44. package/templates/fate/drizzle/docker-compose.yml +11 -0
  45. package/templates/fate/drizzle/package.json +46 -0
  46. package/templates/fate/drizzle/pnpm-workspace.yaml +36 -0
  47. package/templates/fate/drizzle/public/fate-logo-dark.svg +4 -0
  48. package/templates/fate/drizzle/public/fate-logo.svg +4 -0
  49. package/templates/fate/drizzle/server/.env.example +5 -0
  50. package/templates/fate/drizzle/server/drizzle.config.ts +11 -0
  51. package/templates/fate/drizzle/server/package.json +48 -0
  52. package/templates/fate/drizzle/server/src/__tests__/index.test.tsx +5 -0
  53. package/templates/fate/drizzle/server/src/app.tsx +44 -0
  54. package/templates/fate/drizzle/server/src/drizzle/db.ts +25 -0
  55. package/templates/fate/drizzle/server/src/drizzle/queries.ts +89 -0
  56. package/templates/fate/drizzle/server/src/drizzle/schema.ts +174 -0
  57. package/templates/fate/drizzle/server/src/drizzle/seed.tsx +77 -0
  58. package/templates/fate/drizzle/server/src/drizzle/seedData.ts +205 -0
  59. package/templates/fate/drizzle/server/src/index.tsx +24 -0
  60. package/templates/fate/drizzle/server/src/lib/auth.ts +33 -0
  61. package/templates/fate/drizzle/server/src/lib/env.ts +3 -0
  62. package/templates/fate/drizzle/server/src/lib/origins.ts +18 -0
  63. package/templates/fate/drizzle/server/src/router.ts +15 -0
  64. package/templates/fate/drizzle/server/src/trpc/connection.ts +4 -0
  65. package/templates/fate/drizzle/server/src/trpc/context.ts +22 -0
  66. package/templates/fate/drizzle/server/src/trpc/init.ts +51 -0
  67. package/templates/fate/drizzle/server/src/trpc/routers/comment.ts +146 -0
  68. package/templates/fate/drizzle/server/src/trpc/routers/post.ts +99 -0
  69. package/templates/fate/drizzle/server/src/trpc/routers/user.ts +68 -0
  70. package/templates/fate/drizzle/server/src/trpc/views.ts +75 -0
  71. package/templates/fate/drizzle/server/src/user/SessionUser.tsx +17 -0
  72. package/templates/fate/drizzle/server/vite.config.ts +8 -0
  73. package/templates/fate/drizzle/tsconfig.json +33 -0
  74. package/templates/fate/drizzle/vite.config.ts +71 -0
  75. package/templates/fate/http/.github/workflows/push.yml +27 -0
  76. package/templates/fate/http/.oxfmtrc.jsonc +23 -0
  77. package/templates/fate/http/.vite-hooks/pre-commit +1 -0
  78. package/templates/fate/http/.vscode/extensions.json +12 -0
  79. package/templates/fate/http/.vscode/settings.json +25 -0
  80. package/templates/fate/http/AGENTS.md +55 -0
  81. package/templates/fate/http/README.md +112 -0
  82. package/templates/fate/http/_gitignore +20 -0
  83. package/templates/fate/http/client/package.json +56 -0
  84. package/templates/fate/http/client/pages/index.tsx +96 -0
  85. package/templates/fate/http/client/pages/layout.tsx +89 -0
  86. package/templates/fate/http/client/pages/login.tsx +107 -0
  87. package/templates/fate/http/client/pages/post/[id].tsx +22 -0
  88. package/templates/fate/http/client/pages/search.tsx +95 -0
  89. package/templates/fate/http/client/public/icon.svg +1 -0
  90. package/templates/fate/http/client/src/App.css +153 -0
  91. package/templates/fate/http/client/src/lib/AvailableLanguages.tsx +5 -0
  92. package/templates/fate/http/client/src/lib/cx.tsx +6 -0
  93. package/templates/fate/http/client/src/lib/env.tsx +5 -0
  94. package/templates/fate/http/client/src/ui/Badge.tsx +31 -0
  95. package/templates/fate/http/client/src/ui/Button.tsx +108 -0
  96. package/templates/fate/http/client/src/ui/Card.tsx +32 -0
  97. package/templates/fate/http/client/src/ui/CommentCard.tsx +75 -0
  98. package/templates/fate/http/client/src/ui/Error.tsx +19 -0
  99. package/templates/fate/http/client/src/ui/H2.tsx +15 -0
  100. package/templates/fate/http/client/src/ui/H3.tsx +20 -0
  101. package/templates/fate/http/client/src/ui/Header.tsx +96 -0
  102. package/templates/fate/http/client/src/ui/Input.tsx +17 -0
  103. package/templates/fate/http/client/src/ui/Link.tsx +11 -0
  104. package/templates/fate/http/client/src/ui/LocaleSwitcher.tsx +52 -0
  105. package/templates/fate/http/client/src/ui/PostCard.tsx +281 -0
  106. package/templates/fate/http/client/src/ui/Section.tsx +23 -0
  107. package/templates/fate/http/client/src/ui/UserCard.tsx +131 -0
  108. package/templates/fate/http/client/src/user/AuthClient.tsx +6 -0
  109. package/templates/fate/http/client/translations/de_DE.json +689 -0
  110. package/templates/fate/http/client/translations/ja_JP.json +689 -0
  111. package/templates/fate/http/client/vite.config.ts +40 -0
  112. package/templates/fate/http/client/void.json +29 -0
  113. package/templates/fate/http/docker-compose.yml +11 -0
  114. package/templates/fate/http/package.json +46 -0
  115. package/templates/fate/http/pnpm-workspace.yaml +36 -0
  116. package/templates/fate/http/public/fate-logo-dark.svg +4 -0
  117. package/templates/fate/http/public/fate-logo.svg +4 -0
  118. package/templates/fate/http/server/.env.example +5 -0
  119. package/templates/fate/http/server/drizzle.config.ts +11 -0
  120. package/templates/fate/http/server/package.json +46 -0
  121. package/templates/fate/http/server/src/__tests__/index.test.tsx +5 -0
  122. package/templates/fate/http/server/src/app.tsx +36 -0
  123. package/templates/fate/http/server/src/drizzle/db.ts +25 -0
  124. package/templates/fate/http/server/src/drizzle/queries.ts +89 -0
  125. package/templates/fate/http/server/src/drizzle/schema.ts +174 -0
  126. package/templates/fate/http/server/src/drizzle/seed.tsx +77 -0
  127. package/templates/fate/http/server/src/drizzle/seedData.ts +205 -0
  128. package/templates/fate/http/server/src/fate/context.ts +20 -0
  129. package/templates/fate/http/server/src/fate/server.ts +285 -0
  130. package/templates/fate/http/server/src/fate/views.ts +75 -0
  131. package/templates/fate/http/server/src/index.tsx +24 -0
  132. package/templates/fate/http/server/src/lib/auth.ts +33 -0
  133. package/templates/fate/http/server/src/lib/env.ts +3 -0
  134. package/templates/fate/http/server/src/lib/origins.ts +18 -0
  135. package/templates/fate/http/server/src/router.ts +2 -0
  136. package/templates/fate/http/server/src/user/SessionUser.tsx +17 -0
  137. package/templates/fate/http/server/vite.config.ts +8 -0
  138. package/templates/fate/http/tsconfig.json +33 -0
  139. package/templates/fate/http/vite.config.ts +71 -0
  140. package/templates/fate/prisma/.github/workflows/push.yml +27 -0
  141. package/templates/fate/prisma/.oxfmtrc.jsonc +23 -0
  142. package/templates/fate/prisma/.vite-hooks/pre-commit +1 -0
  143. package/templates/fate/prisma/.vscode/extensions.json +12 -0
  144. package/templates/fate/prisma/.vscode/settings.json +25 -0
  145. package/templates/fate/prisma/AGENTS.md +55 -0
  146. package/templates/fate/prisma/README.md +126 -0
  147. package/templates/fate/prisma/_gitignore +20 -0
  148. package/templates/fate/prisma/client/package.json +58 -0
  149. package/templates/fate/prisma/client/pages/index.tsx +96 -0
  150. package/templates/fate/prisma/client/pages/layout.tsx +94 -0
  151. package/templates/fate/prisma/client/pages/login.tsx +107 -0
  152. package/templates/fate/prisma/client/pages/post/[id].tsx +22 -0
  153. package/templates/fate/prisma/client/pages/search.tsx +95 -0
  154. package/templates/fate/prisma/client/public/icon.svg +1 -0
  155. package/templates/fate/prisma/client/src/App.css +153 -0
  156. package/templates/fate/prisma/client/src/lib/AvailableLanguages.tsx +5 -0
  157. package/templates/fate/prisma/client/src/lib/cx.tsx +6 -0
  158. package/templates/fate/prisma/client/src/lib/env.tsx +5 -0
  159. package/templates/fate/prisma/client/src/ui/Badge.tsx +31 -0
  160. package/templates/fate/prisma/client/src/ui/Button.tsx +108 -0
  161. package/templates/fate/prisma/client/src/ui/Card.tsx +32 -0
  162. package/templates/fate/prisma/client/src/ui/CommentCard.tsx +75 -0
  163. package/templates/fate/prisma/client/src/ui/Error.tsx +19 -0
  164. package/templates/fate/prisma/client/src/ui/H2.tsx +15 -0
  165. package/templates/fate/prisma/client/src/ui/H3.tsx +20 -0
  166. package/templates/fate/prisma/client/src/ui/Header.tsx +96 -0
  167. package/templates/fate/prisma/client/src/ui/Input.tsx +17 -0
  168. package/templates/fate/prisma/client/src/ui/Link.tsx +11 -0
  169. package/templates/fate/prisma/client/src/ui/LocaleSwitcher.tsx +52 -0
  170. package/templates/fate/prisma/client/src/ui/PostCard.tsx +281 -0
  171. package/templates/fate/prisma/client/src/ui/Section.tsx +23 -0
  172. package/templates/fate/prisma/client/src/ui/UserCard.tsx +131 -0
  173. package/templates/fate/prisma/client/src/user/AuthClient.tsx +6 -0
  174. package/templates/fate/prisma/client/translations/de_DE.json +689 -0
  175. package/templates/fate/prisma/client/translations/ja_JP.json +689 -0
  176. package/templates/fate/prisma/client/vite.config.ts +39 -0
  177. package/templates/fate/prisma/client/void.json +29 -0
  178. package/templates/fate/prisma/docker-compose.yml +11 -0
  179. package/templates/fate/prisma/package.json +47 -0
  180. package/templates/fate/prisma/pnpm-workspace.yaml +39 -0
  181. package/templates/fate/prisma/public/fate-logo-dark.svg +4 -0
  182. package/templates/fate/prisma/public/fate-logo.svg +4 -0
  183. package/templates/fate/prisma/server/.env.example +5 -0
  184. package/templates/fate/prisma/server/package.json +42 -0
  185. package/templates/fate/prisma/server/prisma.config.ts +21 -0
  186. package/templates/fate/prisma/server/src/__tests__/index.test.tsx +5 -0
  187. package/templates/fate/prisma/server/src/app.tsx +40 -0
  188. package/templates/fate/prisma/server/src/index.tsx +24 -0
  189. package/templates/fate/prisma/server/src/lib/auth.ts +31 -0
  190. package/templates/fate/prisma/server/src/lib/env.ts +3 -0
  191. package/templates/fate/prisma/server/src/lib/origins.ts +18 -0
  192. package/templates/fate/prisma/server/src/prisma/prisma.ts +9 -0
  193. package/templates/fate/prisma/server/src/prisma/schema.prisma +98 -0
  194. package/templates/fate/prisma/server/src/prisma/seed.tsx +71 -0
  195. package/templates/fate/prisma/server/src/prisma/seedData.ts +205 -0
  196. package/templates/fate/prisma/server/src/router.ts +14 -0
  197. package/templates/fate/prisma/server/src/trpc/connection.ts +4 -0
  198. package/templates/fate/prisma/server/src/trpc/context.ts +22 -0
  199. package/templates/fate/prisma/server/src/trpc/init.ts +8 -0
  200. package/templates/fate/prisma/server/src/trpc/routers/comment.ts +176 -0
  201. package/templates/fate/prisma/server/src/trpc/routers/post.ts +154 -0
  202. package/templates/fate/prisma/server/src/trpc/routers/user.ts +71 -0
  203. package/templates/fate/prisma/server/src/trpc/views.ts +77 -0
  204. package/templates/fate/prisma/server/src/user/SessionUser.tsx +17 -0
  205. package/templates/fate/prisma/server/vite.config.ts +8 -0
  206. package/templates/fate/prisma/tsconfig.json +33 -0
  207. package/templates/fate/prisma/vite.config.ts +72 -0
  208. package/templates/fate/void/.oxfmtrc.jsonc +14 -0
  209. package/templates/fate/void/README.md +55 -0
  210. package/templates/fate/void/auth.ts +22 -0
  211. package/templates/fate/void/db/migrations/20260508073357_initial_schema.sql +133 -0
  212. package/templates/fate/void/db/migrations/20260508120500_seed_void_demo.sql +170 -0
  213. package/templates/fate/void/db/migrations/meta/20260508073357_snapshot.json +868 -0
  214. package/templates/fate/void/db/migrations/meta/_journal.json +20 -0
  215. package/templates/fate/void/db/queries.ts +211 -0
  216. package/templates/fate/void/db/schema.ts +325 -0
  217. package/templates/fate/void/db/seed-auth.ts +31 -0
  218. package/templates/fate/void/db/seed.ts +163 -0
  219. package/templates/fate/void/middleware/01.shared.ts +29 -0
  220. package/templates/fate/void/package.json +65 -0
  221. package/templates/fate/void/pages/category/[id].tsx +23 -0
  222. package/templates/fate/void/pages/index.tsx +153 -0
  223. package/templates/fate/void/pages/layout.tsx +46 -0
  224. package/templates/fate/void/pages/login.tsx +103 -0
  225. package/templates/fate/void/pages/post/[id].tsx +20 -0
  226. package/templates/fate/void/pages/search.tsx +81 -0
  227. package/templates/fate/void/pnpm-workspace.yaml +31 -0
  228. package/templates/fate/void/public/.assetsignore +1 -0
  229. package/templates/fate/void/public/icon.svg +1 -0
  230. package/templates/fate/void/routes/fate-live.ts +8 -0
  231. package/templates/fate/void/routes/fate.ts +8 -0
  232. package/templates/fate/void/seedData.ts +535 -0
  233. package/templates/fate/void/src/App.css +130 -0
  234. package/templates/fate/void/src/fate/__tests__/commentPermissions.test.ts +31 -0
  235. package/templates/fate/void/src/fate/commentPermissions.ts +17 -0
  236. package/templates/fate/void/src/fate/context.ts +15 -0
  237. package/templates/fate/void/src/fate/live.ts +6 -0
  238. package/templates/fate/void/src/fate/server.ts +401 -0
  239. package/templates/fate/void/src/fate/views.ts +164 -0
  240. package/templates/fate/void/src/lib/cx.tsx +6 -0
  241. package/templates/fate/void/src/lib/formatLabel.tsx +6 -0
  242. package/templates/fate/void/src/lib/shared.ts +10 -0
  243. package/templates/fate/void/src/ui/Badge.tsx +31 -0
  244. package/templates/fate/void/src/ui/Button.tsx +92 -0
  245. package/templates/fate/void/src/ui/Card.tsx +29 -0
  246. package/templates/fate/void/src/ui/CategoryCard.tsx +105 -0
  247. package/templates/fate/void/src/ui/CommentCard.tsx +80 -0
  248. package/templates/fate/void/src/ui/CreatePost.tsx +111 -0
  249. package/templates/fate/void/src/ui/Error.tsx +19 -0
  250. package/templates/fate/void/src/ui/EventCard.tsx +152 -0
  251. package/templates/fate/void/src/ui/H2.tsx +15 -0
  252. package/templates/fate/void/src/ui/H3.tsx +20 -0
  253. package/templates/fate/void/src/ui/Header.tsx +96 -0
  254. package/templates/fate/void/src/ui/Input.tsx +33 -0
  255. package/templates/fate/void/src/ui/Link.tsx +11 -0
  256. package/templates/fate/void/src/ui/Number.tsx +5 -0
  257. package/templates/fate/void/src/ui/PostCard.tsx +294 -0
  258. package/templates/fate/void/src/ui/Section.tsx +23 -0
  259. package/templates/fate/void/src/ui/TagBadge.tsx +23 -0
  260. package/templates/fate/void/src/ui/UserCard.tsx +129 -0
  261. package/templates/fate/void/src/user/AuthClient.tsx +1 -0
  262. package/templates/fate/void/src/user/SessionUser.tsx +17 -0
  263. package/templates/fate/void/tsconfig.json +34 -0
  264. package/templates/fate/void/vite.config.ts +83 -0
  265. package/templates/fate/void/void.json +32 -0
  266. package/templates/fate/void/wrangler.jsonc +16 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "version": "7",
3
+ "dialect": "sqlite",
4
+ "entries": [
5
+ {
6
+ "idx": 0,
7
+ "version": "6",
8
+ "when": 1778225637900,
9
+ "tag": "20260508073357_initial_schema",
10
+ "breakpoints": true
11
+ },
12
+ {
13
+ "idx": 1,
14
+ "version": "6",
15
+ "when": 1778232300000,
16
+ "tag": "20260508120500_seed_void_demo",
17
+ "breakpoints": false
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,211 @@
1
+ import { and, db as runtimeDb, eq, gt, sql } from 'void/db';
2
+ import {
3
+ comment,
4
+ event,
5
+ eventAttendee,
6
+ post,
7
+ postToTag,
8
+ type CategoryRow,
9
+ type CommentRow,
10
+ type EventAttendeeRow,
11
+ type EventRow,
12
+ type PostRow,
13
+ type TagRow,
14
+ type UserRow,
15
+ } from './schema.ts';
16
+
17
+ type ItemRecord = Record<string, unknown>;
18
+ type InsertDatabase = Pick<typeof runtimeDb, 'insert'>;
19
+
20
+ export type CommentItem = CommentRow &
21
+ ItemRecord & {
22
+ author?: UserRow | null;
23
+ post?: PostItem | null;
24
+ };
25
+
26
+ export type PostItem = PostRow &
27
+ ItemRecord & {
28
+ author?: UserRow | null;
29
+ category?: CategoryRow | null;
30
+ comments?: Array<CommentItem>;
31
+ tags?: Array<TagRow>;
32
+ };
33
+
34
+ export type CategoryItem = CategoryRow &
35
+ ItemRecord & {
36
+ posts?: Array<PostItem>;
37
+ };
38
+
39
+ export type EventAttendeeItem = EventAttendeeRow &
40
+ ItemRecord & {
41
+ user?: UserRow | null;
42
+ };
43
+
44
+ export type EventItem = EventRow &
45
+ ItemRecord & {
46
+ attendees?: Array<EventAttendeeItem>;
47
+ host?: UserRow | null;
48
+ };
49
+
50
+ export const createPostRecord = async (
51
+ {
52
+ authorId,
53
+ categoryId,
54
+ content,
55
+ likes = 0,
56
+ tagIds = [],
57
+ title,
58
+ }: {
59
+ authorId: string;
60
+ categoryId?: string | null;
61
+ content: string;
62
+ likes?: number;
63
+ tagIds?: Array<string>;
64
+ title: string;
65
+ },
66
+ database: InsertDatabase = runtimeDb,
67
+ ) => {
68
+ const [created] = await database
69
+ .insert(post)
70
+ .values({
71
+ authorId,
72
+ categoryId,
73
+ content,
74
+ likes,
75
+ title,
76
+ updatedAt: new Date(),
77
+ })
78
+ .returning({ id: post.id });
79
+
80
+ if (!created) {
81
+ return null;
82
+ }
83
+
84
+ if (tagIds.length) {
85
+ await database.insert(postToTag).values(tagIds.map((tagId) => ({ postId: created.id, tagId })));
86
+ }
87
+
88
+ return created.id;
89
+ };
90
+
91
+ export const likePostRecord = async (id: string) => {
92
+ const [updated] = await runtimeDb
93
+ .update(post)
94
+ .set({
95
+ likes: sql`${post.likes} + 1`,
96
+ updatedAt: new Date(),
97
+ })
98
+ .where(eq(post.id, id))
99
+ .returning({ id: post.id });
100
+
101
+ return Boolean(updated);
102
+ };
103
+
104
+ export const unlikePostRecord = async (id: string) => {
105
+ const [updated] = await runtimeDb
106
+ .update(post)
107
+ .set({
108
+ likes: sql`${post.likes} - 1`,
109
+ updatedAt: new Date(),
110
+ })
111
+ .where(and(eq(post.id, id), gt(post.likes, 0)))
112
+ .returning({ id: post.id });
113
+
114
+ if (updated) {
115
+ return true;
116
+ }
117
+
118
+ const [existing] = await runtimeDb
119
+ .select({ id: post.id })
120
+ .from(post)
121
+ .where(eq(post.id, id))
122
+ .limit(1);
123
+
124
+ return Boolean(existing);
125
+ };
126
+
127
+ export const createCommentRecord = async ({
128
+ authorId,
129
+ content,
130
+ postId,
131
+ }: {
132
+ authorId: string;
133
+ content: string;
134
+ postId: string;
135
+ }) => {
136
+ const [created] = await runtimeDb
137
+ .insert(comment)
138
+ .values({
139
+ authorId,
140
+ content,
141
+ postId,
142
+ })
143
+ .returning({ id: comment.id });
144
+
145
+ return created?.id ?? null;
146
+ };
147
+
148
+ export const deleteCommentRecord = async (id: string) => {
149
+ await runtimeDb.delete(comment).where(eq(comment.id, id));
150
+ };
151
+
152
+ export const createEventRecord = async (
153
+ {
154
+ attendees,
155
+ capacity,
156
+ description,
157
+ endAt,
158
+ hostId,
159
+ livestreamUrl,
160
+ location,
161
+ name,
162
+ startAt,
163
+ topics,
164
+ type,
165
+ }: {
166
+ attendees: Array<{ notes: string | null; status: EventAttendeeRow['status']; userId: string }>;
167
+ capacity: number;
168
+ description: string;
169
+ endAt: Date;
170
+ hostId: string;
171
+ livestreamUrl: string | null;
172
+ location: string;
173
+ name: string;
174
+ startAt: Date;
175
+ topics: Array<string>;
176
+ type: EventRow['type'];
177
+ },
178
+ database: InsertDatabase = runtimeDb,
179
+ ) => {
180
+ const [created] = await database
181
+ .insert(event)
182
+ .values({
183
+ capacity,
184
+ description,
185
+ endAt,
186
+ hostId,
187
+ livestreamUrl,
188
+ location,
189
+ name,
190
+ startAt,
191
+ topics,
192
+ type,
193
+ updatedAt: new Date(),
194
+ })
195
+ .returning({ id: event.id });
196
+
197
+ if (!created) {
198
+ return null;
199
+ }
200
+
201
+ if (attendees.length) {
202
+ await database.insert(eventAttendee).values(
203
+ attendees.map((attendee) => ({
204
+ ...attendee,
205
+ eventId: created.id,
206
+ })),
207
+ );
208
+ }
209
+
210
+ return created.id;
211
+ };
@@ -0,0 +1,325 @@
1
+ import { relations } from 'drizzle-orm';
2
+ import { sql } from 'void/db';
3
+ import { index, integer, primaryKey, sqliteTable, text, uniqueIndex } from 'void/schema-d1';
4
+
5
+ const idColumn = () =>
6
+ text()
7
+ .notNull()
8
+ .$defaultFn(() => crypto.randomUUID());
9
+
10
+ const timestampColumn = () =>
11
+ integer({ mode: 'timestamp' })
12
+ .notNull()
13
+ .default(sql`(unixepoch())`);
14
+ const nullableTimestampColumn = () => integer({ mode: 'timestamp' });
15
+
16
+ export const rsvpStatus = ['INVITED', 'GOING', 'INTERESTED', 'DECLINED'] as const;
17
+ export const eventType = ['WORKSHOP', 'MEETUP', 'AMA', 'LAUNCH', 'COMMUNITY_CALL'] as const;
18
+
19
+ export const user = sqliteTable(
20
+ 'user',
21
+ {
22
+ banExpires: nullableTimestampColumn(),
23
+ banned: integer({ mode: 'boolean' }),
24
+ banReason: text(),
25
+ createdAt: timestampColumn(),
26
+ displayUsername: text(),
27
+ email: text().notNull(),
28
+ emailVerified: integer({ mode: 'boolean' }).notNull().default(false),
29
+ id: idColumn().primaryKey(),
30
+ image: text(),
31
+ name: text().notNull(),
32
+ password: text(),
33
+ role: text().notNull().default('user'),
34
+ updatedAt: timestampColumn(),
35
+ username: text(),
36
+ },
37
+ (table) => [
38
+ uniqueIndex('user_email_key').on(table.email),
39
+ uniqueIndex('user_username_key').on(table.username),
40
+ index('user_id_idx').on(table.id),
41
+ ],
42
+ );
43
+
44
+ export const session = sqliteTable(
45
+ 'session',
46
+ {
47
+ createdAt: timestampColumn(),
48
+ expiresAt: integer({ mode: 'timestamp' }).notNull(),
49
+ id: idColumn().primaryKey(),
50
+ ipAddress: text(),
51
+ token: text().notNull(),
52
+ updatedAt: timestampColumn(),
53
+ userAgent: text(),
54
+ userId: text()
55
+ .notNull()
56
+ .references(() => user.id, { onDelete: 'cascade' }),
57
+ },
58
+ (table) => [uniqueIndex('session_token_key').on(table.token)],
59
+ );
60
+
61
+ export const account = sqliteTable(
62
+ 'account',
63
+ {
64
+ accessToken: text(),
65
+ accessTokenExpiresAt: nullableTimestampColumn(),
66
+ accountId: text().notNull(),
67
+ createdAt: timestampColumn(),
68
+ id: idColumn().primaryKey(),
69
+ idToken: text(),
70
+ password: text(),
71
+ providerId: text().notNull(),
72
+ refreshToken: text(),
73
+ refreshTokenExpiresAt: nullableTimestampColumn(),
74
+ scope: text(),
75
+ updatedAt: timestampColumn(),
76
+ userId: text()
77
+ .notNull()
78
+ .references(() => user.id, { onDelete: 'cascade' }),
79
+ },
80
+ (table) => [
81
+ uniqueIndex('account_providerId_accountId_key').on(table.providerId, table.accountId),
82
+ ],
83
+ );
84
+
85
+ export const category = sqliteTable(
86
+ 'Category',
87
+ {
88
+ createdAt: timestampColumn(),
89
+ description: text(),
90
+ id: idColumn().primaryKey(),
91
+ name: text().notNull(),
92
+ },
93
+ (table) => [uniqueIndex('Category_name_key').on(table.name)],
94
+ );
95
+
96
+ export const tag = sqliteTable(
97
+ 'Tag',
98
+ {
99
+ createdAt: timestampColumn(),
100
+ description: text(),
101
+ id: idColumn().primaryKey(),
102
+ name: text().notNull(),
103
+ },
104
+ (table) => [uniqueIndex('Tag_name_key').on(table.name)],
105
+ );
106
+
107
+ export const post = sqliteTable(
108
+ 'Post',
109
+ {
110
+ authorId: text()
111
+ .notNull()
112
+ .references(() => user.id, { onDelete: 'cascade' }),
113
+ categoryId: text().references(() => category.id, { onDelete: 'set null' }),
114
+ content: text().notNull(),
115
+ createdAt: timestampColumn(),
116
+ id: idColumn().primaryKey(),
117
+ likes: integer().notNull().default(0),
118
+ title: text().notNull(),
119
+ updatedAt: timestampColumn(),
120
+ },
121
+ (table) => [index('Post_authorId_idx').on(table.authorId)],
122
+ );
123
+
124
+ export const comment = sqliteTable(
125
+ 'Comment',
126
+ {
127
+ authorId: text().references(() => user.id, { onDelete: 'set null' }),
128
+ content: text().notNull(),
129
+ createdAt: timestampColumn(),
130
+ id: idColumn().primaryKey(),
131
+ postId: text()
132
+ .notNull()
133
+ .references(() => post.id, { onDelete: 'cascade' }),
134
+ },
135
+ (table) => [
136
+ index('Comment_authorId_idx').on(table.authorId),
137
+ index('Comment_postId_idx').on(table.postId),
138
+ ],
139
+ );
140
+
141
+ export const event = sqliteTable(
142
+ 'Event',
143
+ {
144
+ capacity: integer().notNull(),
145
+ createdAt: timestampColumn(),
146
+ description: text().notNull(),
147
+ endAt: integer({ mode: 'timestamp' }).notNull(),
148
+ hostId: text()
149
+ .notNull()
150
+ .references(() => user.id, { onDelete: 'cascade' }),
151
+ id: idColumn().primaryKey(),
152
+ livestreamUrl: text(),
153
+ location: text().notNull(),
154
+ name: text().notNull(),
155
+ startAt: integer({ mode: 'timestamp' }).notNull(),
156
+ topics: text({ mode: 'json' })
157
+ .$type<Array<string>>()
158
+ .notNull()
159
+ .default(sql`'[]'`),
160
+ type: text({ enum: eventType }).notNull().default('MEETUP'),
161
+ updatedAt: timestampColumn(),
162
+ },
163
+ (table) => [index('Event_hostId_idx').on(table.hostId)],
164
+ );
165
+
166
+ export const eventAttendee = sqliteTable(
167
+ 'EventAttendee',
168
+ {
169
+ createdAt: timestampColumn(),
170
+ eventId: text()
171
+ .notNull()
172
+ .references(() => event.id, { onDelete: 'cascade' }),
173
+ id: idColumn().primaryKey(),
174
+ notes: text(),
175
+ status: text({ enum: rsvpStatus }).notNull().default('INVITED'),
176
+ userId: text()
177
+ .notNull()
178
+ .references(() => user.id, { onDelete: 'cascade' }),
179
+ },
180
+ (table) => [
181
+ uniqueIndex('EventAttendee_eventId_userId_key').on(table.eventId, table.userId),
182
+ index('EventAttendee_userId_idx').on(table.userId),
183
+ ],
184
+ );
185
+
186
+ export const postToTag = sqliteTable(
187
+ '_PostTags',
188
+ {
189
+ postId: text('A')
190
+ .notNull()
191
+ .references(() => post.id, { onDelete: 'cascade' }),
192
+ tagId: text('B')
193
+ .notNull()
194
+ .references(() => tag.id, { onDelete: 'cascade' }),
195
+ },
196
+ (table) => [
197
+ primaryKey({ columns: [table.postId, table.tagId], name: '_PostTags_AB_pkey' }),
198
+ index('_PostTags_B_index').on(table.tagId),
199
+ ],
200
+ );
201
+
202
+ export const userRelations = relations(user, ({ many }) => ({
203
+ accounts: many(account),
204
+ comments: many(comment),
205
+ eventAttendance: many(eventAttendee),
206
+ hostedEvents: many(event, { relationName: 'EventHost' }),
207
+ posts: many(post),
208
+ sessions: many(session),
209
+ }));
210
+
211
+ export const sessionRelations = relations(session, ({ one }) => ({
212
+ user: one(user, {
213
+ fields: [session.userId],
214
+ references: [user.id],
215
+ }),
216
+ }));
217
+
218
+ export const accountRelations = relations(account, ({ one }) => ({
219
+ user: one(user, {
220
+ fields: [account.userId],
221
+ references: [user.id],
222
+ }),
223
+ }));
224
+
225
+ export const categoryRelations = relations(category, ({ many }) => ({
226
+ posts: many(post),
227
+ }));
228
+
229
+ export const tagRelations = relations(tag, ({ many }) => ({
230
+ postLinks: many(postToTag),
231
+ }));
232
+
233
+ export const postRelations = relations(post, ({ many, one }) => ({
234
+ author: one(user, {
235
+ fields: [post.authorId],
236
+ references: [user.id],
237
+ }),
238
+ category: one(category, {
239
+ fields: [post.categoryId],
240
+ references: [category.id],
241
+ }),
242
+ comments: many(comment),
243
+ postLinks: many(postToTag),
244
+ }));
245
+
246
+ export const commentRelations = relations(comment, ({ one }) => ({
247
+ author: one(user, {
248
+ fields: [comment.authorId],
249
+ references: [user.id],
250
+ }),
251
+ post: one(post, {
252
+ fields: [comment.postId],
253
+ references: [post.id],
254
+ }),
255
+ }));
256
+
257
+ export const eventRelations = relations(event, ({ many, one }) => ({
258
+ attendees: many(eventAttendee),
259
+ host: one(user, {
260
+ fields: [event.hostId],
261
+ references: [user.id],
262
+ relationName: 'EventHost',
263
+ }),
264
+ }));
265
+
266
+ export const eventAttendeeRelations = relations(eventAttendee, ({ one }) => ({
267
+ event: one(event, {
268
+ fields: [eventAttendee.eventId],
269
+ references: [event.id],
270
+ }),
271
+ user: one(user, {
272
+ fields: [eventAttendee.userId],
273
+ references: [user.id],
274
+ }),
275
+ }));
276
+
277
+ export const postToTagRelations = relations(postToTag, ({ one }) => ({
278
+ post: one(post, {
279
+ fields: [postToTag.postId],
280
+ references: [post.id],
281
+ }),
282
+ tag: one(tag, {
283
+ fields: [postToTag.tagId],
284
+ references: [tag.id],
285
+ }),
286
+ }));
287
+
288
+ export type UserRow = typeof user.$inferSelect;
289
+ export type SessionRow = typeof session.$inferSelect;
290
+ export type AccountRow = typeof account.$inferSelect;
291
+ export type CategoryRow = typeof category.$inferSelect;
292
+ export type TagRow = typeof tag.$inferSelect;
293
+ export type PostRow = typeof post.$inferSelect;
294
+ export type CommentRow = typeof comment.$inferSelect;
295
+ export type EventRow = typeof event.$inferSelect;
296
+ export type EventAttendeeRow = typeof eventAttendee.$inferSelect;
297
+ export type RSVPStatus = (typeof rsvpStatus)[number];
298
+ export type EventType = (typeof eventType)[number];
299
+
300
+ const schema = {
301
+ account,
302
+ accountRelations,
303
+ category,
304
+ categoryRelations,
305
+ comment,
306
+ commentRelations,
307
+ event,
308
+ eventAttendee,
309
+ eventAttendeeRelations,
310
+ eventRelations,
311
+ eventType,
312
+ post,
313
+ postRelations,
314
+ postToTag,
315
+ postToTagRelations,
316
+ rsvpStatus,
317
+ session,
318
+ sessionRelations,
319
+ tag,
320
+ tagRelations,
321
+ user,
322
+ userRelations,
323
+ } as const;
324
+
325
+ export default schema;
@@ -0,0 +1,31 @@
1
+ import { betterAuth, type BetterAuthOptions } from 'better-auth';
2
+ import { drizzleAdapter } from 'better-auth/adapters/drizzle';
3
+ import authConfig from '../auth.ts';
4
+ import schema from './schema.ts';
5
+
6
+ const seedAuthURL = 'http://localhost:6001';
7
+
8
+ export const createSeedAuth = (database: Parameters<typeof drizzleAdapter>[0]) => {
9
+ const defaults: BetterAuthOptions = {
10
+ basePath: '/api/auth',
11
+ baseURL: seedAuthURL,
12
+ database: drizzleAdapter(database, {
13
+ provider: 'sqlite',
14
+ schema,
15
+ }),
16
+ emailAndPassword: {
17
+ enabled: true,
18
+ },
19
+ secret: process.env.BETTER_AUTH_SECRET ?? 'fate-void-example-development-secret',
20
+ trustedOrigins: [seedAuthURL],
21
+ };
22
+
23
+ return betterAuth(
24
+ authConfig({
25
+ defaults,
26
+ dialect: 'sqlite',
27
+ env: process.env,
28
+ request: new Request(`${seedAuthURL}/api/auth`),
29
+ }),
30
+ );
31
+ };