create-fate 1.0.3 → 1.3.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.
- package/README.md +3 -0
- package/bin/create-fate.mjs +594 -9
- package/package.json +2 -2
- package/templates/fate/README.md +3 -0
- package/templates/fate/_shared/vue-app/package.json +54 -0
- package/templates/fate/_shared/vue-app/pages/index.vue +57 -0
- package/templates/fate/_shared/vue-app/pages/layout.vue +32 -0
- package/templates/fate/_shared/vue-app/pages/post/[id].vue +24 -0
- package/templates/fate/_shared/vue-app/src/App.css +63 -0
- package/templates/fate/_shared/vue-app/src/fateViews.ts +14 -0
- package/templates/fate/_shared/vue-app/src/lib/cx.ts +6 -0
- package/templates/fate/_shared/vue-app/src/lib/env.ts +3 -0
- package/templates/fate/_shared/vue-app/src/ui/Button.vue +67 -0
- package/templates/fate/_shared/vue-app/src/ui/Card.vue +16 -0
- package/templates/fate/_shared/vue-app/src/ui/Error.vue +9 -0
- package/templates/fate/_shared/vue-app/src/ui/ErrorBoundary.vue +22 -0
- package/templates/fate/_shared/vue-app/src/ui/Header.vue +35 -0
- package/templates/fate/_shared/vue-app/src/ui/Link.vue +15 -0
- package/templates/fate/_shared/vue-app/src/ui/PostCard.vue +29 -0
- package/templates/fate/_shared/vue-app/src/ui/Section.vue +19 -0
- package/templates/fate/_shared/vue-app/src/ui/Thinking.vue +11 -0
- package/templates/fate/_shared/vue-app/src/ui/UserCard.vue +23 -0
- package/templates/fate/_shared/vue-app/src/vue-shims.d.ts +6 -0
- package/templates/fate/_shared/vue-app/tsconfig.json +22 -0
- package/templates/fate/_shared/vue-app/vite.config.ts +58 -0
- package/templates/fate/_shared/vue-app/void.json +5 -0
- package/templates/fate/cloudflare/.github/workflows/push.yml +27 -0
- package/templates/fate/cloudflare/.vite-hooks/pre-commit +1 -0
- package/templates/fate/cloudflare/.vscode/extensions.json +12 -0
- package/templates/fate/cloudflare/.vscode/settings.json +25 -0
- package/templates/fate/cloudflare/AGENTS.md +55 -0
- package/templates/fate/cloudflare/README.md +126 -0
- package/templates/fate/cloudflare/_gitignore +20 -0
- package/templates/fate/cloudflare/client/package.json +56 -0
- package/templates/fate/cloudflare/client/pages/index.tsx +96 -0
- package/templates/fate/cloudflare/client/pages/layout.tsx +82 -0
- package/templates/fate/cloudflare/client/pages/login.tsx +107 -0
- package/templates/fate/cloudflare/client/pages/post/[id].tsx +22 -0
- package/templates/fate/cloudflare/client/pages/search.tsx +99 -0
- package/templates/fate/cloudflare/client/public/icon.svg +1 -0
- package/templates/fate/cloudflare/client/src/App.css +153 -0
- package/templates/fate/cloudflare/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/cloudflare/client/src/lib/cx.tsx +6 -0
- package/templates/fate/cloudflare/client/src/lib/env.tsx +5 -0
- package/templates/fate/cloudflare/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/cloudflare/client/src/ui/Button.tsx +108 -0
- package/templates/fate/cloudflare/client/src/ui/Card.tsx +32 -0
- package/templates/fate/cloudflare/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/cloudflare/client/src/ui/Error.tsx +19 -0
- package/templates/fate/cloudflare/client/src/ui/H2.tsx +15 -0
- package/templates/fate/cloudflare/client/src/ui/H3.tsx +20 -0
- package/templates/fate/cloudflare/client/src/ui/Header.tsx +98 -0
- package/templates/fate/cloudflare/client/src/ui/Input.tsx +17 -0
- package/templates/fate/cloudflare/client/src/ui/Link.tsx +11 -0
- package/templates/fate/cloudflare/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/cloudflare/client/src/ui/PostCard.tsx +303 -0
- package/templates/fate/cloudflare/client/src/ui/Section.tsx +23 -0
- package/templates/fate/cloudflare/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/cloudflare/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/cloudflare/client/translations/de_DE.json +689 -0
- package/templates/fate/cloudflare/client/translations/ja_JP.json +689 -0
- package/templates/fate/cloudflare/client/vite.config.ts +45 -0
- package/templates/fate/cloudflare/client/void.json +29 -0
- package/templates/fate/cloudflare/package.json +46 -0
- package/templates/fate/cloudflare/pnpm-workspace.yaml +39 -0
- package/templates/fate/cloudflare/server/.env.example +1 -0
- package/templates/fate/cloudflare/server/db/db.ts +30 -0
- package/templates/fate/cloudflare/server/db/migrations/20260508073357_initial_schema.sql +133 -0
- package/templates/fate/cloudflare/server/db/migrations/20260508120500_seed_cloudflare_demo.sql +170 -0
- package/templates/fate/cloudflare/server/db/migrations/meta/20260508073357_snapshot.json +868 -0
- package/templates/fate/cloudflare/server/db/migrations/meta/_journal.json +20 -0
- package/templates/fate/cloudflare/server/db/queries.ts +211 -0
- package/templates/fate/cloudflare/server/db/schema.ts +332 -0
- package/templates/fate/cloudflare/server/package.json +34 -0
- package/templates/fate/cloudflare/server/src/fate/commentPermissions.ts +17 -0
- package/templates/fate/cloudflare/server/src/fate/context.ts +15 -0
- package/templates/fate/cloudflare/server/src/fate/live.ts +7 -0
- package/templates/fate/cloudflare/server/src/fate/server.ts +411 -0
- package/templates/fate/cloudflare/server/src/fate/views.ts +164 -0
- package/templates/fate/cloudflare/server/src/index.ts +80 -0
- package/templates/fate/cloudflare/server/src/lib/auth.ts +66 -0
- package/templates/fate/cloudflare/server/src/lib/origins.ts +17 -0
- package/templates/fate/cloudflare/server/src/router.ts +2 -0
- package/templates/fate/cloudflare/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/cloudflare/server/tsconfig.json +10 -0
- package/templates/fate/cloudflare/server/wrangler.jsonc +29 -0
- package/templates/fate/cloudflare/tsconfig.json +33 -0
- package/templates/fate/cloudflare/vite.config.ts +72 -0
- package/templates/fate/drizzle/README.md +2 -8
- package/templates/fate/drizzle/client/pages/layout.tsx +2 -2
- package/templates/fate/drizzle/client/src/ui/PostCard.tsx +38 -15
- package/templates/fate/drizzle/pnpm-workspace.yaml +9 -3
- package/templates/fate/drizzle/server/vite.config.ts +1 -1
- package/templates/fate/graphql/.github/workflows/push.yml +27 -0
- package/templates/fate/graphql/.vite-hooks/pre-commit +1 -0
- package/templates/fate/graphql/.vscode/extensions.json +12 -0
- package/templates/fate/graphql/.vscode/settings.json +25 -0
- package/templates/fate/graphql/AGENTS.md +55 -0
- package/templates/fate/graphql/README.md +121 -0
- package/templates/fate/graphql/_gitignore +23 -0
- package/templates/fate/graphql/client/package.json +57 -0
- package/templates/fate/graphql/client/pages/index.tsx +96 -0
- package/templates/fate/graphql/client/pages/layout.tsx +83 -0
- package/templates/fate/graphql/client/pages/login.tsx +107 -0
- package/templates/fate/graphql/client/pages/post/[id].tsx +22 -0
- package/templates/fate/graphql/client/pages/search.tsx +99 -0
- package/templates/fate/graphql/client/public/icon.svg +1 -0
- package/templates/fate/graphql/client/src/App.css +153 -0
- package/templates/fate/graphql/client/src/lib/AvailableLanguages.tsx +5 -0
- package/templates/fate/graphql/client/src/lib/cx.tsx +6 -0
- package/templates/fate/graphql/client/src/lib/env.tsx +5 -0
- package/templates/fate/graphql/client/src/ui/Badge.tsx +31 -0
- package/templates/fate/graphql/client/src/ui/Button.tsx +108 -0
- package/templates/fate/graphql/client/src/ui/Card.tsx +32 -0
- package/templates/fate/graphql/client/src/ui/CommentCard.tsx +75 -0
- package/templates/fate/graphql/client/src/ui/Error.tsx +19 -0
- package/templates/fate/graphql/client/src/ui/H2.tsx +15 -0
- package/templates/fate/graphql/client/src/ui/H3.tsx +20 -0
- package/templates/fate/graphql/client/src/ui/Header.tsx +98 -0
- package/templates/fate/graphql/client/src/ui/Input.tsx +17 -0
- package/templates/fate/graphql/client/src/ui/Link.tsx +11 -0
- package/templates/fate/graphql/client/src/ui/LocaleSwitcher.tsx +52 -0
- package/templates/fate/graphql/client/src/ui/PostCard.tsx +303 -0
- package/templates/fate/graphql/client/src/ui/Section.tsx +23 -0
- package/templates/fate/graphql/client/src/ui/UserCard.tsx +131 -0
- package/templates/fate/graphql/client/src/user/AuthClient.tsx +6 -0
- package/templates/fate/graphql/client/translations/de_DE.json +689 -0
- package/templates/fate/graphql/client/translations/ja_JP.json +689 -0
- package/templates/fate/graphql/client/vite.config.ts +40 -0
- package/templates/fate/graphql/client/void.json +29 -0
- package/templates/fate/graphql/docker-compose.yml +11 -0
- package/templates/fate/graphql/package.json +47 -0
- package/templates/fate/graphql/pnpm-workspace.yaml +45 -0
- package/templates/fate/graphql/server/.env.example +5 -0
- package/templates/fate/graphql/server/README.md +27 -0
- package/templates/fate/graphql/server/package.json +64 -0
- package/templates/fate/graphql/server/prisma.config.ts +17 -0
- package/templates/fate/graphql/server/scripts/generate-graphql.tsx +40 -0
- package/templates/fate/graphql/server/src/__tests__/index.test.tsx +5 -0
- package/templates/fate/graphql/server/src/app.tsx +47 -0
- package/templates/fate/graphql/server/src/graphql/builder.tsx +121 -0
- package/templates/fate/graphql/server/src/graphql/context.tsx +22 -0
- package/templates/fate/graphql/server/src/graphql/fate.ts +85 -0
- package/templates/fate/graphql/server/src/graphql/lib/authDirectives.tsx +93 -0
- package/templates/fate/graphql/server/src/graphql/lib/decodeGlobalID.tsx +12 -0
- package/templates/fate/graphql/server/src/graphql/lib/decodeIDOrThrow.tsx +10 -0
- package/templates/fate/graphql/server/src/graphql/lib/encodeGlobalID.tsx +5 -0
- package/templates/fate/graphql/server/src/graphql/live.tsx +241 -0
- package/templates/fate/graphql/server/src/graphql/mutations.tsx +265 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Category.tsx +35 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Comment.tsx +46 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Event.tsx +71 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Post.tsx +45 -0
- package/templates/fate/graphql/server/src/graphql/nodes/Tag.tsx +11 -0
- package/templates/fate/graphql/server/src/graphql/nodes/User.tsx +28 -0
- package/templates/fate/graphql/server/src/graphql/schema.tsx +16 -0
- package/templates/fate/graphql/server/src/graphql/views.ts +193 -0
- package/templates/fate/graphql/server/src/index.tsx +45 -0
- package/templates/fate/graphql/server/src/lib/auth.tsx +31 -0
- package/templates/fate/graphql/server/src/lib/env.tsx +3 -0
- package/templates/fate/graphql/server/src/lib/origins.ts +18 -0
- package/templates/fate/graphql/server/src/prisma/prisma.tsx +9 -0
- package/templates/fate/graphql/server/src/prisma/schema.prisma +181 -0
- package/templates/fate/graphql/server/src/prisma/seed.tsx +161 -0
- package/templates/fate/graphql/server/src/user/SessionUser.tsx +17 -0
- package/templates/fate/graphql/server/src/user/isAdmin.tsx +3 -0
- package/templates/fate/graphql/server/tsconfig.json +33 -0
- package/templates/fate/graphql/server/vite.config.ts +55 -0
- package/templates/fate/graphql/tsconfig.json +33 -0
- package/templates/fate/graphql/vite.config.ts +80 -0
- package/templates/fate/graphql-client/.env.example +3 -0
- package/templates/fate/graphql-client/AGENTS.md +3 -0
- package/templates/fate/graphql-client/README.md +69 -0
- package/templates/fate/graphql-client/_gitignore +14 -0
- package/templates/fate/graphql-client/package.json +62 -0
- package/templates/fate/graphql-client/pages/index.tsx +69 -0
- package/templates/fate/graphql-client/pages/layout.tsx +41 -0
- package/templates/fate/graphql-client/pnpm-workspace.yaml +38 -0
- package/templates/fate/graphql-client/public/icon.svg +8 -0
- package/templates/fate/graphql-client/src/App.css +28 -0
- package/templates/fate/graphql-client/src/fate/graphql.ts +52 -0
- package/templates/fate/graphql-client/tsconfig.json +30 -0
- package/templates/fate/graphql-client/vite.config.ts +87 -0
- package/templates/fate/graphql-client/void.json +29 -0
- package/templates/fate/http/README.md +2 -8
- package/templates/fate/http/client/pages/layout.tsx +2 -2
- package/templates/fate/http/client/src/ui/PostCard.tsx +37 -15
- package/templates/fate/http/pnpm-workspace.yaml +9 -3
- package/templates/fate/http/server/vite.config.ts +1 -1
- package/templates/fate/prisma/README.md +2 -8
- package/templates/fate/prisma/client/pages/layout.tsx +2 -2
- package/templates/fate/prisma/client/src/ui/PostCard.tsx +37 -15
- package/templates/fate/prisma/pnpm-workspace.yaml +9 -3
- package/templates/fate/prisma/server/vite.config.ts +1 -1
- package/templates/fate/prisma/vite.config.ts +1 -1
- package/templates/fate/void/package.json +10 -2
- package/templates/fate/void/pages/login.tsx +3 -3
- package/templates/fate/void/pnpm-workspace.yaml +12 -4
- package/templates/fate/void/src/ui/Button.tsx +42 -26
- package/templates/fate/void/src/ui/CategoryCard.tsx +3 -7
- package/templates/fate/void/src/ui/PostCard.tsx +38 -15
- package/templates/fate/void/vite.config.ts +15 -14
- package/templates/fate/drizzle/public/fate-logo-dark.svg +0 -4
- package/templates/fate/drizzle/public/fate-logo.svg +0 -4
- package/templates/fate/http/public/fate-logo-dark.svg +0 -4
- package/templates/fate/http/public/fate-logo.svg +0 -4
- package/templates/fate/prisma/public/fate-logo-dark.svg +0 -4
- package/templates/fate/prisma/public/fate-logo.svg +0 -4
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import prisma from '../../prisma/prisma.tsx';
|
|
2
|
+
import builder, { DateTimeScalar } from '../builder.tsx';
|
|
3
|
+
|
|
4
|
+
const EventAttendee = builder.prismaNode('EventAttendee', {
|
|
5
|
+
fields: (t) => ({
|
|
6
|
+
notes: t.exposeString('notes'),
|
|
7
|
+
status: t.string({
|
|
8
|
+
nullable: false,
|
|
9
|
+
resolve: ({ status }) => status,
|
|
10
|
+
}),
|
|
11
|
+
user: t.relation('user', { nullable: false }),
|
|
12
|
+
}),
|
|
13
|
+
id: { field: 'id' },
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const Event = builder.prismaNode('Event', {
|
|
17
|
+
fields: (t) => ({
|
|
18
|
+
attendees: t.relatedConnection('attendees', {
|
|
19
|
+
cursor: 'id',
|
|
20
|
+
nullable: false,
|
|
21
|
+
query: {
|
|
22
|
+
orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
|
|
23
|
+
},
|
|
24
|
+
}),
|
|
25
|
+
attendingCount: t.int({
|
|
26
|
+
nullable: false,
|
|
27
|
+
resolve: ({ id }) => prisma.eventAttendee.count({ where: { eventId: id, status: 'GOING' } }),
|
|
28
|
+
}),
|
|
29
|
+
capacity: t.exposeInt('capacity', { nullable: false }),
|
|
30
|
+
description: t.exposeString('description', { nullable: false }),
|
|
31
|
+
endAt: t.field({
|
|
32
|
+
nullable: false,
|
|
33
|
+
resolve: ({ endAt }) => endAt,
|
|
34
|
+
type: DateTimeScalar,
|
|
35
|
+
}),
|
|
36
|
+
host: t.relation('host', { nullable: false }),
|
|
37
|
+
livestreamUrl: t.exposeString('livestreamUrl'),
|
|
38
|
+
location: t.exposeString('location', { nullable: false }),
|
|
39
|
+
name: t.exposeString('name', { nullable: false }),
|
|
40
|
+
startAt: t.field({
|
|
41
|
+
nullable: false,
|
|
42
|
+
resolve: ({ startAt }) => startAt,
|
|
43
|
+
type: DateTimeScalar,
|
|
44
|
+
}),
|
|
45
|
+
topics: t.field({
|
|
46
|
+
nullable: false,
|
|
47
|
+
resolve: ({ topics }) => topics,
|
|
48
|
+
type: ['String'],
|
|
49
|
+
}),
|
|
50
|
+
type: t.string({
|
|
51
|
+
nullable: false,
|
|
52
|
+
resolve: ({ type }) => type,
|
|
53
|
+
}),
|
|
54
|
+
}),
|
|
55
|
+
id: { field: 'id' },
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
builder.queryFields((t) => ({
|
|
59
|
+
events: t.prismaConnection({
|
|
60
|
+
cursor: 'id',
|
|
61
|
+
resolve: (query) =>
|
|
62
|
+
prisma.event.findMany({
|
|
63
|
+
...query,
|
|
64
|
+
orderBy: [{ startAt: 'asc' }, { id: 'asc' }],
|
|
65
|
+
}),
|
|
66
|
+
type: 'Event',
|
|
67
|
+
}),
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
export { EventAttendee };
|
|
71
|
+
export default Event;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import prisma from '../../prisma/prisma.tsx';
|
|
2
|
+
import builder from '../builder.tsx';
|
|
3
|
+
|
|
4
|
+
const Post = builder.prismaNode('Post', {
|
|
5
|
+
fields: (t) => ({
|
|
6
|
+
author: t.relation('author', { nullable: false }),
|
|
7
|
+
category: t.relation('category'),
|
|
8
|
+
commentCount: t.int({
|
|
9
|
+
nullable: false,
|
|
10
|
+
resolve: ({ id }) => prisma.comment.count({ where: { postId: id } }),
|
|
11
|
+
}),
|
|
12
|
+
comments: t.relatedConnection('comments', {
|
|
13
|
+
cursor: 'id',
|
|
14
|
+
nullable: false,
|
|
15
|
+
query: {
|
|
16
|
+
orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
content: t.exposeString('content', { nullable: false }),
|
|
20
|
+
likes: t.exposeInt('likes', { nullable: false }),
|
|
21
|
+
tags: t.relatedConnection('tags', {
|
|
22
|
+
cursor: 'id',
|
|
23
|
+
nullable: false,
|
|
24
|
+
query: {
|
|
25
|
+
orderBy: [{ name: 'asc' }, { id: 'asc' }],
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
title: t.exposeString('title', { nullable: false }),
|
|
29
|
+
}),
|
|
30
|
+
id: { field: 'id' },
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
builder.queryFields((t) => ({
|
|
34
|
+
posts: t.prismaConnection({
|
|
35
|
+
cursor: 'id',
|
|
36
|
+
resolve: (query) =>
|
|
37
|
+
prisma.post.findMany({
|
|
38
|
+
...query,
|
|
39
|
+
orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],
|
|
40
|
+
}),
|
|
41
|
+
type: 'Post',
|
|
42
|
+
}),
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
export default Post;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import builder from '../builder.tsx';
|
|
2
|
+
|
|
3
|
+
const Tag = builder.prismaNode('Tag', {
|
|
4
|
+
fields: (t) => ({
|
|
5
|
+
description: t.exposeString('description'),
|
|
6
|
+
name: t.exposeString('name', { nullable: false }),
|
|
7
|
+
}),
|
|
8
|
+
id: { field: 'id' },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export default Tag;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import prisma from '../../prisma/prisma.tsx';
|
|
2
|
+
import builder from '../builder.tsx';
|
|
3
|
+
|
|
4
|
+
const User = builder.prismaNode('User', {
|
|
5
|
+
fields: (t) => ({
|
|
6
|
+
email: t.exposeString('email', {
|
|
7
|
+
authScopes: (user) => ({ self: user.id }),
|
|
8
|
+
}),
|
|
9
|
+
name: t.exposeString('name', { nullable: false }),
|
|
10
|
+
username: t.exposeString('username'),
|
|
11
|
+
}),
|
|
12
|
+
id: { field: 'id' },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
builder.queryFields((t) => ({
|
|
16
|
+
viewer: t.prismaField({
|
|
17
|
+
resolve: (query, _root, _args, { sessionUser }) =>
|
|
18
|
+
sessionUser
|
|
19
|
+
? prisma.user.findUniqueOrThrow({
|
|
20
|
+
...query,
|
|
21
|
+
where: { id: sessionUser.id },
|
|
22
|
+
})
|
|
23
|
+
: void query.include,
|
|
24
|
+
type: 'User',
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export default User;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { writeFileSync } from 'node:fs';
|
|
2
|
+
import './schemaImportMap.tsx';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { printSchemaWithDirectives } from '@graphql-tools/utils';
|
|
5
|
+
import builder from './builder.tsx';
|
|
6
|
+
|
|
7
|
+
const schema = builder.toSchema();
|
|
8
|
+
|
|
9
|
+
if (process.env.NODE_ENV === 'development') {
|
|
10
|
+
writeFileSync(
|
|
11
|
+
join(process.cwd(), 'src/graphql/schema.graphql'),
|
|
12
|
+
printSchemaWithDirectives(schema),
|
|
13
|
+
'utf8',
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
export default schema;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { computed, count, dataView, field, list, type Entity } from '@nkzw/fate/server';
|
|
2
|
+
import type {
|
|
3
|
+
Category as PrismaCategory,
|
|
4
|
+
Comment as PrismaComment,
|
|
5
|
+
Event as PrismaEvent,
|
|
6
|
+
EventAttendee as PrismaEventAttendee,
|
|
7
|
+
Post as PrismaPost,
|
|
8
|
+
Tag as PrismaTag,
|
|
9
|
+
User as PrismaUser,
|
|
10
|
+
} from '../prisma/prisma-client/client.ts';
|
|
11
|
+
import type { AppContext } from './context.tsx';
|
|
12
|
+
|
|
13
|
+
export type CommentItem = PrismaComment & {
|
|
14
|
+
author?: PrismaUser | null;
|
|
15
|
+
post?: PrismaPost | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type PostItem = PrismaPost & {
|
|
19
|
+
_count?: { comments: number };
|
|
20
|
+
author?: PrismaUser | null;
|
|
21
|
+
category?: PrismaCategory | null;
|
|
22
|
+
comments?: Array<CommentItem>;
|
|
23
|
+
tags?: Array<PrismaTag>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type CategoryItem = PrismaCategory & {
|
|
27
|
+
_count?: { posts: number };
|
|
28
|
+
posts?: Array<PostItem>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type EventAttendeeItem = PrismaEventAttendee & {
|
|
32
|
+
user?: PrismaUser | null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type EventItem = PrismaEvent & {
|
|
36
|
+
_count?: { attendees: number };
|
|
37
|
+
attendees?: Array<EventAttendeeItem>;
|
|
38
|
+
host?: PrismaUser | null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const userDataView = dataView<PrismaUser>('User')({
|
|
42
|
+
email: computed<PrismaUser, string | null, AppContext>({
|
|
43
|
+
authorize: ({ id }, context) => context?.sessionUser?.id === id,
|
|
44
|
+
resolve: (_item, deps) => (deps.email as string | null) ?? null,
|
|
45
|
+
select: {
|
|
46
|
+
email: field('email'),
|
|
47
|
+
},
|
|
48
|
+
}),
|
|
49
|
+
id: true,
|
|
50
|
+
name: true,
|
|
51
|
+
username: true,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
export const tagDataView = dataView<PrismaTag>('Tag')({
|
|
55
|
+
description: true,
|
|
56
|
+
id: true,
|
|
57
|
+
name: true,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const categorySummaryDataView = dataView<PrismaCategory>('Category')({
|
|
61
|
+
id: true,
|
|
62
|
+
name: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const basePost = {
|
|
66
|
+
author: userDataView,
|
|
67
|
+
category: categorySummaryDataView,
|
|
68
|
+
commentCount: computed<PostItem, number>({
|
|
69
|
+
resolve: (_item, deps) => (deps.count as number) ?? 0,
|
|
70
|
+
select: {
|
|
71
|
+
count: count('comments'),
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
content: true,
|
|
75
|
+
id: true,
|
|
76
|
+
likes: true,
|
|
77
|
+
title: true,
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
export const postSummaryDataView = dataView<PostItem>('Post')({
|
|
81
|
+
...basePost,
|
|
82
|
+
tags: list(tagDataView),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
export const commentDataView = dataView<CommentItem>('Comment')({
|
|
86
|
+
author: userDataView,
|
|
87
|
+
content: true,
|
|
88
|
+
id: true,
|
|
89
|
+
post: postSummaryDataView,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
export const postDataView = dataView<PostItem>('Post')({
|
|
93
|
+
...basePost,
|
|
94
|
+
comments: list(commentDataView, { orderBy: { createdAt: 'asc', id: 'asc' } }),
|
|
95
|
+
tags: list(tagDataView),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
export const categoryDataView = dataView<CategoryItem>('Category')({
|
|
99
|
+
description: true,
|
|
100
|
+
id: true,
|
|
101
|
+
name: true,
|
|
102
|
+
postCount: computed<CategoryItem, number>({
|
|
103
|
+
resolve: (_item, deps) => (deps.count as number) ?? 0,
|
|
104
|
+
select: {
|
|
105
|
+
count: count('posts'),
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
posts: list(postDataView),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
export const eventAttendeeDataView = dataView<EventAttendeeItem>('EventAttendee')({
|
|
112
|
+
id: true,
|
|
113
|
+
notes: true,
|
|
114
|
+
status: true,
|
|
115
|
+
user: userDataView,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
export const eventDataView = dataView<EventItem>('Event')({
|
|
119
|
+
attendees: list(eventAttendeeDataView, { orderBy: { createdAt: 'asc', id: 'asc' } }),
|
|
120
|
+
attendingCount: computed<EventItem, number>({
|
|
121
|
+
resolve: (_item, deps) => (deps.count as number) ?? 0,
|
|
122
|
+
select: {
|
|
123
|
+
count: count('attendees', {
|
|
124
|
+
where: { status: 'GOING' },
|
|
125
|
+
}),
|
|
126
|
+
},
|
|
127
|
+
}),
|
|
128
|
+
capacity: true,
|
|
129
|
+
description: true,
|
|
130
|
+
endAt: true,
|
|
131
|
+
host: userDataView,
|
|
132
|
+
id: true,
|
|
133
|
+
livestreamUrl: true,
|
|
134
|
+
location: true,
|
|
135
|
+
name: true,
|
|
136
|
+
startAt: true,
|
|
137
|
+
topics: true,
|
|
138
|
+
type: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
export type User = Entity<typeof userDataView, 'User'>;
|
|
142
|
+
export type Tag = Entity<typeof tagDataView, 'Tag'>;
|
|
143
|
+
export type Comment = Entity<
|
|
144
|
+
typeof commentDataView,
|
|
145
|
+
'Comment',
|
|
146
|
+
{
|
|
147
|
+
author: User;
|
|
148
|
+
post: Post;
|
|
149
|
+
}
|
|
150
|
+
>;
|
|
151
|
+
export type EventAttendee = Entity<
|
|
152
|
+
typeof eventAttendeeDataView,
|
|
153
|
+
'EventAttendee',
|
|
154
|
+
{
|
|
155
|
+
user: User;
|
|
156
|
+
}
|
|
157
|
+
>;
|
|
158
|
+
export type Post = Entity<
|
|
159
|
+
typeof postDataView,
|
|
160
|
+
'Post',
|
|
161
|
+
{
|
|
162
|
+
author: User;
|
|
163
|
+
category: Category | null;
|
|
164
|
+
comments: Array<Comment>;
|
|
165
|
+
tags: Array<Tag>;
|
|
166
|
+
}
|
|
167
|
+
>;
|
|
168
|
+
export type Category = Entity<
|
|
169
|
+
typeof categoryDataView,
|
|
170
|
+
'Category',
|
|
171
|
+
{
|
|
172
|
+
posts: Array<Post>;
|
|
173
|
+
}
|
|
174
|
+
>;
|
|
175
|
+
export type Event = Entity<
|
|
176
|
+
typeof eventDataView,
|
|
177
|
+
'Event',
|
|
178
|
+
{
|
|
179
|
+
attendees: Array<EventAttendee>;
|
|
180
|
+
host: User;
|
|
181
|
+
}
|
|
182
|
+
>;
|
|
183
|
+
|
|
184
|
+
export const Root = {
|
|
185
|
+
categories: list(categoryDataView, { orderBy: { createdAt: 'asc', id: 'asc' } }),
|
|
186
|
+
commentSearch: {
|
|
187
|
+
procedure: 'search',
|
|
188
|
+
view: list(commentDataView, { orderBy: { createdAt: 'desc', id: 'desc' } }),
|
|
189
|
+
},
|
|
190
|
+
events: list(eventDataView, { orderBy: [{ startAt: 'asc' }, { id: 'asc' }] }),
|
|
191
|
+
posts: list(postDataView, { orderBy: { createdAt: 'desc', id: 'desc' } }),
|
|
192
|
+
viewer: userDataView,
|
|
193
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env NODE_ENV=development node_modules/.bin/nodemon -q -I --exec node --no-warnings --experimental-specifier-resolution=node --loader ts-node/esm --env-file .env
|
|
2
|
+
import { parseArgs, styleText } from 'node:util';
|
|
3
|
+
import { serve } from '@hono/node-server';
|
|
4
|
+
import parseInteger from '@nkzw/core/parseInteger.js';
|
|
5
|
+
import app from './app.tsx';
|
|
6
|
+
|
|
7
|
+
const name = 'Pothos GraphQL Server';
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
values: { port: portArg },
|
|
11
|
+
} = parseArgs({
|
|
12
|
+
options: {
|
|
13
|
+
port: {
|
|
14
|
+
default: '9000',
|
|
15
|
+
short: 'p',
|
|
16
|
+
type: 'string',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const port = (portArg && parseInteger(portArg)) || 9000;
|
|
22
|
+
|
|
23
|
+
// See https://github.com/honojs/node-server/issues/233
|
|
24
|
+
process.on('uncaughtException', (error) => {
|
|
25
|
+
if (
|
|
26
|
+
!(error instanceof Error) ||
|
|
27
|
+
!error.message.includes('Invalid state: ReadableStream is already closed')
|
|
28
|
+
) {
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
serve({ fetch: app.fetch, port }, () =>
|
|
34
|
+
console.log(
|
|
35
|
+
`${styleText(['green', 'bold'], `${name}\n ➜`)} Server running on port ${styleText('bold', String(port))}.\n`,
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const setTitle = (title: string) => {
|
|
40
|
+
process.title = title;
|
|
41
|
+
if (process.stdout.isTTY) {
|
|
42
|
+
process.stdout.write(`${String.fromCharCode(27)}]0;🚀 ${title}${String.fromCharCode(7)}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
setTimeout(() => setTitle(name), 0);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { betterAuth } from 'better-auth';
|
|
2
|
+
import { prismaAdapter } from 'better-auth/adapters/prisma';
|
|
3
|
+
import { admin, username } from 'better-auth/plugins';
|
|
4
|
+
import prisma from '../prisma/prisma.tsx';
|
|
5
|
+
import { trustedOrigins } from './origins.ts';
|
|
6
|
+
|
|
7
|
+
export const auth = betterAuth({
|
|
8
|
+
advanced: {
|
|
9
|
+
database: {
|
|
10
|
+
generateId: false,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
database: prismaAdapter(prisma, {
|
|
14
|
+
provider: 'postgresql',
|
|
15
|
+
}),
|
|
16
|
+
emailAndPassword: {
|
|
17
|
+
autoSignIn: true,
|
|
18
|
+
enabled: true,
|
|
19
|
+
maxPasswordLength: 128,
|
|
20
|
+
minPasswordLength: 8,
|
|
21
|
+
},
|
|
22
|
+
plugins: [admin(), username()],
|
|
23
|
+
session: {
|
|
24
|
+
cookieCache: {
|
|
25
|
+
enabled: true,
|
|
26
|
+
maxAge: 15 * 24 * 60 * 60,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
telemetry: { enabled: false },
|
|
30
|
+
trustedOrigins,
|
|
31
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import env from './env.tsx';
|
|
2
|
+
|
|
3
|
+
const localDevOriginPattern = /^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?$/;
|
|
4
|
+
|
|
5
|
+
const isDevelopment = process.env.NODE_ENV === 'development' || process.env.DEV;
|
|
6
|
+
|
|
7
|
+
export const clientOrigin = env('CLIENT_DOMAIN');
|
|
8
|
+
|
|
9
|
+
export const trustedOrigins = [
|
|
10
|
+
clientOrigin,
|
|
11
|
+
...(isDevelopment ? ['http://localhost:*', 'http://127.0.0.1:*'] : []),
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
export const resolveCorsOrigin = (origin: string) => {
|
|
15
|
+
if (origin === clientOrigin || (isDevelopment && localDevOriginPattern.test(origin))) {
|
|
16
|
+
return origin;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PrismaPg } from '@prisma/adapter-pg';
|
|
2
|
+
import env from '../lib/env.tsx';
|
|
3
|
+
import { PrismaClient } from './prisma-client/client.ts';
|
|
4
|
+
|
|
5
|
+
const adapter = new PrismaPg({
|
|
6
|
+
connectionString: env('DATABASE_URL'),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export default new PrismaClient({ adapter });
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// This is your Prisma schema file,
|
|
2
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
3
|
+
|
|
4
|
+
generator client {
|
|
5
|
+
binaryTargets = ["native", "linux-musl"]
|
|
6
|
+
importFileExtension = "ts"
|
|
7
|
+
output = "./prisma-client"
|
|
8
|
+
provider = "prisma-client"
|
|
9
|
+
engineType = "client"
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
generator pothos {
|
|
13
|
+
provider = "prisma-pothos-types"
|
|
14
|
+
clientOutput = "./prisma-client"
|
|
15
|
+
output = "./pothos-types.ts"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
generator json {
|
|
19
|
+
provider = "prisma-json-types-generator"
|
|
20
|
+
clientOutput = "./prisma-client"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
datasource db {
|
|
24
|
+
provider = "postgresql"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
model User {
|
|
28
|
+
id String @id @unique @default(uuid(7))
|
|
29
|
+
accounts Account[]
|
|
30
|
+
banExpires DateTime?
|
|
31
|
+
banned Boolean?
|
|
32
|
+
banReason String?
|
|
33
|
+
createdAt DateTime @default(now())
|
|
34
|
+
displayUsername String?
|
|
35
|
+
email String @unique
|
|
36
|
+
emailVerified Boolean
|
|
37
|
+
image String?
|
|
38
|
+
name String
|
|
39
|
+
password String?
|
|
40
|
+
role String @default("user")
|
|
41
|
+
sessions Session[]
|
|
42
|
+
posts Post[]
|
|
43
|
+
comments Comment[]
|
|
44
|
+
hostedEvents Event[] @relation("EventHost")
|
|
45
|
+
eventAttendance EventAttendee[]
|
|
46
|
+
updatedAt DateTime
|
|
47
|
+
username String? @unique
|
|
48
|
+
|
|
49
|
+
@@index([id(sort: Asc)])
|
|
50
|
+
@@map("user")
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
model Session {
|
|
54
|
+
id String @unique @default(uuid(7))
|
|
55
|
+
expiresAt DateTime
|
|
56
|
+
token String
|
|
57
|
+
createdAt DateTime
|
|
58
|
+
updatedAt DateTime
|
|
59
|
+
ipAddress String?
|
|
60
|
+
userAgent String?
|
|
61
|
+
userId String
|
|
62
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
63
|
+
|
|
64
|
+
@@unique([token])
|
|
65
|
+
@@map("session")
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
model Account {
|
|
69
|
+
id String @unique @default(uuid(7))
|
|
70
|
+
accountId String
|
|
71
|
+
providerId String
|
|
72
|
+
userId String
|
|
73
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
74
|
+
accessToken String?
|
|
75
|
+
refreshToken String?
|
|
76
|
+
idToken String?
|
|
77
|
+
accessTokenExpiresAt DateTime?
|
|
78
|
+
refreshTokenExpiresAt DateTime?
|
|
79
|
+
scope String?
|
|
80
|
+
password String?
|
|
81
|
+
createdAt DateTime
|
|
82
|
+
updatedAt DateTime
|
|
83
|
+
|
|
84
|
+
@@unique([providerId, accountId])
|
|
85
|
+
@@map("account")
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
model Post {
|
|
89
|
+
id String @id @default(uuid(7))
|
|
90
|
+
title String
|
|
91
|
+
content String
|
|
92
|
+
likes Int @default(0)
|
|
93
|
+
createdAt DateTime @default(now())
|
|
94
|
+
updatedAt DateTime @updatedAt
|
|
95
|
+
authorId String
|
|
96
|
+
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
|
|
97
|
+
comments Comment[]
|
|
98
|
+
categoryId String?
|
|
99
|
+
category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull)
|
|
100
|
+
tags Tag[] @relation("PostTags")
|
|
101
|
+
|
|
102
|
+
@@index([authorId(sort: Asc)])
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
model Comment {
|
|
106
|
+
id String @id @default(uuid(7))
|
|
107
|
+
content String
|
|
108
|
+
createdAt DateTime @default(now())
|
|
109
|
+
postId String
|
|
110
|
+
authorId String?
|
|
111
|
+
post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
|
|
112
|
+
author User? @relation(fields: [authorId], references: [id], onDelete: SetNull)
|
|
113
|
+
|
|
114
|
+
@@index([authorId(sort: Asc)])
|
|
115
|
+
@@index([postId(sort: Asc)])
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
model Category {
|
|
119
|
+
id String @id @default(uuid(7))
|
|
120
|
+
name String @unique
|
|
121
|
+
description String?
|
|
122
|
+
createdAt DateTime @default(now())
|
|
123
|
+
posts Post[]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
model Tag {
|
|
127
|
+
id String @id @default(uuid(7))
|
|
128
|
+
name String @unique
|
|
129
|
+
description String?
|
|
130
|
+
createdAt DateTime @default(now())
|
|
131
|
+
posts Post[] @relation("PostTags")
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
enum RSVPStatus {
|
|
135
|
+
INVITED
|
|
136
|
+
GOING
|
|
137
|
+
INTERESTED
|
|
138
|
+
DECLINED
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
enum EventType {
|
|
142
|
+
WORKSHOP
|
|
143
|
+
MEETUP
|
|
144
|
+
AMA
|
|
145
|
+
LAUNCH
|
|
146
|
+
COMMUNITY_CALL
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
model Event {
|
|
150
|
+
id String @id @default(uuid(7))
|
|
151
|
+
name String
|
|
152
|
+
description String
|
|
153
|
+
type EventType @default(MEETUP)
|
|
154
|
+
hostId String
|
|
155
|
+
host User @relation("EventHost", fields: [hostId], references: [id], onDelete: Cascade)
|
|
156
|
+
location String
|
|
157
|
+
startAt DateTime
|
|
158
|
+
endAt DateTime
|
|
159
|
+
capacity Int
|
|
160
|
+
topics String[] @default([])
|
|
161
|
+
livestreamUrl String?
|
|
162
|
+
attendees EventAttendee[]
|
|
163
|
+
createdAt DateTime @default(now())
|
|
164
|
+
updatedAt DateTime @updatedAt
|
|
165
|
+
|
|
166
|
+
@@index([hostId(sort: Asc)])
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
model EventAttendee {
|
|
170
|
+
id String @id @default(uuid(7))
|
|
171
|
+
eventId String
|
|
172
|
+
userId String
|
|
173
|
+
status RSVPStatus @default(INVITED)
|
|
174
|
+
notes String?
|
|
175
|
+
event Event @relation(fields: [eventId], references: [id], onDelete: Cascade)
|
|
176
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
177
|
+
createdAt DateTime @default(now())
|
|
178
|
+
|
|
179
|
+
@@unique([eventId, userId])
|
|
180
|
+
@@index([userId(sort: Asc)])
|
|
181
|
+
}
|