@goscribe/server 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goscribe/server",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -140,8 +140,8 @@ model FileAsset {
140
140
  name String
141
141
  mimeType String
142
142
  size Int
143
- bucket String
144
- objectKey String
143
+ bucket String?
144
+ objectKey String?
145
145
  url String? // optional if serving via signed GET per-view
146
146
  checksum String? // optional server-side integrity
147
147
 
File without changes
@@ -0,0 +1,13 @@
1
+ // src/server/lib/gcs.ts
2
+ import { Storage } from "@google-cloud/storage";
3
+
4
+ export const storage = new Storage({
5
+ projectId: process.env.GCP_PROJECT_ID,
6
+ credentials: {
7
+ client_email: process.env.GCP_CLIENT_EMAIL,
8
+ private_key: process.env.GCP_PRIVATE_KEY?.replace(/\\n/g, "\n"),
9
+ },
10
+ });
11
+
12
+ export const bucket = storage.bucket(process.env.GCP_BUCKET!);
13
+
@@ -1,51 +1,156 @@
1
1
  import { z } from 'zod';
2
2
  import { router, publicProcedure, authedProcedure } from '../trpc';
3
+ import { bucket } from 'src/lib/storage';
3
4
 
4
5
  export const workspace = router({
5
6
  // Mutation with Zod input
6
7
  list: publicProcedure
7
8
  .query(async ({ ctx, input }) => {
9
+ const workspaces = await ctx.db.workspace.findMany({
10
+ where: {
11
+ ownerId: ctx.session?.user.id,
12
+ },
13
+ });
14
+ return workspaces;
8
15
  }),
9
16
 
10
17
  create: publicProcedure
11
18
  .input(z.object({
12
-
19
+ name: z.string().min(1).max(100),
20
+ description: z.string().max(500).optional(),
13
21
  }))
14
- .mutation(({ input }) => {
15
-
22
+ .mutation(({ ctx, input}) => {
23
+ return ctx.db.workspace.create({
24
+ data: {
25
+ title: input.name,
26
+ description: input.description,
27
+ ownerId: ctx.session?.user.id,
28
+ },
29
+ });
16
30
  }),
17
31
  get: publicProcedure
18
32
  .input(z.object({
19
33
  id: z.string().uuid(),
20
34
  }))
21
- .query(({ input }) => {
35
+ .query(({ ctx, input }) => {
36
+ return ctx.db.workspace.findUnique({
37
+ where: {
38
+ id: input.id,
39
+ },
40
+ });
22
41
  }),
23
42
  update: publicProcedure
24
43
  .input(z.object({
25
44
  id: z.string().uuid(),
45
+ name: z.string().min(1).max(100).optional(),
46
+ description: z.string().max(500).optional(),
26
47
  }))
27
- .mutation(({ input }) => {
28
-
48
+ .mutation(({ ctx, input }) => {
49
+ return ctx.db.workspace.update({
50
+ where: {
51
+ id: input.id,
52
+ },
53
+ data: {
54
+ title: input.name,
55
+ description: input.description,
56
+ },
57
+ });
29
58
  }),
30
59
  delete: publicProcedure
31
60
  .input(z.object({
32
61
  id: z.string().uuid(),
33
62
  }))
34
- .mutation(({ input }) => {
35
-
63
+ .mutation(({ ctx, input }) => {
64
+ ctx.db.workspace.delete({
65
+ where: {
66
+ id: input.id,
67
+ },
68
+ });
69
+ return true;
36
70
  }),
37
- upload: publicProcedure
71
+ uploadFiles: publicProcedure
38
72
  .input(z.object({
39
- file: z.string(),
73
+ id: z.string().uuid(),
74
+ files: z.array(
75
+ z.object({
76
+ filename: z.string().min(1).max(255),
77
+ contentType: z.string().min(1).max(100),
78
+ size: z.number().min(1), // size in bytes
79
+ })
80
+ ),
40
81
  }))
41
- .mutation(({ input }) => {
42
-
82
+ .mutation(async ({ ctx, input }) => {
83
+ const results = [];
84
+
85
+ for (const file of input.files) {
86
+ // 1. Insert into DB
87
+ const record = await ctx.db.fileAsset.create({
88
+ data: {
89
+ userId: ctx.session.user.id,
90
+ name: file.filename,
91
+ mimeType: file.contentType,
92
+ size: file.size,
93
+ workspaceId: input.id,
94
+ },
95
+ });
96
+
97
+ // 2. Generate signed URL for direct upload
98
+ const [url] = await bucket
99
+ .file(`${ctx.session.user.id}/${record.id}-${file.filename}`)
100
+ .getSignedUrl({
101
+ action: "write",
102
+ expires: Date.now() + 5 * 60 * 1000, // 5 min
103
+ contentType: file.contentType,
104
+ });
105
+
106
+ // 3. Update record with bucket info
107
+ await ctx.db.fileAsset.update({
108
+ where: { id: record.id },
109
+ data: {
110
+ bucket: bucket.name,
111
+ objectKey: `${ctx.session.user.id}/${record.id}-${file.filename}`,
112
+ },
113
+ });
114
+
115
+ results.push({
116
+ fileId: record.id,
117
+ uploadUrl: url,
118
+ });
119
+ }
120
+
121
+ return results;
122
+
43
123
  }),
44
- deleteFile: publicProcedure
124
+ deleteFiles: publicProcedure
45
125
  .input(z.object({
46
- fileId: z.string().uuid(),
126
+ fileId: z.array(z.string().uuid()),
127
+ id: z.string().uuid(),
47
128
  }))
48
- .mutation(({ input }) => {
49
-
129
+ .mutation(({ ctx, input }) => {
130
+ const files = ctx.db.fileAsset.findMany({
131
+ where: {
132
+ id: { in: input.fileId },
133
+ workspaceId: input.id,
134
+ },
135
+ });
136
+
137
+ // Delete from GCS
138
+ files.then((fileRecords) => {
139
+ fileRecords.forEach((file) => {
140
+ if (file.bucket && file.objectKey) {
141
+ const gcsFile = bucket.file(file.objectKey);
142
+ gcsFile.delete({ ignoreNotFound: true }).catch((err: unknown) => {
143
+ console.error(`Error deleting file ${file.objectKey} from bucket ${file.bucket}:`, err);
144
+ });
145
+ }
146
+ });
147
+ });
148
+
149
+ return ctx.db.fileAsset.deleteMany({
150
+ where: {
151
+ id: { in: input.fileId },
152
+ workspaceId: input.id,
153
+ },
154
+ });
50
155
  }),
51
156
  });
package/src/server.ts CHANGED
@@ -16,7 +16,11 @@ async function main() {
16
16
 
17
17
  // Middlewares
18
18
  app.use(helmet());
19
- app.use(cors({ origin: true, credentials: true }));
19
+ app.use(cors({
20
+ origin: "http://localhost:3000", // your Next.js dev URL
21
+ credentials: true, // allow cookies
22
+ }));
23
+
20
24
  app.use(morgan('dev'));
21
25
  app.use(compression());
22
26
  app.use(express.json());