9drive 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.
- package/LICENSE +201 -0
- package/README.md +389 -0
- package/backend/dist/app.js +113 -0
- package/backend/dist/config/env.js +75 -0
- package/backend/dist/config/prisma.js +30 -0
- package/backend/dist/middleware/api-key.middleware.js +38 -0
- package/backend/dist/middleware/auth.middleware.js +21 -0
- package/backend/dist/middleware/error.middleware.js +7 -0
- package/backend/dist/modules/api-keys/api-key.routes.js +90 -0
- package/backend/dist/modules/audit-logs/audit-log.routes.js +20 -0
- package/backend/dist/modules/auth/auth.routes.js +201 -0
- package/backend/dist/modules/cdn/cdn.routes.js +105 -0
- package/backend/dist/modules/connected-accounts/connected-account.routes.js +313 -0
- package/backend/dist/modules/files/file.routes.js +1013 -0
- package/backend/dist/modules/files/stream-file.js +10 -0
- package/backend/dist/modules/files/stream-google-file.js +79 -0
- package/backend/dist/modules/folders/folder.routes.js +546 -0
- package/backend/dist/modules/google/google.service.js +485 -0
- package/backend/dist/modules/invites/invite.routes.js +127 -0
- package/backend/dist/modules/provider-configs/provider-config.routes.js +53 -0
- package/backend/dist/modules/public/public.routes.js +44 -0
- package/backend/dist/modules/public-api/public-api.routes.js +8 -0
- package/backend/dist/modules/s3/s3.service.js +104 -0
- package/backend/dist/modules/storage/storage.routes.js +108 -0
- package/backend/dist/modules/system/system.routes.js +663 -0
- package/backend/dist/modules/uploads/upload.routes.js +569 -0
- package/backend/dist/scripts/debug-query.js +19 -0
- package/backend/dist/scripts/seed-google-config-if-present.js +49 -0
- package/backend/dist/scripts/seed-google-config.js +40 -0
- package/backend/dist/scripts/test-api-upload.js +37 -0
- package/backend/dist/scripts/test-external-upload.js +181 -0
- package/backend/dist/scripts/test-s3-connection.js +33 -0
- package/backend/dist/server.js +7 -0
- package/backend/dist/utils/audit.js +20 -0
- package/backend/dist/utils/crypto.js +33 -0
- package/backend/dist/utils/jwt.js +15 -0
- package/backend/dist/utils/password.js +14 -0
- package/backend/prisma/generated/sqlite-client/client.d.ts +1 -0
- package/backend/prisma/generated/sqlite-client/client.js +5 -0
- package/backend/prisma/generated/sqlite-client/default.d.ts +1 -0
- package/backend/prisma/generated/sqlite-client/default.js +5 -0
- package/backend/prisma/generated/sqlite-client/edge.d.ts +1 -0
- package/backend/prisma/generated/sqlite-client/edge.js +436 -0
- package/backend/prisma/generated/sqlite-client/index-browser.js +422 -0
- package/backend/prisma/generated/sqlite-client/index.d.ts +35178 -0
- package/backend/prisma/generated/sqlite-client/index.js +457 -0
- package/backend/prisma/generated/sqlite-client/package.json +183 -0
- package/backend/prisma/generated/sqlite-client/query_engine-windows.dll.node +0 -0
- package/backend/prisma/generated/sqlite-client/query_engine_bg.js +2 -0
- package/backend/prisma/generated/sqlite-client/query_engine_bg.wasm +0 -0
- package/backend/prisma/generated/sqlite-client/runtime/edge-esm.js +35 -0
- package/backend/prisma/generated/sqlite-client/runtime/edge.js +35 -0
- package/backend/prisma/generated/sqlite-client/runtime/index-browser.d.ts +370 -0
- package/backend/prisma/generated/sqlite-client/runtime/index-browser.js +17 -0
- package/backend/prisma/generated/sqlite-client/runtime/library.d.ts +3982 -0
- package/backend/prisma/generated/sqlite-client/runtime/library.js +147 -0
- package/backend/prisma/generated/sqlite-client/runtime/react-native.js +84 -0
- package/backend/prisma/generated/sqlite-client/runtime/wasm-compiler-edge.js +85 -0
- package/backend/prisma/generated/sqlite-client/runtime/wasm-engine-edge.js +38 -0
- package/backend/prisma/generated/sqlite-client/schema.prisma +354 -0
- package/backend/prisma/generated/sqlite-client/wasm-edge-light-loader.mjs +5 -0
- package/backend/prisma/generated/sqlite-client/wasm-worker-loader.mjs +5 -0
- package/backend/prisma/generated/sqlite-client/wasm.d.ts +1 -0
- package/backend/prisma/generated/sqlite-client/wasm.js +443 -0
- package/backend/prisma/migrations/20260604084325_init/migration.sql +191 -0
- package/backend/prisma/migrations/20260604090020_global_provider_config/migration.sql +8 -0
- package/backend/prisma/migrations/20260604091957_virtual_folders/migration.sql +32 -0
- package/backend/prisma/migrations/20260604095418_nested_folders/migration.sql +8 -0
- package/backend/prisma/migrations/20260604095705_file_shares/migration.sql +22 -0
- package/backend/prisma/migrations/20260604100802_file_preview_tokens/migration.sql +20 -0
- package/backend/prisma/migrations/20260604120000_add_file_share_token/migration.sql +3 -0
- package/backend/prisma/migrations/20260604130000_workspace_invites/migration.sql +17 -0
- package/backend/prisma/migrations/20260604133000_invite_targets/migration.sql +9 -0
- package/backend/prisma/migrations/20260605090000_query_indexes/migration.sql +12 -0
- package/backend/prisma/migrations/20260605100000_folder_icons/migration.sql +1 -0
- package/backend/prisma/migrations/20260605110000_google_auth_handoff/migration.sql +22 -0
- package/backend/prisma/migrations/20260611120000_s3_storage_support/migration.sql +31 -0
- package/backend/prisma/migrations/20260611130000_upload_routing_policy/migration.sql +14 -0
- package/backend/prisma/migrations/20260611140000_api_keys/migration.sql +21 -0
- package/backend/prisma/migrations/20260630030000_upload_session_resumable_fields/migration.sql +9 -0
- package/backend/prisma/migrations/migration_lock.toml +3 -0
- package/backend/prisma/schema.prisma +353 -0
- package/backend/prisma/schema.sqlite.prisma +354 -0
- package/backend/public/assets/index-B4ifIM_G.js +135 -0
- package/backend/public/assets/index-DwOPZrmt.css +2 -0
- package/backend/public/assets/ort-wasm-simd-threaded.jsep-D5Jk56-t.wasm +0 -0
- package/backend/public/assets/ort.bundle.min-CpXgll2e.js +2746 -0
- package/backend/public/assets/ort.bundle.min-OfoG_cy9.mjs +2817 -0
- package/backend/public/assets/ort.webgpu.bundle.min-C1vdBqT7.js +2746 -0
- package/backend/public/assets/ort.webgpu.bundle.min-CEayb2S6.mjs +2817 -0
- package/backend/public/assets/pdf.worker.min-Dswkl-cV.mjs +29 -0
- package/backend/public/favicon.ico +74 -0
- package/backend/public/favicon.svg +74 -0
- package/backend/public/icons.svg +24 -0
- package/backend/public/index.html +34 -0
- package/backend/public/manifest.webmanifest +1 -0
- package/backend/public/maskable-icon.svg +22 -0
- package/backend/public/pwa-192x192.svg +22 -0
- package/backend/public/pwa-512x512.svg +22 -0
- package/backend/public/registerSW.js +1 -0
- package/backend/public/sw.js +1 -0
- package/backend/public/workbox-9c191d2f.js +1 -0
- package/bin/9drive.js +320 -0
- package/package.json +60 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOAuthClient = createOAuthClient;
|
|
4
|
+
exports.getAuthedGoogleClient = getAuthedGoogleClient;
|
|
5
|
+
exports.syncGoogleQuota = syncGoogleQuota;
|
|
6
|
+
exports.ensureGoogleAppFolder = ensureGoogleAppFolder;
|
|
7
|
+
exports.syncGoogleAppFolderFiles = syncGoogleAppFolderFiles;
|
|
8
|
+
exports.getGoogleStarredFiles = getGoogleStarredFiles;
|
|
9
|
+
exports.getGoogleRecentFiles = getGoogleRecentFiles;
|
|
10
|
+
exports.getGoogleSharedFiles = getGoogleSharedFiles;
|
|
11
|
+
exports.setGoogleFileStarred = setGoogleFileStarred;
|
|
12
|
+
exports.findInheritedPermissionOrigin = findInheritedPermissionOrigin;
|
|
13
|
+
const googleapis_1 = require("googleapis");
|
|
14
|
+
const prisma_js_1 = require("../../config/prisma.js");
|
|
15
|
+
const crypto_js_1 = require("../../utils/crypto.js");
|
|
16
|
+
const googleDriveFolderMimeType = 'application/vnd.google-apps.folder';
|
|
17
|
+
const appFolderName = '9drive';
|
|
18
|
+
function createOAuthClient(config) {
|
|
19
|
+
return new googleapis_1.google.auth.OAuth2((0, crypto_js_1.decryptText)(config.clientIdEncrypted), (0, crypto_js_1.decryptText)(config.clientSecretEncrypted), config.redirectUri);
|
|
20
|
+
}
|
|
21
|
+
async function getAuthedGoogleClient(account) {
|
|
22
|
+
if (!account.accessTokenEncrypted || !account.refreshTokenEncrypted || !account.tokenExpiresAt)
|
|
23
|
+
throw new Error('Google account tokens are missing.');
|
|
24
|
+
if (!account.providerConfigId)
|
|
25
|
+
throw new Error('Google provider config is missing.');
|
|
26
|
+
const config = await prisma_js_1.prisma.providerConfig.findUniqueOrThrow({ where: { id: account.providerConfigId } });
|
|
27
|
+
const client = createOAuthClient(config);
|
|
28
|
+
client.setCredentials({
|
|
29
|
+
access_token: (0, crypto_js_1.decryptText)(account.accessTokenEncrypted),
|
|
30
|
+
refresh_token: (0, crypto_js_1.decryptText)(account.refreshTokenEncrypted),
|
|
31
|
+
expiry_date: account.tokenExpiresAt.getTime(),
|
|
32
|
+
});
|
|
33
|
+
if (account.tokenExpiresAt.getTime() < Date.now() + 60_000) {
|
|
34
|
+
const result = await client.refreshAccessToken();
|
|
35
|
+
const credentials = result.credentials;
|
|
36
|
+
if (credentials.access_token) {
|
|
37
|
+
await prisma_js_1.prisma.connectedAccount.update({
|
|
38
|
+
where: { id: account.id },
|
|
39
|
+
data: {
|
|
40
|
+
accessTokenEncrypted: (0, crypto_js_1.encryptText)(credentials.access_token),
|
|
41
|
+
tokenExpiresAt: new Date(credentials.expiry_date ?? Date.now() + 3600_000),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
client.setCredentials(credentials);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return client;
|
|
48
|
+
}
|
|
49
|
+
async function syncGoogleQuota(accountId) {
|
|
50
|
+
const account = await prisma_js_1.prisma.connectedAccount.findUniqueOrThrow({ where: { id: accountId } });
|
|
51
|
+
const auth = await getAuthedGoogleClient(account);
|
|
52
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
53
|
+
const about = await drive.about.get({ fields: 'storageQuota,user' });
|
|
54
|
+
const quota = about.data.storageQuota;
|
|
55
|
+
const total = quota?.limit ? BigInt(quota.limit) : null;
|
|
56
|
+
const used = quota?.usage ? BigInt(quota.usage) : 0n;
|
|
57
|
+
return prisma_js_1.prisma.storageAccount.upsert({
|
|
58
|
+
where: { connectedAccountId: accountId },
|
|
59
|
+
create: {
|
|
60
|
+
connectedAccountId: accountId,
|
|
61
|
+
totalBytes: total,
|
|
62
|
+
usedBytes: used,
|
|
63
|
+
availableBytes: total === null ? null : total - used,
|
|
64
|
+
trashBytes: quota?.usageInDriveTrash ? BigInt(quota.usageInDriveTrash) : null,
|
|
65
|
+
lastSyncedAt: new Date(),
|
|
66
|
+
},
|
|
67
|
+
update: {
|
|
68
|
+
totalBytes: total,
|
|
69
|
+
usedBytes: used,
|
|
70
|
+
availableBytes: total === null ? null : total - used,
|
|
71
|
+
trashBytes: quota?.usageInDriveTrash ? BigInt(quota.usageInDriveTrash) : null,
|
|
72
|
+
lastSyncedAt: new Date(),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
function escapeDriveQueryValue(value) {
|
|
77
|
+
return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
78
|
+
}
|
|
79
|
+
async function ensureGoogleAppFolder(account) {
|
|
80
|
+
const auth = await getAuthedGoogleClient(account);
|
|
81
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
82
|
+
const queryName = escapeDriveQueryValue(appFolderName);
|
|
83
|
+
const existing = await drive.files.list({
|
|
84
|
+
q: `name = '${queryName}' and mimeType = '${googleDriveFolderMimeType}' and 'root' in parents and trashed = false`,
|
|
85
|
+
spaces: 'drive',
|
|
86
|
+
fields: 'files(id,name)',
|
|
87
|
+
pageSize: 1,
|
|
88
|
+
});
|
|
89
|
+
const folderId = existing.data.files?.[0]?.id ?? (await drive.files.create({
|
|
90
|
+
requestBody: { name: appFolderName, mimeType: googleDriveFolderMimeType, parents: ['root'] },
|
|
91
|
+
fields: 'id',
|
|
92
|
+
})).data.id;
|
|
93
|
+
if (!folderId)
|
|
94
|
+
throw new Error('Failed to create Google Drive app folder.');
|
|
95
|
+
return folderId;
|
|
96
|
+
}
|
|
97
|
+
async function syncGoogleAppFolderFiles(accountId, userId) {
|
|
98
|
+
const account = await prisma_js_1.prisma.connectedAccount.findFirstOrThrow({ where: { id: accountId, userId, provider: 'google_drive', status: 'connected' } });
|
|
99
|
+
const auth = await getAuthedGoogleClient(account);
|
|
100
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
101
|
+
const driveFolders = [];
|
|
102
|
+
let folderPageToken;
|
|
103
|
+
do {
|
|
104
|
+
const response = await drive.files.list({
|
|
105
|
+
q: `mimeType = '${googleDriveFolderMimeType}' and trashed = false`,
|
|
106
|
+
spaces: 'drive',
|
|
107
|
+
fields: 'nextPageToken,files(id,name,parents)',
|
|
108
|
+
pageSize: 1000,
|
|
109
|
+
pageToken: folderPageToken,
|
|
110
|
+
});
|
|
111
|
+
for (const folder of response.data.files ?? []) {
|
|
112
|
+
if (!folder.id || !folder.name)
|
|
113
|
+
continue;
|
|
114
|
+
driveFolders.push({
|
|
115
|
+
id: folder.id,
|
|
116
|
+
name: folder.name,
|
|
117
|
+
parentId: folder.parents?.[0] ?? null,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
folderPageToken = response.data.nextPageToken ?? undefined;
|
|
121
|
+
} while (folderPageToken);
|
|
122
|
+
const existingFolders = await prisma_js_1.prisma.folder.findMany({
|
|
123
|
+
where: { userId, connectedAccountId: account.id },
|
|
124
|
+
});
|
|
125
|
+
const existingFolderByProviderId = new Map(existingFolders.filter((f) => f.providerFolderId).map((f) => [f.providerFolderId, f]));
|
|
126
|
+
const gdriveFolderIdToDbFolder = new Map();
|
|
127
|
+
for (const driveFolder of driveFolders) {
|
|
128
|
+
let dbFolder = existingFolderByProviderId.get(driveFolder.id);
|
|
129
|
+
if (!dbFolder) {
|
|
130
|
+
dbFolder = await prisma_js_1.prisma.folder.create({
|
|
131
|
+
data: {
|
|
132
|
+
userId,
|
|
133
|
+
connectedAccountId: account.id,
|
|
134
|
+
provider: 'google_drive',
|
|
135
|
+
providerFolderId: driveFolder.id,
|
|
136
|
+
name: driveFolder.name,
|
|
137
|
+
color: 'text-blue-500',
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
existingFolderByProviderId.set(driveFolder.id, dbFolder);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
if (dbFolder.name !== driveFolder.name || dbFolder.deletedAt !== null) {
|
|
144
|
+
dbFolder = await prisma_js_1.prisma.folder.update({
|
|
145
|
+
where: { id: dbFolder.id },
|
|
146
|
+
data: { name: driveFolder.name, deletedAt: null },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
gdriveFolderIdToDbFolder.set(driveFolder.id, dbFolder);
|
|
151
|
+
}
|
|
152
|
+
// Set parentId relationships among synced folders
|
|
153
|
+
for (const driveFolder of driveFolders) {
|
|
154
|
+
const dbFolder = gdriveFolderIdToDbFolder.get(driveFolder.id);
|
|
155
|
+
if (!dbFolder)
|
|
156
|
+
continue;
|
|
157
|
+
const parentDbFolder = driveFolder.parentId ? gdriveFolderIdToDbFolder.get(driveFolder.parentId) : null;
|
|
158
|
+
const targetParentId = parentDbFolder ? parentDbFolder.id : null;
|
|
159
|
+
if (dbFolder.parentId !== targetParentId) {
|
|
160
|
+
await prisma_js_1.prisma.folder.update({
|
|
161
|
+
where: { id: dbFolder.id },
|
|
162
|
+
data: { parentId: targetParentId },
|
|
163
|
+
});
|
|
164
|
+
dbFolder.parentId = targetParentId;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Mark folders missing on Google Drive as deleted
|
|
168
|
+
const driveFolderIds = new Set(driveFolders.map((f) => f.id));
|
|
169
|
+
const missingFolderIds = existingFolders
|
|
170
|
+
.filter((f) => f.deletedAt === null && f.providerFolderId && !driveFolderIds.has(f.providerFolderId))
|
|
171
|
+
.map((f) => f.id);
|
|
172
|
+
if (missingFolderIds.length > 0) {
|
|
173
|
+
await prisma_js_1.prisma.folder.updateMany({
|
|
174
|
+
where: { id: { in: missingFolderIds } },
|
|
175
|
+
data: { deletedAt: new Date() },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const driveFiles = [];
|
|
179
|
+
let pageToken;
|
|
180
|
+
do {
|
|
181
|
+
const response = await drive.files.list({
|
|
182
|
+
q: `mimeType != '${googleDriveFolderMimeType}' and trashed = false`,
|
|
183
|
+
spaces: 'drive',
|
|
184
|
+
fields: 'nextPageToken,files(id,name,mimeType,size,parents,createdTime,modifiedTime)',
|
|
185
|
+
pageSize: 1000,
|
|
186
|
+
pageToken,
|
|
187
|
+
});
|
|
188
|
+
for (const file of response.data.files ?? []) {
|
|
189
|
+
if (!file.id || !file.name || !file.mimeType)
|
|
190
|
+
continue;
|
|
191
|
+
const parentId = file.parents?.[0] ?? null;
|
|
192
|
+
driveFiles.push({
|
|
193
|
+
id: file.id,
|
|
194
|
+
name: file.name,
|
|
195
|
+
mimeType: file.mimeType,
|
|
196
|
+
sizeBytes: BigInt(file.size ?? 0),
|
|
197
|
+
parentId,
|
|
198
|
+
createdTime: file.createdTime,
|
|
199
|
+
modifiedTime: file.modifiedTime,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
pageToken = response.data.nextPageToken ?? undefined;
|
|
203
|
+
} while (pageToken);
|
|
204
|
+
const existingFiles = await prisma_js_1.prisma.file.findMany({ where: { userId, connectedAccountId: account.id, provider: 'google_drive' } });
|
|
205
|
+
const existingByProviderId = new Map(existingFiles.map((file) => [file.providerFileId, file]));
|
|
206
|
+
const driveFileIds = new Set(driveFiles.map((file) => file.id));
|
|
207
|
+
let created = 0;
|
|
208
|
+
let updated = 0;
|
|
209
|
+
let deleted = 0;
|
|
210
|
+
for (const driveFile of driveFiles) {
|
|
211
|
+
const dbFolder = driveFile.parentId ? gdriveFolderIdToDbFolder.get(driveFile.parentId) : null;
|
|
212
|
+
const dbFolderId = dbFolder ? dbFolder.id : null;
|
|
213
|
+
const existing = existingByProviderId.get(driveFile.id);
|
|
214
|
+
if (!existing) {
|
|
215
|
+
await prisma_js_1.prisma.file.create({
|
|
216
|
+
data: {
|
|
217
|
+
userId,
|
|
218
|
+
connectedAccountId: account.id,
|
|
219
|
+
provider: 'google_drive',
|
|
220
|
+
providerFileId: driveFile.id,
|
|
221
|
+
name: driveFile.name,
|
|
222
|
+
mimeType: driveFile.mimeType,
|
|
223
|
+
sizeBytes: driveFile.sizeBytes,
|
|
224
|
+
status: 'active',
|
|
225
|
+
folderId: dbFolderId,
|
|
226
|
+
createdAt: driveFile.createdTime ? new Date(driveFile.createdTime) : new Date(),
|
|
227
|
+
updatedAt: driveFile.modifiedTime ? new Date(driveFile.modifiedTime) : new Date(),
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
created += 1;
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const needsUpdate = existing.name !== driveFile.name ||
|
|
234
|
+
existing.mimeType !== driveFile.mimeType ||
|
|
235
|
+
existing.sizeBytes !== driveFile.sizeBytes ||
|
|
236
|
+
existing.status !== 'active' ||
|
|
237
|
+
existing.deletedAt !== null ||
|
|
238
|
+
existing.folderId !== dbFolderId;
|
|
239
|
+
if (needsUpdate) {
|
|
240
|
+
await prisma_js_1.prisma.file.update({
|
|
241
|
+
where: { id: existing.id },
|
|
242
|
+
data: {
|
|
243
|
+
name: driveFile.name,
|
|
244
|
+
mimeType: driveFile.mimeType,
|
|
245
|
+
sizeBytes: driveFile.sizeBytes,
|
|
246
|
+
status: 'active',
|
|
247
|
+
deletedAt: null,
|
|
248
|
+
folderId: dbFolderId,
|
|
249
|
+
...(driveFile.modifiedTime ? { updatedAt: new Date(driveFile.modifiedTime) } : {}),
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
updated += 1;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const missingActiveIds = existingFiles.filter((file) => file.status === 'active' && !driveFileIds.has(file.providerFileId)).map((file) => file.id);
|
|
256
|
+
if (missingActiveIds.length > 0) {
|
|
257
|
+
const result = await prisma_js_1.prisma.file.updateMany({ where: { id: { in: missingActiveIds }, userId }, data: { status: 'deleted', deletedAt: new Date() } });
|
|
258
|
+
deleted = result.count;
|
|
259
|
+
}
|
|
260
|
+
await syncGoogleQuota(account.id).catch(() => undefined);
|
|
261
|
+
return { accountId: account.id, created, updated, deleted };
|
|
262
|
+
}
|
|
263
|
+
async function getGoogleStarredFiles(accountId, userId) {
|
|
264
|
+
const account = await prisma_js_1.prisma.connectedAccount.findFirstOrThrow({ where: { id: accountId, userId, provider: 'google_drive', status: 'connected' } });
|
|
265
|
+
const auth = await getAuthedGoogleClient(account);
|
|
266
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
267
|
+
const response = await drive.files.list({
|
|
268
|
+
q: 'starred = true and trashed = false',
|
|
269
|
+
spaces: 'drive',
|
|
270
|
+
fields: 'files(id,name,mimeType,size,parents,createdTime,modifiedTime,webViewLink)',
|
|
271
|
+
pageSize: 100,
|
|
272
|
+
});
|
|
273
|
+
// Look up folder names from DB if available
|
|
274
|
+
const existingFiles = await prisma_js_1.prisma.file.findMany({
|
|
275
|
+
where: { userId, connectedAccountId: account.id, providerFileId: { in: (response.data.files ?? []).map((f) => f.id).filter(Boolean) } },
|
|
276
|
+
include: { folder: { select: { id: true, name: true } } },
|
|
277
|
+
});
|
|
278
|
+
const fileByProviderId = new Map(existingFiles.map((f) => [f.providerFileId, f]));
|
|
279
|
+
return (response.data.files ?? []).map((f) => {
|
|
280
|
+
const dbFile = f.id ? fileByProviderId.get(f.id) : null;
|
|
281
|
+
return {
|
|
282
|
+
id: dbFile?.id ?? f.id ?? '',
|
|
283
|
+
providerFileId: f.id ?? '',
|
|
284
|
+
name: f.name ?? 'Untitled',
|
|
285
|
+
mimeType: f.mimeType ?? 'application/octet-stream',
|
|
286
|
+
sizeBytes: String(f.size ?? '0'),
|
|
287
|
+
createdAt: f.createdTime ?? new Date().toISOString(),
|
|
288
|
+
updatedAt: f.modifiedTime ?? f.createdTime ?? new Date().toISOString(),
|
|
289
|
+
folderId: dbFile?.folderId ?? null,
|
|
290
|
+
folder: dbFile?.folder ?? null,
|
|
291
|
+
connectedAccount: { id: account.id, email: account.email, provider: 'google_drive' },
|
|
292
|
+
driveUrl: f.webViewLink ?? (f.id ? `https://drive.google.com/file/d/${f.id}/view?usp=sharing` : null),
|
|
293
|
+
};
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async function getGoogleRecentFiles(accountId, userId) {
|
|
297
|
+
const account = await prisma_js_1.prisma.connectedAccount.findFirstOrThrow({ where: { id: accountId, userId, provider: 'google_drive', status: 'connected' } });
|
|
298
|
+
const auth = await getAuthedGoogleClient(account);
|
|
299
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
300
|
+
const response = await drive.files.list({
|
|
301
|
+
q: `mimeType != '${googleDriveFolderMimeType}' and trashed = false`,
|
|
302
|
+
orderBy: 'viewedByMeTime desc, modifiedTime desc',
|
|
303
|
+
spaces: 'drive',
|
|
304
|
+
fields: 'files(id,name,mimeType,size,parents,createdTime,modifiedTime,viewedByMeTime,webViewLink)',
|
|
305
|
+
pageSize: 50,
|
|
306
|
+
});
|
|
307
|
+
const existingFiles = await prisma_js_1.prisma.file.findMany({
|
|
308
|
+
where: { userId, connectedAccountId: account.id, providerFileId: { in: (response.data.files ?? []).map((f) => f.id).filter(Boolean) } },
|
|
309
|
+
include: { folder: { select: { id: true, name: true } } },
|
|
310
|
+
});
|
|
311
|
+
const fileByProviderId = new Map(existingFiles.map((f) => [f.providerFileId, f]));
|
|
312
|
+
return (response.data.files ?? []).map((f) => {
|
|
313
|
+
const dbFile = f.id ? fileByProviderId.get(f.id) : null;
|
|
314
|
+
return {
|
|
315
|
+
id: dbFile?.id ?? f.id ?? '',
|
|
316
|
+
providerFileId: f.id ?? '',
|
|
317
|
+
name: f.name ?? 'Untitled',
|
|
318
|
+
mimeType: f.mimeType ?? 'application/octet-stream',
|
|
319
|
+
sizeBytes: String(f.size ?? '0'),
|
|
320
|
+
createdAt: f.createdTime ?? new Date().toISOString(),
|
|
321
|
+
updatedAt: f.modifiedTime ?? f.createdTime ?? new Date().toISOString(),
|
|
322
|
+
viewedAt: f.viewedByMeTime ?? f.modifiedTime ?? f.createdTime,
|
|
323
|
+
folderId: dbFile?.folderId ?? null,
|
|
324
|
+
folder: dbFile?.folder ?? null,
|
|
325
|
+
connectedAccount: { id: account.id, email: account.email, provider: 'google_drive' },
|
|
326
|
+
driveUrl: f.webViewLink ?? (f.id ? `https://drive.google.com/file/d/${f.id}/view?usp=sharing` : null),
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async function getGoogleSharedFiles(accountId, userId) {
|
|
331
|
+
const account = await prisma_js_1.prisma.connectedAccount.findFirstOrThrow({
|
|
332
|
+
where: { id: accountId, userId, provider: 'google_drive', status: 'connected' },
|
|
333
|
+
});
|
|
334
|
+
const auth = await getAuthedGoogleClient(account);
|
|
335
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
336
|
+
const response = await drive.files.list({
|
|
337
|
+
q: 'sharedWithMe = true and trashed = false',
|
|
338
|
+
orderBy: 'sharedWithMeTime desc',
|
|
339
|
+
spaces: 'drive',
|
|
340
|
+
fields: 'files(id,name,mimeType,size,parents,createdTime,modifiedTime,sharedWithMeTime,sharingUser(displayName,emailAddress,photoLink),owners(displayName,emailAddress,photoLink),webViewLink,iconLink)',
|
|
341
|
+
pageSize: 100,
|
|
342
|
+
supportsAllDrives: true,
|
|
343
|
+
includeItemsFromAllDrives: true,
|
|
344
|
+
});
|
|
345
|
+
const rawItems = response.data.files ?? [];
|
|
346
|
+
const rawFolders = rawItems.filter((f) => f.mimeType === googleDriveFolderMimeType);
|
|
347
|
+
const rawFiles = rawItems.filter((f) => f.mimeType !== googleDriveFolderMimeType);
|
|
348
|
+
// Find or create DB records for shared folders
|
|
349
|
+
const folderByProviderId = new Map();
|
|
350
|
+
for (const rf of rawFolders) {
|
|
351
|
+
if (!rf.id || !rf.name)
|
|
352
|
+
continue;
|
|
353
|
+
let dbFolder = await prisma_js_1.prisma.folder.findFirst({
|
|
354
|
+
where: { userId, connectedAccountId: account.id, providerFolderId: rf.id },
|
|
355
|
+
});
|
|
356
|
+
if (!dbFolder) {
|
|
357
|
+
dbFolder = await prisma_js_1.prisma.folder.create({
|
|
358
|
+
data: {
|
|
359
|
+
userId,
|
|
360
|
+
connectedAccountId: account.id,
|
|
361
|
+
provider: 'google_drive',
|
|
362
|
+
providerFolderId: rf.id,
|
|
363
|
+
name: rf.name,
|
|
364
|
+
color: 'text-blue-500',
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
folderByProviderId.set(rf.id, dbFolder);
|
|
369
|
+
}
|
|
370
|
+
// Find or create DB records for shared files (needed for preview & download streaming)
|
|
371
|
+
const fileByProviderId = new Map();
|
|
372
|
+
for (const rf of rawFiles) {
|
|
373
|
+
if (!rf.id || !rf.name)
|
|
374
|
+
continue;
|
|
375
|
+
let dbFile = await prisma_js_1.prisma.file.findFirst({
|
|
376
|
+
where: { userId, connectedAccountId: account.id, providerFileId: rf.id },
|
|
377
|
+
include: { folder: { select: { id: true, name: true } } },
|
|
378
|
+
});
|
|
379
|
+
if (!dbFile) {
|
|
380
|
+
dbFile = await prisma_js_1.prisma.file.create({
|
|
381
|
+
data: {
|
|
382
|
+
userId,
|
|
383
|
+
connectedAccountId: account.id,
|
|
384
|
+
provider: 'google_drive',
|
|
385
|
+
providerFileId: rf.id,
|
|
386
|
+
name: rf.name,
|
|
387
|
+
mimeType: rf.mimeType ?? 'application/octet-stream',
|
|
388
|
+
sizeBytes: BigInt(rf.size ?? 0),
|
|
389
|
+
status: 'active',
|
|
390
|
+
},
|
|
391
|
+
include: { folder: { select: { id: true, name: true } } },
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
fileByProviderId.set(rf.id, dbFile);
|
|
395
|
+
}
|
|
396
|
+
const folders = rawFolders.map((f) => {
|
|
397
|
+
const dbFolder = f.id ? folderByProviderId.get(f.id) : null;
|
|
398
|
+
const ownerName = f.sharingUser?.displayName ||
|
|
399
|
+
f.sharingUser?.emailAddress ||
|
|
400
|
+
f.owners?.[0]?.displayName ||
|
|
401
|
+
f.owners?.[0]?.emailAddress ||
|
|
402
|
+
'Shared';
|
|
403
|
+
return {
|
|
404
|
+
id: dbFolder?.id ?? f.id ?? '',
|
|
405
|
+
name: f.name ?? 'Untitled Folder',
|
|
406
|
+
updated: f.sharedWithMeTime ?? f.modifiedTime ?? f.createdTime ?? new Date().toISOString(),
|
|
407
|
+
color: 'text-blue-500',
|
|
408
|
+
iconUrl: f.iconLink ?? null,
|
|
409
|
+
providerFolderId: f.id ?? null,
|
|
410
|
+
driveUrl: f.webViewLink ?? (f.id ? `https://drive.google.com/drive/folders/${f.id}` : undefined),
|
|
411
|
+
owner: ownerName,
|
|
412
|
+
connectedAccount: { id: account.id, email: account.email, provider: 'google_drive' },
|
|
413
|
+
};
|
|
414
|
+
});
|
|
415
|
+
const files = rawFiles.map((f) => {
|
|
416
|
+
const dbFile = f.id ? fileByProviderId.get(f.id) : null;
|
|
417
|
+
const ownerName = f.sharingUser?.displayName ||
|
|
418
|
+
f.sharingUser?.emailAddress ||
|
|
419
|
+
f.owners?.[0]?.displayName ||
|
|
420
|
+
f.owners?.[0]?.emailAddress ||
|
|
421
|
+
'Shared';
|
|
422
|
+
return {
|
|
423
|
+
id: dbFile?.id ?? f.id ?? '',
|
|
424
|
+
providerFileId: f.id ?? '',
|
|
425
|
+
name: f.name ?? 'Untitled',
|
|
426
|
+
mimeType: f.mimeType ?? 'application/octet-stream',
|
|
427
|
+
sizeBytes: String(f.size ?? '0'),
|
|
428
|
+
createdAt: f.createdTime ?? new Date().toISOString(),
|
|
429
|
+
updatedAt: f.modifiedTime ?? f.createdTime ?? new Date().toISOString(),
|
|
430
|
+
sharedWithMeTime: f.sharedWithMeTime ?? f.modifiedTime ?? f.createdTime ?? new Date().toISOString(),
|
|
431
|
+
owner: ownerName,
|
|
432
|
+
folderId: dbFile?.folderId ?? null,
|
|
433
|
+
folder: dbFile?.folder ?? null,
|
|
434
|
+
connectedAccount: { id: account.id, email: account.email, provider: 'google_drive' },
|
|
435
|
+
driveUrl: f.webViewLink ?? (f.id ? `https://drive.google.com/file/d/${f.id}/view?usp=sharing` : null),
|
|
436
|
+
};
|
|
437
|
+
});
|
|
438
|
+
return { files, folders };
|
|
439
|
+
}
|
|
440
|
+
async function setGoogleFileStarred(accountId, userId, providerFileId, starred) {
|
|
441
|
+
const account = await prisma_js_1.prisma.connectedAccount.findFirstOrThrow({ where: { id: accountId, userId, provider: 'google_drive', status: 'connected' } });
|
|
442
|
+
const auth = await getAuthedGoogleClient(account);
|
|
443
|
+
const drive = googleapis_1.google.drive({ version: 'v3', auth });
|
|
444
|
+
await drive.files.update({
|
|
445
|
+
fileId: providerFileId,
|
|
446
|
+
requestBody: { starred },
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
async function findInheritedPermissionOrigin(drive, startingFileOrFolderId) {
|
|
450
|
+
let currentId = startingFileOrFolderId;
|
|
451
|
+
const visited = new Set();
|
|
452
|
+
while (currentId && !visited.has(currentId)) {
|
|
453
|
+
visited.add(currentId);
|
|
454
|
+
try {
|
|
455
|
+
const fileRes = await drive.files.get({
|
|
456
|
+
fileId: currentId,
|
|
457
|
+
fields: 'id, name, parents, permissions(id, type, role, permissionDetails)',
|
|
458
|
+
supportsAllDrives: true,
|
|
459
|
+
});
|
|
460
|
+
const permissions = fileRes.data?.permissions || [];
|
|
461
|
+
const anyonePerm = permissions.find((p) => p.type === 'anyone' || p.id === 'anyoneWithLink');
|
|
462
|
+
if (anyonePerm) {
|
|
463
|
+
const isInherited = anyonePerm.permissionDetails?.some((d) => d.inherited === true);
|
|
464
|
+
if (!isInherited) {
|
|
465
|
+
return {
|
|
466
|
+
id: fileRes.data.id,
|
|
467
|
+
name: fileRes.data.name || 'Folder Induk',
|
|
468
|
+
permissionId: anyonePerm.id || 'anyoneWithLink',
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
const parents = fileRes.data?.parents;
|
|
473
|
+
if (parents && parents.length > 0) {
|
|
474
|
+
currentId = parents[0];
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch {
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inviteRouter = void 0;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const prisma_js_1 = require("../../config/prisma.js");
|
|
7
|
+
const auth_middleware_js_1 = require("../../middleware/auth.middleware.js");
|
|
8
|
+
exports.inviteRouter = (0, express_1.Router)();
|
|
9
|
+
exports.inviteRouter.use(auth_middleware_js_1.requireAuth);
|
|
10
|
+
const inviteSchema = zod_1.z.object({ email: zod_1.z.string().email(), role: zod_1.z.enum(['viewer', 'editor']).default('viewer'), targetType: zod_1.z.enum(['file', 'folder']), targetId: zod_1.z.string().min(1) });
|
|
11
|
+
async function assertTargetOwner(userId, targetType, targetId) {
|
|
12
|
+
if (targetType === 'file') {
|
|
13
|
+
const file = await prisma_js_1.prisma.file.findFirst({
|
|
14
|
+
where: {
|
|
15
|
+
OR: [
|
|
16
|
+
{ id: targetId, userId },
|
|
17
|
+
{ providerFileId: targetId, userId },
|
|
18
|
+
{ id: targetId },
|
|
19
|
+
{ providerFileId: targetId },
|
|
20
|
+
],
|
|
21
|
+
status: 'active',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
if (!file)
|
|
25
|
+
throw new Error('File not found');
|
|
26
|
+
return file;
|
|
27
|
+
}
|
|
28
|
+
const folder = await prisma_js_1.prisma.folder.findFirst({
|
|
29
|
+
where: {
|
|
30
|
+
OR: [
|
|
31
|
+
{ id: targetId, userId },
|
|
32
|
+
{ providerFolderId: targetId, userId },
|
|
33
|
+
{ id: targetId },
|
|
34
|
+
{ providerFolderId: targetId },
|
|
35
|
+
],
|
|
36
|
+
deletedAt: null,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
if (!folder)
|
|
40
|
+
throw new Error('Folder not found');
|
|
41
|
+
return folder;
|
|
42
|
+
}
|
|
43
|
+
async function resolveTargets(invites) {
|
|
44
|
+
const fileIds = invites.filter((invite) => invite.targetType === 'file').map((invite) => invite.targetId);
|
|
45
|
+
const folderIds = invites.filter((invite) => invite.targetType === 'folder').map((invite) => invite.targetId);
|
|
46
|
+
const [files, folders] = await Promise.all([
|
|
47
|
+
prisma_js_1.prisma.file.findMany({ where: { id: { in: fileIds }, status: 'active' }, select: { id: true, name: true, mimeType: true, sizeBytes: true, folderId: true } }),
|
|
48
|
+
prisma_js_1.prisma.folder.findMany({ where: { id: { in: folderIds }, deletedAt: null }, select: { id: true, name: true } }),
|
|
49
|
+
]);
|
|
50
|
+
const targets = new Map();
|
|
51
|
+
for (const file of files)
|
|
52
|
+
targets.set(`file:${file.id}`, { id: file.id, name: file.name, type: 'file', mimeType: file.mimeType, sizeBytes: file.sizeBytes.toString(), folderId: file.folderId });
|
|
53
|
+
for (const folder of folders)
|
|
54
|
+
targets.set(`folder:${folder.id}`, { id: folder.id, name: folder.name, type: 'folder' });
|
|
55
|
+
return targets;
|
|
56
|
+
}
|
|
57
|
+
function serializeInvite(invite, target, user) {
|
|
58
|
+
return {
|
|
59
|
+
id: invite.id,
|
|
60
|
+
email: invite.inviteeEmail,
|
|
61
|
+
role: invite.role,
|
|
62
|
+
status: invite.status,
|
|
63
|
+
targetType: invite.targetType,
|
|
64
|
+
targetId: invite.targetId,
|
|
65
|
+
target,
|
|
66
|
+
revokedAt: invite.revokedAt?.toISOString() ?? null,
|
|
67
|
+
acceptedAt: invite.acceptedAt?.toISOString() ?? null,
|
|
68
|
+
createdAt: invite.createdAt.toISOString(),
|
|
69
|
+
updatedAt: invite.updatedAt.toISOString(),
|
|
70
|
+
user: user ?? null,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
exports.inviteRouter.get('/', async (req, res, next) => {
|
|
74
|
+
try {
|
|
75
|
+
const me = await prisma_js_1.prisma.user.findUniqueOrThrow({ where: { id: req.user.id }, select: { email: true } });
|
|
76
|
+
const [sent, received] = await Promise.all([
|
|
77
|
+
prisma_js_1.prisma.workspaceInvite.findMany({ where: { inviterId: req.user.id, revokedAt: null, targetId: { not: '' } }, orderBy: { createdAt: 'desc' } }),
|
|
78
|
+
prisma_js_1.prisma.workspaceInvite.findMany({ where: { inviteeEmail: me.email, revokedAt: null, targetId: { not: '' } }, orderBy: { createdAt: 'desc' } }),
|
|
79
|
+
]);
|
|
80
|
+
const allInvites = [...sent, ...received];
|
|
81
|
+
const emails = [...new Set(sent.map((invite) => invite.inviteeEmail))];
|
|
82
|
+
const users = await prisma_js_1.prisma.user.findMany({ where: { email: { in: emails } }, select: { id: true, name: true, email: true } });
|
|
83
|
+
const userByEmail = new Map(users.map((user) => [user.email, user]));
|
|
84
|
+
const acceptedInvites = sent.filter((invite) => invite.status === 'pending' && userByEmail.has(invite.inviteeEmail));
|
|
85
|
+
if (acceptedInvites.length > 0)
|
|
86
|
+
await prisma_js_1.prisma.workspaceInvite.updateMany({ where: { id: { in: acceptedInvites.map((invite) => invite.id) } }, data: { status: 'accepted', acceptedAt: new Date() } });
|
|
87
|
+
const targetByKey = await resolveTargets(allInvites);
|
|
88
|
+
const sentInvites = sent.map((invite) => serializeInvite({ ...invite, status: userByEmail.has(invite.inviteeEmail) ? 'accepted' : invite.status, acceptedAt: userByEmail.has(invite.inviteeEmail) ? invite.acceptedAt ?? new Date() : invite.acceptedAt }, targetByKey.get(`${invite.targetType}:${invite.targetId}`) ?? null, userByEmail.get(invite.inviteeEmail)));
|
|
89
|
+
const receivedInvites = received.map((invite) => serializeInvite(invite, targetByKey.get(`${invite.targetType}:${invite.targetId}`) ?? null));
|
|
90
|
+
return res.json({ sent: sentInvites, received: receivedInvites, invites: sentInvites });
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
return next(error);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
exports.inviteRouter.post('/', async (req, res, next) => {
|
|
97
|
+
try {
|
|
98
|
+
const body = inviteSchema.parse(req.body);
|
|
99
|
+
const email = body.email.trim().toLowerCase();
|
|
100
|
+
const inviter = await prisma_js_1.prisma.user.findUniqueOrThrow({ where: { id: req.user.id }, select: { email: true } });
|
|
101
|
+
if (email === inviter.email)
|
|
102
|
+
return res.status(400).json({ code: 'INVITE_SELF_NOT_ALLOWED', message: 'You cannot invite yourself.' });
|
|
103
|
+
await assertTargetOwner(req.user.id, body.targetType, body.targetId);
|
|
104
|
+
const existingUser = await prisma_js_1.prisma.user.findUnique({ where: { email }, select: { id: true, name: true, email: true } });
|
|
105
|
+
const invite = await prisma_js_1.prisma.workspaceInvite.upsert({
|
|
106
|
+
where: { inviterId_inviteeEmail_targetType_targetId: { inviterId: req.user.id, inviteeEmail: email, targetType: body.targetType, targetId: body.targetId } },
|
|
107
|
+
create: { inviterId: req.user.id, inviteeEmail: email, role: body.role, targetType: body.targetType, targetId: body.targetId, status: existingUser ? 'accepted' : 'pending', acceptedAt: existingUser ? new Date() : null },
|
|
108
|
+
update: { role: body.role, status: existingUser ? 'accepted' : 'pending', acceptedAt: existingUser ? new Date() : null, revokedAt: null },
|
|
109
|
+
});
|
|
110
|
+
const targetByKey = await resolveTargets([invite]);
|
|
111
|
+
return res.status(201).json({ invite: serializeInvite(invite, targetByKey.get(`${invite.targetType}:${invite.targetId}`) ?? null, existingUser) });
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
return next(error);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
exports.inviteRouter.delete('/:id', async (req, res, next) => {
|
|
118
|
+
try {
|
|
119
|
+
const result = await prisma_js_1.prisma.workspaceInvite.updateMany({ where: { id: String(req.params.id), inviterId: req.user.id, revokedAt: null }, data: { status: 'revoked', revokedAt: new Date() } });
|
|
120
|
+
if (result.count === 0)
|
|
121
|
+
return res.status(404).json({ code: 'INVITE_NOT_FOUND', message: 'Invite not found.' });
|
|
122
|
+
return res.json({ status: 'ok' });
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
return next(error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.providerConfigRouter = void 0;
|
|
4
|
+
const express_1 = require("express");
|
|
5
|
+
const zod_1 = require("zod");
|
|
6
|
+
const prisma_js_1 = require("../../config/prisma.js");
|
|
7
|
+
const auth_middleware_js_1 = require("../../middleware/auth.middleware.js");
|
|
8
|
+
const crypto_js_1 = require("../../utils/crypto.js");
|
|
9
|
+
exports.providerConfigRouter = (0, express_1.Router)();
|
|
10
|
+
exports.providerConfigRouter.use(auth_middleware_js_1.requireAuth);
|
|
11
|
+
const schema = zod_1.z.object({
|
|
12
|
+
clientId: zod_1.z.string().min(1),
|
|
13
|
+
clientSecret: zod_1.z.string().min(1),
|
|
14
|
+
redirectUri: zod_1.z.string().url(),
|
|
15
|
+
scopes: zod_1.z.array(zod_1.z.string()).min(1),
|
|
16
|
+
});
|
|
17
|
+
exports.providerConfigRouter.post('/google', async (req, res, next) => {
|
|
18
|
+
try {
|
|
19
|
+
const body = schema.parse(req.body);
|
|
20
|
+
const config = await prisma_js_1.prisma.providerConfig.create({
|
|
21
|
+
data: {
|
|
22
|
+
userId: req.user.id,
|
|
23
|
+
provider: 'google_drive',
|
|
24
|
+
clientIdEncrypted: (0, crypto_js_1.encryptText)(body.clientId),
|
|
25
|
+
clientSecretEncrypted: (0, crypto_js_1.encryptText)(body.clientSecret),
|
|
26
|
+
redirectUri: body.redirectUri,
|
|
27
|
+
scopes: body.scopes,
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
return res.status(201).json({ id: config.id, provider: config.provider, redirectUri: config.redirectUri, scopes: config.scopes, status: config.status });
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return next(error);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
exports.providerConfigRouter.get('/', async (req, res, next) => {
|
|
37
|
+
try {
|
|
38
|
+
const configs = await prisma_js_1.prisma.providerConfig.findMany({ where: { userId: req.user.id }, select: { id: true, provider: true, redirectUri: true, scopes: true, status: true, createdAt: true } });
|
|
39
|
+
return res.json({ configs });
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
return next(error);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
exports.providerConfigRouter.delete('/:id', async (req, res, next) => {
|
|
46
|
+
try {
|
|
47
|
+
await prisma_js_1.prisma.providerConfig.deleteMany({ where: { id: String(req.params.id), userId: req.user.id } });
|
|
48
|
+
return res.json({ status: 'ok' });
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
return next(error);
|
|
52
|
+
}
|
|
53
|
+
});
|