@codaijs/keel 0.2.3 → 0.2.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/__tests__/sail-installer.test.js +25 -25
- package/dist/sail-installer.js +174 -174
- package/dist/scaffold.js +68 -68
- package/package.json +58 -58
- package/sails/_template/addon.json +20 -20
- package/sails/_template/install.ts +402 -402
- package/sails/admin-dashboard/README.md +117 -117
- package/sails/admin-dashboard/addon.json +28 -28
- package/sails/admin-dashboard/files/backend/middleware/admin.ts +34 -34
- package/sails/admin-dashboard/files/backend/routes/admin.ts +243 -243
- package/sails/admin-dashboard/files/frontend/components/admin/StatsCard.tsx +40 -40
- package/sails/admin-dashboard/files/frontend/components/admin/UsersTable.tsx +240 -240
- package/sails/admin-dashboard/files/frontend/hooks/useAdmin.ts +149 -149
- package/sails/admin-dashboard/files/frontend/pages/admin/Dashboard.tsx +173 -173
- package/sails/admin-dashboard/files/frontend/pages/admin/UserDetail.tsx +203 -203
- package/sails/admin-dashboard/install.ts +305 -305
- package/sails/analytics/README.md +178 -178
- package/sails/analytics/addon.json +27 -27
- package/sails/analytics/files/frontend/components/AnalyticsProvider.tsx +58 -58
- package/sails/analytics/files/frontend/hooks/useAnalytics.ts +64 -64
- package/sails/analytics/files/frontend/lib/analytics.ts +103 -103
- package/sails/analytics/install.ts +297 -297
- package/sails/file-uploads/addon.json +30 -30
- package/sails/file-uploads/files/backend/routes/files.ts +198 -198
- package/sails/file-uploads/files/backend/schema/files.ts +36 -36
- package/sails/file-uploads/files/backend/services/file-storage.ts +128 -128
- package/sails/file-uploads/files/frontend/components/FileList.tsx +248 -248
- package/sails/file-uploads/files/frontend/components/FileUploadButton.tsx +147 -147
- package/sails/file-uploads/files/frontend/hooks/useFileUpload.ts +106 -106
- package/sails/file-uploads/files/frontend/hooks/useFiles.ts +118 -118
- package/sails/file-uploads/files/frontend/pages/Files.tsx +37 -37
- package/sails/file-uploads/install.ts +466 -466
- package/sails/gdpr/README.md +174 -174
- package/sails/gdpr/addon.json +27 -27
- package/sails/gdpr/files/backend/routes/gdpr.ts +140 -140
- package/sails/gdpr/files/backend/services/gdpr.ts +293 -293
- package/sails/gdpr/files/frontend/components/auth/ConsentCheckboxes.tsx +97 -97
- package/sails/gdpr/files/frontend/components/gdpr/AccountDeletionRequest.tsx +192 -192
- package/sails/gdpr/files/frontend/components/gdpr/DataExportButton.tsx +75 -75
- package/sails/gdpr/files/frontend/pages/PrivacyPolicy.tsx +186 -186
- package/sails/gdpr/install.ts +756 -756
- package/sails/google-oauth/README.md +121 -121
- package/sails/google-oauth/addon.json +22 -22
- package/sails/google-oauth/files/GoogleButton.tsx +50 -50
- package/sails/google-oauth/install.ts +252 -252
- package/sails/i18n/README.md +193 -193
- package/sails/i18n/addon.json +30 -30
- package/sails/i18n/files/frontend/components/LanguageSwitcher.tsx +108 -108
- package/sails/i18n/files/frontend/hooks/useLanguage.ts +31 -31
- package/sails/i18n/files/frontend/lib/i18n.ts +32 -32
- package/sails/i18n/files/frontend/locales/de/common.json +44 -44
- package/sails/i18n/files/frontend/locales/en/common.json +44 -44
- package/sails/i18n/install.ts +407 -407
- package/sails/push-notifications/README.md +163 -163
- package/sails/push-notifications/addon.json +31 -31
- package/sails/push-notifications/files/backend/routes/notifications.ts +153 -153
- package/sails/push-notifications/files/backend/schema/notifications.ts +31 -31
- package/sails/push-notifications/files/backend/services/notifications.ts +117 -117
- package/sails/push-notifications/files/frontend/components/PushNotificationInit.tsx +12 -12
- package/sails/push-notifications/files/frontend/hooks/usePushNotifications.ts +154 -154
- package/sails/push-notifications/install.ts +384 -384
- package/sails/r2-storage/addon.json +29 -29
- package/sails/r2-storage/files/backend/services/storage.ts +71 -71
- package/sails/r2-storage/files/frontend/components/ProfilePictureUpload.tsx +167 -167
- package/sails/r2-storage/install.ts +412 -412
- package/sails/rate-limiting/addon.json +20 -20
- package/sails/rate-limiting/files/backend/middleware/rate-limit-store.ts +104 -104
- package/sails/rate-limiting/files/backend/middleware/rate-limit.ts +137 -137
- package/sails/rate-limiting/install.ts +300 -300
- package/sails/registry.json +107 -107
- package/sails/stripe/README.md +214 -214
- package/sails/stripe/addon.json +24 -24
- package/sails/stripe/files/backend/routes/stripe.ts +154 -154
- package/sails/stripe/files/backend/schema/stripe.ts +74 -74
- package/sails/stripe/files/backend/services/stripe.ts +224 -224
- package/sails/stripe/files/frontend/components/SubscriptionStatus.tsx +135 -135
- package/sails/stripe/files/frontend/hooks/useSubscription.ts +86 -86
- package/sails/stripe/files/frontend/pages/Checkout.tsx +116 -116
- package/sails/stripe/files/frontend/pages/Pricing.tsx +226 -226
- package/sails/stripe/install.ts +378 -378
|
@@ -1,198 +1,198 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File management API routes.
|
|
3
|
-
*
|
|
4
|
-
* All routes require authentication. Files are scoped per user.
|
|
5
|
-
*
|
|
6
|
-
* Endpoints:
|
|
7
|
-
* POST /upload-url -- generate a presigned upload URL
|
|
8
|
-
* GET / -- list the current user's files
|
|
9
|
-
* GET /:fileId -- get file metadata + download URL
|
|
10
|
-
* DELETE /:fileId -- delete a file
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { Router, type Request, type Response } from "express";
|
|
14
|
-
import { eq, and } from "drizzle-orm";
|
|
15
|
-
import { db } from "../db/index.js";
|
|
16
|
-
import { files } from "../db/schema/files.js";
|
|
17
|
-
import { requireAuth } from "../middleware/auth.js";
|
|
18
|
-
import {
|
|
19
|
-
generateUploadUrl,
|
|
20
|
-
generateDownloadUrl,
|
|
21
|
-
deleteFile as deleteFromStorage,
|
|
22
|
-
} from "../services/file-storage.js";
|
|
23
|
-
|
|
24
|
-
export const filesRouter = Router();
|
|
25
|
-
|
|
26
|
-
// All file routes require authentication.
|
|
27
|
-
filesRouter.use(requireAuth);
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// POST /upload-url -- generate presigned upload URL
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
filesRouter.post("/upload-url", async (req: Request, res: Response) => {
|
|
34
|
-
try {
|
|
35
|
-
const { fileName, contentType, maxSize } = req.body as {
|
|
36
|
-
fileName?: string;
|
|
37
|
-
contentType?: string;
|
|
38
|
-
maxSize?: number;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
if (!fileName || typeof fileName !== "string") {
|
|
42
|
-
res.status(400).json({ error: "fileName is required" });
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!contentType || typeof contentType !== "string") {
|
|
47
|
-
res.status(400).json({ error: "contentType is required" });
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const userId = req.user!.id;
|
|
52
|
-
const { uploadUrl, key } = await generateUploadUrl(
|
|
53
|
-
userId,
|
|
54
|
-
fileName,
|
|
55
|
-
contentType,
|
|
56
|
-
maxSize,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// Create a file record in the database so we can track it.
|
|
60
|
-
const [fileRecord] = await db
|
|
61
|
-
.insert(files)
|
|
62
|
-
.values({
|
|
63
|
-
id: crypto.randomUUID(),
|
|
64
|
-
userId,
|
|
65
|
-
key,
|
|
66
|
-
fileName,
|
|
67
|
-
contentType,
|
|
68
|
-
sizeBytes: maxSize ?? null,
|
|
69
|
-
})
|
|
70
|
-
.returning();
|
|
71
|
-
|
|
72
|
-
res.json({
|
|
73
|
-
uploadUrl,
|
|
74
|
-
file: {
|
|
75
|
-
id: fileRecord.id,
|
|
76
|
-
key: fileRecord.key,
|
|
77
|
-
fileName: fileRecord.fileName,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
} catch (error) {
|
|
81
|
-
console.error("Error generating upload URL:", error);
|
|
82
|
-
res.status(500).json({ error: "Failed to generate upload URL" });
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
// GET / -- list user's files
|
|
88
|
-
// ---------------------------------------------------------------------------
|
|
89
|
-
|
|
90
|
-
filesRouter.get("/", async (req: Request, res: Response) => {
|
|
91
|
-
try {
|
|
92
|
-
const userId = req.user!.id;
|
|
93
|
-
const prefix = req.query.prefix as string | undefined;
|
|
94
|
-
|
|
95
|
-
let query = db
|
|
96
|
-
.select()
|
|
97
|
-
.from(files)
|
|
98
|
-
.where(eq(files.userId, userId))
|
|
99
|
-
.$dynamic();
|
|
100
|
-
|
|
101
|
-
if (prefix) {
|
|
102
|
-
query = query.where(
|
|
103
|
-
and(eq(files.userId, userId)),
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const userFiles = await db
|
|
108
|
-
.select()
|
|
109
|
-
.from(files)
|
|
110
|
-
.where(eq(files.userId, userId))
|
|
111
|
-
.orderBy(files.createdAt);
|
|
112
|
-
|
|
113
|
-
res.json({
|
|
114
|
-
files: userFiles.map((f) => ({
|
|
115
|
-
id: f.id,
|
|
116
|
-
fileName: f.fileName,
|
|
117
|
-
contentType: f.contentType,
|
|
118
|
-
sizeBytes: f.sizeBytes,
|
|
119
|
-
createdAt: f.createdAt,
|
|
120
|
-
})),
|
|
121
|
-
});
|
|
122
|
-
} catch (error) {
|
|
123
|
-
console.error("Error listing files:", error);
|
|
124
|
-
res.status(500).json({ error: "Failed to list files" });
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// ---------------------------------------------------------------------------
|
|
129
|
-
// GET /:fileId -- get file metadata + download URL
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
filesRouter.get("/:fileId", async (req: Request, res: Response) => {
|
|
133
|
-
try {
|
|
134
|
-
const userId = req.user!.id;
|
|
135
|
-
const { fileId } = req.params;
|
|
136
|
-
|
|
137
|
-
const file = await db.query.files.findFirst({
|
|
138
|
-
where: and(eq(files.id, fileId), eq(files.userId, userId)),
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
if (!file) {
|
|
142
|
-
res.status(404).json({ error: "File not found" });
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const downloadUrl = await generateDownloadUrl(file.key);
|
|
147
|
-
|
|
148
|
-
res.json({
|
|
149
|
-
file: {
|
|
150
|
-
id: file.id,
|
|
151
|
-
fileName: file.fileName,
|
|
152
|
-
contentType: file.contentType,
|
|
153
|
-
sizeBytes: file.sizeBytes,
|
|
154
|
-
createdAt: file.createdAt,
|
|
155
|
-
downloadUrl,
|
|
156
|
-
},
|
|
157
|
-
});
|
|
158
|
-
} catch (error) {
|
|
159
|
-
console.error("Error getting file:", error);
|
|
160
|
-
res.status(500).json({ error: "Failed to get file" });
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// ---------------------------------------------------------------------------
|
|
165
|
-
// DELETE /:fileId -- delete a file
|
|
166
|
-
// ---------------------------------------------------------------------------
|
|
167
|
-
|
|
168
|
-
filesRouter.delete("/:fileId", async (req: Request, res: Response) => {
|
|
169
|
-
try {
|
|
170
|
-
const userId = req.user!.id;
|
|
171
|
-
const { fileId } = req.params;
|
|
172
|
-
|
|
173
|
-
const file = await db.query.files.findFirst({
|
|
174
|
-
where: and(eq(files.id, fileId), eq(files.userId, userId)),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (!file) {
|
|
178
|
-
res.status(404).json({ error: "File not found" });
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Delete from S3-compatible storage.
|
|
183
|
-
try {
|
|
184
|
-
await deleteFromStorage(file.key);
|
|
185
|
-
} catch (err) {
|
|
186
|
-
console.error("Warning: failed to delete file from storage:", err);
|
|
187
|
-
// Continue with DB deletion even if storage deletion fails.
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// Delete from database.
|
|
191
|
-
await db.delete(files).where(eq(files.id, fileId));
|
|
192
|
-
|
|
193
|
-
res.json({ success: true });
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error("Error deleting file:", error);
|
|
196
|
-
res.status(500).json({ error: "Failed to delete file" });
|
|
197
|
-
}
|
|
198
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* File management API routes.
|
|
3
|
+
*
|
|
4
|
+
* All routes require authentication. Files are scoped per user.
|
|
5
|
+
*
|
|
6
|
+
* Endpoints:
|
|
7
|
+
* POST /upload-url -- generate a presigned upload URL
|
|
8
|
+
* GET / -- list the current user's files
|
|
9
|
+
* GET /:fileId -- get file metadata + download URL
|
|
10
|
+
* DELETE /:fileId -- delete a file
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Router, type Request, type Response } from "express";
|
|
14
|
+
import { eq, and } from "drizzle-orm";
|
|
15
|
+
import { db } from "../db/index.js";
|
|
16
|
+
import { files } from "../db/schema/files.js";
|
|
17
|
+
import { requireAuth } from "../middleware/auth.js";
|
|
18
|
+
import {
|
|
19
|
+
generateUploadUrl,
|
|
20
|
+
generateDownloadUrl,
|
|
21
|
+
deleteFile as deleteFromStorage,
|
|
22
|
+
} from "../services/file-storage.js";
|
|
23
|
+
|
|
24
|
+
export const filesRouter = Router();
|
|
25
|
+
|
|
26
|
+
// All file routes require authentication.
|
|
27
|
+
filesRouter.use(requireAuth);
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// POST /upload-url -- generate presigned upload URL
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
filesRouter.post("/upload-url", async (req: Request, res: Response) => {
|
|
34
|
+
try {
|
|
35
|
+
const { fileName, contentType, maxSize } = req.body as {
|
|
36
|
+
fileName?: string;
|
|
37
|
+
contentType?: string;
|
|
38
|
+
maxSize?: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
if (!fileName || typeof fileName !== "string") {
|
|
42
|
+
res.status(400).json({ error: "fileName is required" });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!contentType || typeof contentType !== "string") {
|
|
47
|
+
res.status(400).json({ error: "contentType is required" });
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const userId = req.user!.id;
|
|
52
|
+
const { uploadUrl, key } = await generateUploadUrl(
|
|
53
|
+
userId,
|
|
54
|
+
fileName,
|
|
55
|
+
contentType,
|
|
56
|
+
maxSize,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Create a file record in the database so we can track it.
|
|
60
|
+
const [fileRecord] = await db
|
|
61
|
+
.insert(files)
|
|
62
|
+
.values({
|
|
63
|
+
id: crypto.randomUUID(),
|
|
64
|
+
userId,
|
|
65
|
+
key,
|
|
66
|
+
fileName,
|
|
67
|
+
contentType,
|
|
68
|
+
sizeBytes: maxSize ?? null,
|
|
69
|
+
})
|
|
70
|
+
.returning();
|
|
71
|
+
|
|
72
|
+
res.json({
|
|
73
|
+
uploadUrl,
|
|
74
|
+
file: {
|
|
75
|
+
id: fileRecord.id,
|
|
76
|
+
key: fileRecord.key,
|
|
77
|
+
fileName: fileRecord.fileName,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.error("Error generating upload URL:", error);
|
|
82
|
+
res.status(500).json({ error: "Failed to generate upload URL" });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// GET / -- list user's files
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
filesRouter.get("/", async (req: Request, res: Response) => {
|
|
91
|
+
try {
|
|
92
|
+
const userId = req.user!.id;
|
|
93
|
+
const prefix = req.query.prefix as string | undefined;
|
|
94
|
+
|
|
95
|
+
let query = db
|
|
96
|
+
.select()
|
|
97
|
+
.from(files)
|
|
98
|
+
.where(eq(files.userId, userId))
|
|
99
|
+
.$dynamic();
|
|
100
|
+
|
|
101
|
+
if (prefix) {
|
|
102
|
+
query = query.where(
|
|
103
|
+
and(eq(files.userId, userId)),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const userFiles = await db
|
|
108
|
+
.select()
|
|
109
|
+
.from(files)
|
|
110
|
+
.where(eq(files.userId, userId))
|
|
111
|
+
.orderBy(files.createdAt);
|
|
112
|
+
|
|
113
|
+
res.json({
|
|
114
|
+
files: userFiles.map((f) => ({
|
|
115
|
+
id: f.id,
|
|
116
|
+
fileName: f.fileName,
|
|
117
|
+
contentType: f.contentType,
|
|
118
|
+
sizeBytes: f.sizeBytes,
|
|
119
|
+
createdAt: f.createdAt,
|
|
120
|
+
})),
|
|
121
|
+
});
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Error listing files:", error);
|
|
124
|
+
res.status(500).json({ error: "Failed to list files" });
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// GET /:fileId -- get file metadata + download URL
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
filesRouter.get("/:fileId", async (req: Request, res: Response) => {
|
|
133
|
+
try {
|
|
134
|
+
const userId = req.user!.id;
|
|
135
|
+
const { fileId } = req.params;
|
|
136
|
+
|
|
137
|
+
const file = await db.query.files.findFirst({
|
|
138
|
+
where: and(eq(files.id, fileId), eq(files.userId, userId)),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (!file) {
|
|
142
|
+
res.status(404).json({ error: "File not found" });
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const downloadUrl = await generateDownloadUrl(file.key);
|
|
147
|
+
|
|
148
|
+
res.json({
|
|
149
|
+
file: {
|
|
150
|
+
id: file.id,
|
|
151
|
+
fileName: file.fileName,
|
|
152
|
+
contentType: file.contentType,
|
|
153
|
+
sizeBytes: file.sizeBytes,
|
|
154
|
+
createdAt: file.createdAt,
|
|
155
|
+
downloadUrl,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.error("Error getting file:", error);
|
|
160
|
+
res.status(500).json({ error: "Failed to get file" });
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// DELETE /:fileId -- delete a file
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
filesRouter.delete("/:fileId", async (req: Request, res: Response) => {
|
|
169
|
+
try {
|
|
170
|
+
const userId = req.user!.id;
|
|
171
|
+
const { fileId } = req.params;
|
|
172
|
+
|
|
173
|
+
const file = await db.query.files.findFirst({
|
|
174
|
+
where: and(eq(files.id, fileId), eq(files.userId, userId)),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!file) {
|
|
178
|
+
res.status(404).json({ error: "File not found" });
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Delete from S3-compatible storage.
|
|
183
|
+
try {
|
|
184
|
+
await deleteFromStorage(file.key);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
console.error("Warning: failed to delete file from storage:", err);
|
|
187
|
+
// Continue with DB deletion even if storage deletion fails.
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Delete from database.
|
|
191
|
+
await db.delete(files).where(eq(files.id, fileId));
|
|
192
|
+
|
|
193
|
+
res.json({ success: true });
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error("Error deleting file:", error);
|
|
196
|
+
res.status(500).json({ error: "Failed to delete file" });
|
|
197
|
+
}
|
|
198
|
+
});
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
pgTable,
|
|
3
|
-
text,
|
|
4
|
-
varchar,
|
|
5
|
-
integer,
|
|
6
|
-
timestamp,
|
|
7
|
-
} from "drizzle-orm/pg-core";
|
|
8
|
-
import { relations } from "drizzle-orm";
|
|
9
|
-
import { users } from "./users.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Files table.
|
|
13
|
-
*
|
|
14
|
-
* Tracks uploaded files and their S3-compatible storage keys. Each file
|
|
15
|
-
* belongs to a user and is deleted when the user is deleted (cascade).
|
|
16
|
-
*/
|
|
17
|
-
export const files = pgTable("files", {
|
|
18
|
-
id: text("id").primaryKey(),
|
|
19
|
-
userId: text("user_id")
|
|
20
|
-
.notNull()
|
|
21
|
-
.references(() => users.id, { onDelete: "cascade" }),
|
|
22
|
-
key: text("key").notNull(),
|
|
23
|
-
fileName: text("file_name").notNull(),
|
|
24
|
-
contentType: varchar("content_type", { length: 100 }),
|
|
25
|
-
sizeBytes: integer("size_bytes"),
|
|
26
|
-
createdAt: timestamp("created_at", { withTimezone: true })
|
|
27
|
-
.notNull()
|
|
28
|
-
.defaultNow(),
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export const filesRelations = relations(files, ({ one }) => ({
|
|
32
|
-
user: one(users, {
|
|
33
|
-
fields: [files.userId],
|
|
34
|
-
references: [users.id],
|
|
35
|
-
}),
|
|
36
|
-
}));
|
|
1
|
+
import {
|
|
2
|
+
pgTable,
|
|
3
|
+
text,
|
|
4
|
+
varchar,
|
|
5
|
+
integer,
|
|
6
|
+
timestamp,
|
|
7
|
+
} from "drizzle-orm/pg-core";
|
|
8
|
+
import { relations } from "drizzle-orm";
|
|
9
|
+
import { users } from "./users.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Files table.
|
|
13
|
+
*
|
|
14
|
+
* Tracks uploaded files and their S3-compatible storage keys. Each file
|
|
15
|
+
* belongs to a user and is deleted when the user is deleted (cascade).
|
|
16
|
+
*/
|
|
17
|
+
export const files = pgTable("files", {
|
|
18
|
+
id: text("id").primaryKey(),
|
|
19
|
+
userId: text("user_id")
|
|
20
|
+
.notNull()
|
|
21
|
+
.references(() => users.id, { onDelete: "cascade" }),
|
|
22
|
+
key: text("key").notNull(),
|
|
23
|
+
fileName: text("file_name").notNull(),
|
|
24
|
+
contentType: varchar("content_type", { length: 100 }),
|
|
25
|
+
sizeBytes: integer("size_bytes"),
|
|
26
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
27
|
+
.notNull()
|
|
28
|
+
.defaultNow(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const filesRelations = relations(files, ({ one }) => ({
|
|
32
|
+
user: one(users, {
|
|
33
|
+
fields: [files.userId],
|
|
34
|
+
references: [users.id],
|
|
35
|
+
}),
|
|
36
|
+
}));
|