@goscribe/server 1.0.2 → 1.0.4
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/dist/context.d.ts +2 -2
- package/dist/context.js +11 -7
- package/dist/index.d.ts +3 -4
- package/dist/index.js +1 -2
- package/dist/lib/auth.d.ts +3 -2
- package/dist/lib/auth.js +39 -36
- package/dist/lib/file.d.ts +0 -0
- package/dist/lib/file.js +1 -0
- package/dist/lib/prisma.d.ts +2 -3
- package/dist/lib/prisma.js +4 -7
- package/dist/lib/storage.d.ts +3 -0
- package/dist/lib/storage.js +10 -0
- package/dist/routers/_app.d.ts +90 -16
- package/dist/routers/_app.js +6 -9
- package/dist/routers/auth.d.ts +28 -2
- package/dist/routers/auth.js +48 -16
- package/dist/routers/workspace.d.ts +60 -14
- package/dist/routers/workspace.js +129 -30
- package/dist/server.d.ts +0 -1
- package/dist/server.js +19 -56
- package/dist/trpc.d.ts +10 -9
- package/dist/trpc.js +13 -26
- package/package.json +11 -4
- package/prisma/schema.prisma +7 -21
- package/src/context.ts +12 -4
- package/src/index.ts +3 -3
- package/src/lib/auth.ts +37 -32
- package/src/lib/prisma.ts +1 -1
- package/src/routers/_app.ts +3 -3
- package/src/routers/auth.ts +47 -2
- package/src/routers/workspace.ts +16 -15
- package/src/server.ts +2 -5
- package/src/trpc.ts +5 -11
- package/tsconfig.json +17 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/lib/auth.d.ts.map +0 -1
- package/dist/lib/prisma.d.ts.map +0 -1
- package/dist/routers/_app.d.ts.map +0 -1
- package/dist/routers/auth.d.ts.map +0 -1
- package/dist/routers/sample.js +0 -21
- package/dist/routers/workspace.d.ts.map +0 -1
- package/dist/server.d.ts.map +0 -1
- package/dist/trpc.d.ts.map +0 -1
|
@@ -1,45 +1,144 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
const trpc_1 = require("../trpc");
|
|
6
|
-
exports.workspace = (0, trpc_1.router)({
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { router, authedProcedure } from '../trpc.js';
|
|
3
|
+
import { bucket } from '../lib/storage.js';
|
|
4
|
+
export const workspace = router({
|
|
7
5
|
// Mutation with Zod input
|
|
8
|
-
list:
|
|
6
|
+
list: authedProcedure
|
|
9
7
|
.query(async ({ ctx, input }) => {
|
|
8
|
+
const workspaces = await ctx.db.workspace.findMany({
|
|
9
|
+
where: {
|
|
10
|
+
ownerId: ctx.session?.user.id,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
return workspaces;
|
|
10
14
|
}),
|
|
11
|
-
create:
|
|
12
|
-
.input(
|
|
13
|
-
.
|
|
15
|
+
create: authedProcedure
|
|
16
|
+
.input(z.object({
|
|
17
|
+
name: z.string().min(1).max(100),
|
|
18
|
+
description: z.string().max(500).optional(),
|
|
19
|
+
}))
|
|
20
|
+
.mutation(({ ctx, input }) => {
|
|
21
|
+
return ctx.db.workspace.create({
|
|
22
|
+
data: {
|
|
23
|
+
title: input.name,
|
|
24
|
+
description: input.description,
|
|
25
|
+
ownerId: ctx.session?.user.id,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
14
28
|
}),
|
|
15
|
-
get:
|
|
16
|
-
.input(
|
|
17
|
-
id:
|
|
29
|
+
get: authedProcedure
|
|
30
|
+
.input(z.object({
|
|
31
|
+
id: z.string().uuid(),
|
|
18
32
|
}))
|
|
19
|
-
.query(({ input }) => {
|
|
33
|
+
.query(({ ctx, input }) => {
|
|
34
|
+
return ctx.db.workspace.findUnique({
|
|
35
|
+
where: {
|
|
36
|
+
id: input.id,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
20
39
|
}),
|
|
21
|
-
update:
|
|
22
|
-
.input(
|
|
23
|
-
id:
|
|
40
|
+
update: authedProcedure
|
|
41
|
+
.input(z.object({
|
|
42
|
+
id: z.string().uuid(),
|
|
43
|
+
name: z.string().min(1).max(100).optional(),
|
|
44
|
+
description: z.string().max(500).optional(),
|
|
24
45
|
}))
|
|
25
|
-
.mutation(({ input }) => {
|
|
46
|
+
.mutation(({ ctx, input }) => {
|
|
47
|
+
return ctx.db.workspace.update({
|
|
48
|
+
where: {
|
|
49
|
+
id: input.id,
|
|
50
|
+
},
|
|
51
|
+
data: {
|
|
52
|
+
title: input.name,
|
|
53
|
+
description: input.description,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
26
56
|
}),
|
|
27
|
-
delete:
|
|
28
|
-
.input(
|
|
29
|
-
id:
|
|
57
|
+
delete: authedProcedure
|
|
58
|
+
.input(z.object({
|
|
59
|
+
id: z.string().uuid(),
|
|
30
60
|
}))
|
|
31
|
-
.mutation(({ input }) => {
|
|
61
|
+
.mutation(({ ctx, input }) => {
|
|
62
|
+
ctx.db.workspace.delete({
|
|
63
|
+
where: {
|
|
64
|
+
id: input.id,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return true;
|
|
32
68
|
}),
|
|
33
|
-
|
|
34
|
-
.input(
|
|
35
|
-
|
|
69
|
+
uploadFiles: authedProcedure
|
|
70
|
+
.input(z.object({
|
|
71
|
+
id: z.string().uuid(),
|
|
72
|
+
files: z.array(z.object({
|
|
73
|
+
filename: z.string().min(1).max(255),
|
|
74
|
+
contentType: z.string().min(1).max(100),
|
|
75
|
+
size: z.number().min(1), // size in bytes
|
|
76
|
+
})),
|
|
36
77
|
}))
|
|
37
|
-
.mutation(({ input }) => {
|
|
78
|
+
.mutation(async ({ ctx, input }) => {
|
|
79
|
+
const results = [];
|
|
80
|
+
for (const file of input.files) {
|
|
81
|
+
// 1. Insert into DB
|
|
82
|
+
const record = await ctx.db.fileAsset.create({
|
|
83
|
+
data: {
|
|
84
|
+
userId: ctx.session.user.id,
|
|
85
|
+
name: file.filename,
|
|
86
|
+
mimeType: file.contentType,
|
|
87
|
+
size: file.size,
|
|
88
|
+
workspaceId: input.id,
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
// 2. Generate signed URL for direct upload
|
|
92
|
+
const [url] = await bucket
|
|
93
|
+
.file(`${ctx.session.user.id}/${record.id}-${file.filename}`)
|
|
94
|
+
.getSignedUrl({
|
|
95
|
+
action: "write",
|
|
96
|
+
expires: Date.now() + 5 * 60 * 1000, // 5 min
|
|
97
|
+
contentType: file.contentType,
|
|
98
|
+
});
|
|
99
|
+
// 3. Update record with bucket info
|
|
100
|
+
await ctx.db.fileAsset.update({
|
|
101
|
+
where: { id: record.id },
|
|
102
|
+
data: {
|
|
103
|
+
bucket: bucket.name,
|
|
104
|
+
objectKey: `${ctx.session.user.id}/${record.id}-${file.filename}`,
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
results.push({
|
|
108
|
+
fileId: record.id,
|
|
109
|
+
uploadUrl: url,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return results;
|
|
38
113
|
}),
|
|
39
|
-
|
|
40
|
-
.input(
|
|
41
|
-
fileId:
|
|
114
|
+
deleteFiles: authedProcedure
|
|
115
|
+
.input(z.object({
|
|
116
|
+
fileId: z.array(z.string().uuid()),
|
|
117
|
+
id: z.string().uuid(),
|
|
42
118
|
}))
|
|
43
|
-
.mutation(({ input }) => {
|
|
119
|
+
.mutation(({ ctx, input }) => {
|
|
120
|
+
const files = ctx.db.fileAsset.findMany({
|
|
121
|
+
where: {
|
|
122
|
+
id: { in: input.fileId },
|
|
123
|
+
workspaceId: input.id,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
// Delete from GCS
|
|
127
|
+
files.then((fileRecords) => {
|
|
128
|
+
fileRecords.forEach((file) => {
|
|
129
|
+
if (file.bucket && file.objectKey) {
|
|
130
|
+
const gcsFile = bucket.file(file.objectKey);
|
|
131
|
+
gcsFile.delete({ ignoreNotFound: true }).catch((err) => {
|
|
132
|
+
console.error(`Error deleting file ${file.objectKey} from bucket ${file.bucket}:`, err);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
return ctx.db.fileAsset.deleteMany({
|
|
138
|
+
where: {
|
|
139
|
+
id: { in: input.fileId },
|
|
140
|
+
workspaceId: input.id,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
44
143
|
}),
|
|
45
144
|
});
|
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -1,68 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const express_1 = __importDefault(require("express"));
|
|
40
|
-
const cors_1 = __importDefault(require("cors"));
|
|
41
|
-
const helmet_1 = __importDefault(require("helmet"));
|
|
42
|
-
const morgan_1 = __importDefault(require("morgan"));
|
|
43
|
-
const compression_1 = __importDefault(require("compression"));
|
|
44
|
-
const trpcExpress = __importStar(require("@trpc/server/adapters/express"));
|
|
45
|
-
const auth_1 = require("./lib/auth");
|
|
46
|
-
const _app_1 = require("./routers/_app");
|
|
47
|
-
const context_1 = require("./context");
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import helmet from 'helmet';
|
|
4
|
+
import morgan from 'morgan';
|
|
5
|
+
import compression from 'compression';
|
|
6
|
+
import * as trpcExpress from '@trpc/server/adapters/express';
|
|
7
|
+
import { appRouter } from './routers/_app.js';
|
|
8
|
+
import { createContext } from './context.js';
|
|
48
9
|
const PORT = process.env.PORT ? Number(process.env.PORT) : 3001;
|
|
49
10
|
async function main() {
|
|
50
|
-
const app = (
|
|
11
|
+
const app = express();
|
|
51
12
|
// Middlewares
|
|
52
|
-
app.use((
|
|
53
|
-
app.use((
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
app.use(
|
|
13
|
+
app.use(helmet());
|
|
14
|
+
app.use(cors({
|
|
15
|
+
origin: "http://localhost:3000", // your Next.js dev URL
|
|
16
|
+
credentials: true, // allow cookies
|
|
17
|
+
}));
|
|
18
|
+
app.use(morgan('dev'));
|
|
19
|
+
app.use(compression());
|
|
20
|
+
app.use(express.json());
|
|
58
21
|
// Health (plain Express)
|
|
59
22
|
app.get('/', (_req, res) => {
|
|
60
23
|
res.json({ ok: true, service: 'trpc-express', ts: Date.now() });
|
|
61
24
|
});
|
|
62
25
|
// tRPC mounted under /trpc
|
|
63
26
|
app.use('/trpc', trpcExpress.createExpressMiddleware({
|
|
64
|
-
router:
|
|
65
|
-
createContext
|
|
27
|
+
router: appRouter,
|
|
28
|
+
createContext,
|
|
66
29
|
}));
|
|
67
30
|
app.listen(PORT, () => {
|
|
68
31
|
console.log(`✅ Server ready on http://localhost:${PORT}`);
|
package/dist/trpc.d.ts
CHANGED
|
@@ -1,41 +1,42 @@
|
|
|
1
1
|
export declare const router: import("@trpc/server").TRPCRouterBuilder<{
|
|
2
2
|
ctx: {
|
|
3
|
-
db: import("
|
|
3
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
4
4
|
session: any;
|
|
5
5
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
6
6
|
res: import("express").Response<any, Record<string, any>>;
|
|
7
|
+
cookies: Record<string, string | undefined>;
|
|
7
8
|
};
|
|
8
9
|
meta: object;
|
|
9
10
|
errorShape: import("@trpc/server").TRPCDefaultErrorShape;
|
|
10
11
|
transformer: true;
|
|
11
12
|
}>;
|
|
12
13
|
export declare const middleware: <$ContextOverrides>(fn: import("@trpc/server").TRPCMiddlewareFunction<{
|
|
13
|
-
db: import("
|
|
14
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
14
15
|
session: any;
|
|
15
16
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
16
17
|
res: import("express").Response<any, Record<string, any>>;
|
|
18
|
+
cookies: Record<string, string | undefined>;
|
|
17
19
|
}, object, object, $ContextOverrides, unknown>) => import("@trpc/server").TRPCMiddlewareBuilder<{
|
|
18
|
-
db: import("
|
|
20
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
19
21
|
session: any;
|
|
20
22
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
21
23
|
res: import("express").Response<any, Record<string, any>>;
|
|
24
|
+
cookies: Record<string, string | undefined>;
|
|
22
25
|
}, object, $ContextOverrides, unknown>;
|
|
23
26
|
export declare const publicProcedure: import("@trpc/server").TRPCProcedureBuilder<{
|
|
24
|
-
db: import("
|
|
27
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
25
28
|
session: any;
|
|
26
29
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
27
30
|
res: import("express").Response<any, Record<string, any>>;
|
|
31
|
+
cookies: Record<string, string | undefined>;
|
|
28
32
|
}, object, object, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
|
|
29
33
|
/** Exported authed procedure */
|
|
30
34
|
export declare const authedProcedure: import("@trpc/server").TRPCProcedureBuilder<{
|
|
31
|
-
db: import("
|
|
35
|
+
db: import("@prisma/client").PrismaClient<import("@prisma/client").Prisma.PrismaClientOptions, never, import("@prisma/client/runtime/library").DefaultArgs>;
|
|
32
36
|
session: any;
|
|
33
37
|
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
34
38
|
res: import("express").Response<any, Record<string, any>>;
|
|
39
|
+
cookies: Record<string, string | undefined>;
|
|
35
40
|
}, object, {
|
|
36
|
-
req: import("express").Request<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
37
|
-
res: import("express").Response<any, Record<string, any>>;
|
|
38
|
-
db: import("src/generated/prisma").PrismaClient<import("src/generated/prisma").Prisma.PrismaClientOptions, never, import("src/generated/prisma/runtime/library").DefaultArgs>;
|
|
39
41
|
session: any;
|
|
40
42
|
}, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, import("@trpc/server").TRPCUnsetMarker, false>;
|
|
41
|
-
//# sourceMappingURL=trpc.d.ts.map
|
package/dist/trpc.js
CHANGED
|
@@ -1,38 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.authedProcedure = exports.publicProcedure = exports.middleware = exports.router = void 0;
|
|
7
|
-
const server_1 = require("@trpc/server");
|
|
8
|
-
const superjson_1 = __importDefault(require("superjson"));
|
|
9
|
-
const t = server_1.initTRPC.context().create({
|
|
10
|
-
transformer: superjson_1.default,
|
|
1
|
+
import { initTRPC, TRPCError } from "@trpc/server";
|
|
2
|
+
import superjson from "superjson";
|
|
3
|
+
const t = initTRPC.context().create({
|
|
4
|
+
transformer: superjson,
|
|
11
5
|
errorFormatter({ shape }) {
|
|
12
6
|
return shape;
|
|
13
7
|
},
|
|
14
8
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
9
|
+
export const router = t.router;
|
|
10
|
+
export const middleware = t.middleware;
|
|
11
|
+
export const publicProcedure = t.procedure;
|
|
18
12
|
/** Middleware that enforces authentication */
|
|
19
|
-
const isAuthed =
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
const isAuthed = middleware(({ ctx, next }) => {
|
|
14
|
+
const hasUser = Boolean(ctx.session?.user?.id);
|
|
15
|
+
if (!ctx.session || !hasUser) {
|
|
16
|
+
throw new TRPCError({ code: "UNAUTHORIZED" });
|
|
22
17
|
}
|
|
23
18
|
return next({
|
|
24
19
|
ctx: {
|
|
25
|
-
|
|
26
|
-
// refine ctx: session is guaranteed, user.id is string
|
|
27
|
-
session: {
|
|
28
|
-
...ctx.session,
|
|
29
|
-
user: {
|
|
30
|
-
...ctx.session.user,
|
|
31
|
-
id: ctx.session.user.id, // typed non-null
|
|
32
|
-
},
|
|
33
|
-
},
|
|
20
|
+
session: ctx.session,
|
|
34
21
|
},
|
|
35
22
|
});
|
|
36
23
|
});
|
|
37
24
|
/** Exported authed procedure */
|
|
38
|
-
|
|
25
|
+
export const authedProcedure = publicProcedure.use(isAuthed);
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goscribe/server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": {
|
|
9
10
|
"types": "./dist/index.d.ts",
|
|
@@ -12,18 +13,21 @@
|
|
|
12
13
|
},
|
|
13
14
|
"scripts": {
|
|
14
15
|
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
|
|
15
|
-
"build": "tsc -p .",
|
|
16
|
-
"start": "node dist/server.js"
|
|
16
|
+
"build": "npx prisma generate && tsc -p .",
|
|
17
|
+
"start": "node --experimental-specifier-resolution=node dist/server.js"
|
|
17
18
|
},
|
|
18
19
|
"author": "",
|
|
19
20
|
"license": "MIT",
|
|
20
21
|
"dependencies": {
|
|
21
22
|
"@auth/express": "^0.11.0",
|
|
22
23
|
"@auth/prisma-adapter": "^2.10.0",
|
|
24
|
+
"@google-cloud/storage": "^7.17.0",
|
|
23
25
|
"@prisma/client": "^6.14.0",
|
|
24
26
|
"@trpc/server": "^11.5.0",
|
|
27
|
+
"@types/cookie": "^1.0.0",
|
|
25
28
|
"bcryptjs": "^3.0.2",
|
|
26
29
|
"compression": "^1.8.1",
|
|
30
|
+
"cookie": "^1.0.2",
|
|
27
31
|
"cors": "^2.8.5",
|
|
28
32
|
"express": "^5.1.0",
|
|
29
33
|
"helmet": "^8.1.0",
|
|
@@ -40,6 +44,9 @@
|
|
|
40
44
|
"@types/node": "^24.3.0",
|
|
41
45
|
"ts-node": "^10.9.2",
|
|
42
46
|
"ts-node-dev": "^2.0.0",
|
|
43
|
-
"
|
|
47
|
+
"tsc-alias": "^1.8.16",
|
|
48
|
+
"tsc-esm-fix": "^3.1.2",
|
|
49
|
+
"typescript": "^5.9.2",
|
|
50
|
+
"typescript-transform-paths": "^3.5.5"
|
|
44
51
|
}
|
|
45
52
|
}
|
package/prisma/schema.prisma
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
generator client {
|
|
3
3
|
provider = "prisma-client-js"
|
|
4
|
-
output = "../src/generated/prisma"
|
|
5
4
|
}
|
|
6
5
|
|
|
7
6
|
datasource db {
|
|
8
7
|
provider = "postgresql"
|
|
9
8
|
url = env("DATABASE_URL")
|
|
9
|
+
directUrl = env("DIRECT_URL") // for shadow db in migrations
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
//
|
|
@@ -36,6 +36,7 @@ model User {
|
|
|
36
36
|
emailVerified DateTime?
|
|
37
37
|
passwordHash String? // for credentials login
|
|
38
38
|
image String?
|
|
39
|
+
session Session[]
|
|
39
40
|
|
|
40
41
|
// Ownership
|
|
41
42
|
folders Folder[] @relation("UserFolders")
|
|
@@ -44,30 +45,15 @@ model User {
|
|
|
44
45
|
artifacts Artifact[] @relation("UserArtifacts")
|
|
45
46
|
versions ArtifactVersion[] @relation("UserArtifactVersions")
|
|
46
47
|
|
|
47
|
-
accounts Account[]
|
|
48
|
-
|
|
49
48
|
createdAt DateTime @default(now())
|
|
50
49
|
updatedAt DateTime @updatedAt
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
model
|
|
54
|
-
id
|
|
55
|
-
userId
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
providerAccountId String
|
|
59
|
-
|
|
60
|
-
refresh_token String?
|
|
61
|
-
access_token String?
|
|
62
|
-
expires_at Int?
|
|
63
|
-
token_type String?
|
|
64
|
-
scope String?
|
|
65
|
-
id_token String?
|
|
66
|
-
session_state String?
|
|
67
|
-
|
|
68
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
69
|
-
|
|
70
|
-
@@unique([provider, providerAccountId])
|
|
52
|
+
model Session {
|
|
53
|
+
id String @id @default(cuid())
|
|
54
|
+
userId String
|
|
55
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
56
|
+
expires DateTime
|
|
71
57
|
}
|
|
72
58
|
|
|
73
59
|
model VerificationToken {
|
package/src/context.ts
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
// src/server/trpc/context.ts
|
|
2
2
|
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
|
3
|
-
import { prisma } from "./lib/prisma";
|
|
3
|
+
import { prisma } from "./lib/prisma.js";
|
|
4
|
+
import { verifyCustomAuthCookie } from "./lib/auth.js";
|
|
5
|
+
import cookie from "cookie";
|
|
4
6
|
|
|
5
7
|
export async function createContext({ req, res }: CreateExpressContextOptions) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
const cookies = cookie.parse(req.headers.cookie ?? "");
|
|
9
|
+
|
|
10
|
+
// Only use custom auth cookie
|
|
11
|
+
const custom = verifyCustomAuthCookie(cookies["auth_token"]);
|
|
12
|
+
if (custom) {
|
|
13
|
+
return { db: prisma, session: { user: { id: custom.userId } } as any, req, res, cookies };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return { db: prisma, session: null, req, res, cookies };
|
|
9
17
|
}
|
|
10
18
|
|
|
11
19
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export type { AppRouter } from "./routers/_app";
|
|
2
|
-
export type { RouterInputs } from "./routers/_app";
|
|
3
|
-
export type { RouterOutputs } from "./routers/_app";
|
|
1
|
+
export type { AppRouter } from "./routers/_app.js";
|
|
2
|
+
export type { RouterInputs } from "./routers/_app.js";
|
|
3
|
+
export type { RouterOutputs } from "./routers/_app.js";
|
package/src/lib/auth.ts
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
1
|
// src/server/auth.ts
|
|
2
|
-
import
|
|
3
|
-
import { PrismaAdapter } from "@auth/prisma-adapter";
|
|
4
|
-
import { prisma } from "../lib/prisma";
|
|
5
|
-
import Google from "@auth/core/providers/google";
|
|
6
|
-
import Credentials from "@auth/core/providers/credentials";
|
|
2
|
+
import crypto from "node:crypto";
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
4
|
+
// Custom HMAC cookie: auth_token = base64(userId).hex(hmacSHA256(base64(userId), secret))
|
|
5
|
+
export function verifyCustomAuthCookie(cookieValue: string | undefined): { userId: string } | null {
|
|
6
|
+
if (!cookieValue) return null;
|
|
7
|
+
const secret = process.env.CUSTOM_AUTH_SECRET;
|
|
8
|
+
if (!secret) return null;
|
|
9
|
+
|
|
10
|
+
const parts = cookieValue.split(".");
|
|
11
|
+
if (parts.length !== 2) return null;
|
|
12
|
+
const [base64UserId, signatureHex] = parts;
|
|
13
|
+
|
|
14
|
+
let userId: string;
|
|
15
|
+
try {
|
|
16
|
+
const buf = Buffer.from(base64UserId, "base64url");
|
|
17
|
+
userId = buf.toString("utf8");
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const hmac = crypto.createHmac("sha256", secret);
|
|
23
|
+
hmac.update(base64UserId);
|
|
24
|
+
const expected = hmac.digest("hex");
|
|
25
|
+
if (!timingSafeEqualHex(signatureHex, expected)) return null;
|
|
26
|
+
|
|
27
|
+
return { userId };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function timingSafeEqualHex(a: string, b: string): boolean {
|
|
31
|
+
try {
|
|
32
|
+
const ab = Buffer.from(a, "hex");
|
|
33
|
+
const bb = Buffer.from(b, "hex");
|
|
34
|
+
if (ab.length !== bb.length) return false;
|
|
35
|
+
return crypto.timingSafeEqual(ab, bb);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/lib/prisma.ts
CHANGED
package/src/routers/_app.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
|
|
2
|
-
import { router } from '../trpc';
|
|
3
|
-
import { auth } from './auth';
|
|
4
|
-
import { workspace } from './workspace';
|
|
2
|
+
import { router } from '../trpc.js';
|
|
3
|
+
import { auth } from './auth.js';
|
|
4
|
+
import { workspace } from './workspace.js';
|
|
5
5
|
|
|
6
6
|
export const appRouter = router({
|
|
7
7
|
auth,
|