@muhgholy/next-drive 3.8.0 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -0
- package/dist/{chunk-RQ3YRDNR.js → chunk-32UNO3KE.js} +107 -53
- package/dist/chunk-32UNO3KE.js.map +1 -0
- package/dist/{chunk-LMM5IU5U.cjs → chunk-UCSCXKC2.cjs} +107 -53
- package/dist/chunk-UCSCXKC2.cjs.map +1 -0
- package/dist/client/index.cjs +0 -1
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.js +0 -1
- package/dist/client/index.js.map +1 -1
- package/dist/schemas.cjs +0 -1
- package/dist/schemas.js +0 -1
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/controllers/drive.d.ts.map +1 -1
- package/dist/server/express.cjs +11 -12
- package/dist/server/express.cjs.map +1 -1
- package/dist/server/express.js +2 -3
- package/dist/server/express.js.map +1 -1
- package/dist/server/index.cjs +13 -14
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -2
- package/dist/server/providers/google.d.ts.map +1 -1
- package/dist/server/providers/local.d.ts.map +1 -1
- package/dist/types/server/config.d.ts +14 -3
- package/dist/types/server/config.d.ts.map +1 -1
- package/dist/types/server/express.d.ts +40 -15
- package/dist/types/server/express.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-DGUM43GV.js +0 -10
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-JEQ2X3Z6.cjs +0 -12
- package/dist/chunk-JEQ2X3Z6.cjs.map +0 -1
- package/dist/chunk-LMM5IU5U.cjs.map +0 -1
- package/dist/chunk-RQ3YRDNR.js.map +0 -1
package/README.md
CHANGED
|
@@ -28,6 +28,46 @@ npm install @muhgholy/next-drive
|
|
|
28
28
|
| React | >= 18 |
|
|
29
29
|
| Mongoose | >= 7 |
|
|
30
30
|
| Tailwind CSS | >= 3 |
|
|
31
|
+
| TypeScript | >= 5 |
|
|
32
|
+
|
|
33
|
+
**TypeScript Configuration:**
|
|
34
|
+
|
|
35
|
+
This package uses [subpath exports](https://nodejs.org/api/packages.html#subpath-exports). Configure your `tsconfig.json` based on your project type:
|
|
36
|
+
|
|
37
|
+
**For Next.js (App Router or Pages Router):**
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"compilerOptions": {
|
|
42
|
+
"module": "esnext",
|
|
43
|
+
"moduleResolution": "bundler"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**For Node.js/Express servers:**
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{
|
|
52
|
+
"compilerOptions": {
|
|
53
|
+
"module": "nodenext",
|
|
54
|
+
"moduleResolution": "nodenext"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**For projects using bundlers (Vite, Webpack, etc.):**
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"compilerOptions": {
|
|
64
|
+
"module": "esnext",
|
|
65
|
+
"moduleResolution": "bundler"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> ⚠️ The legacy `"moduleResolution": "node"` is **not supported** and will cause build errors with subpath imports like `@muhgholy/next-drive/server`.
|
|
31
71
|
|
|
32
72
|
**FFmpeg** (for video thumbnails):
|
|
33
73
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { __require } from './chunk-DGUM43GV.js';
|
|
2
1
|
import formidable from 'formidable';
|
|
3
2
|
import path3 from 'path';
|
|
4
3
|
import fs4 from 'fs';
|
|
@@ -10,30 +9,41 @@ import { z } from 'zod';
|
|
|
10
9
|
import ffmpeg from 'fluent-ffmpeg';
|
|
11
10
|
import { google } from 'googleapis';
|
|
12
11
|
|
|
12
|
+
// src/server/index.ts
|
|
13
13
|
var globalConfig = null;
|
|
14
14
|
var driveConfiguration = (config) => {
|
|
15
15
|
if (mongoose2.connection.readyState !== 1) {
|
|
16
16
|
throw new Error("Database not connected. Please connect to Mongoose before initializing next-drive.");
|
|
17
17
|
}
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
const mode = config.mode || "NORMAL";
|
|
19
|
+
if (mode === "ROOT") {
|
|
20
|
+
globalConfig = {
|
|
21
|
+
...config,
|
|
22
|
+
mode: "ROOT",
|
|
23
|
+
security: config.security || {
|
|
24
|
+
maxUploadSizeInBytes: 1024 * 1024 * 1024 * 10,
|
|
25
|
+
// 10GB default for ROOT
|
|
26
|
+
allowedMimeTypes: ["*/*"]
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
return globalConfig;
|
|
30
|
+
} else {
|
|
31
|
+
if (!config.information) {
|
|
32
|
+
throw new Error("information callback is required in NORMAL mode");
|
|
33
|
+
}
|
|
34
|
+
globalConfig = {
|
|
35
|
+
...config,
|
|
36
|
+
mode: "NORMAL",
|
|
37
|
+
security: {
|
|
38
|
+
maxUploadSizeInBytes: config.security?.maxUploadSizeInBytes ?? 10 * 1024 * 1024,
|
|
39
|
+
allowedMimeTypes: config.security?.allowedMimeTypes ?? ["*/*"],
|
|
40
|
+
signedUrls: config.security?.signedUrls,
|
|
41
|
+
trash: config.security?.trash
|
|
42
|
+
},
|
|
43
|
+
information: config.information
|
|
44
|
+
};
|
|
45
|
+
return globalConfig;
|
|
46
|
+
}
|
|
37
47
|
};
|
|
38
48
|
var getDriveConfig = () => {
|
|
39
49
|
if (!globalConfig) throw new Error("Drive configuration not initialized");
|
|
@@ -41,6 +51,16 @@ var getDriveConfig = () => {
|
|
|
41
51
|
};
|
|
42
52
|
var getDriveInformation = async (req) => {
|
|
43
53
|
const config = getDriveConfig();
|
|
54
|
+
if (config.mode === "ROOT") {
|
|
55
|
+
if (!config.information) {
|
|
56
|
+
return {
|
|
57
|
+
key: null,
|
|
58
|
+
storage: { quotaInBytes: Number.MAX_SAFE_INTEGER }
|
|
59
|
+
// Unlimited quota in ROOT mode
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return config.information(req);
|
|
63
|
+
}
|
|
44
64
|
return config.information(req);
|
|
45
65
|
};
|
|
46
66
|
var informationSchema = new Schema({
|
|
@@ -252,19 +272,25 @@ var LocalStorageProvider = {
|
|
|
252
272
|
search: async (query, owner, accountId) => {
|
|
253
273
|
},
|
|
254
274
|
getQuota: async (owner, accountId, configuredQuotaInBytes) => {
|
|
275
|
+
const config = getDriveConfig();
|
|
276
|
+
const isRootMode = config.mode === "ROOT";
|
|
277
|
+
const match = {
|
|
278
|
+
"information.type": "FILE",
|
|
279
|
+
trashedAt: null,
|
|
280
|
+
"provider.type": "LOCAL",
|
|
281
|
+
storageAccountId: accountId || null
|
|
282
|
+
};
|
|
283
|
+
if (!isRootMode) {
|
|
284
|
+
match.owner = owner;
|
|
285
|
+
}
|
|
255
286
|
const result = await drive_default.aggregate([
|
|
256
|
-
{
|
|
257
|
-
$match: {
|
|
258
|
-
owner,
|
|
259
|
-
"information.type": "FILE",
|
|
260
|
-
trashedAt: null,
|
|
261
|
-
"provider.type": "LOCAL",
|
|
262
|
-
storageAccountId: accountId || null
|
|
263
|
-
}
|
|
264
|
-
},
|
|
287
|
+
{ $match: match },
|
|
265
288
|
{ $group: { _id: null, total: { $sum: "$information.sizeInBytes" } } }
|
|
266
289
|
]);
|
|
267
290
|
const usedInBytes = result[0]?.total || 0;
|
|
291
|
+
if (isRootMode) {
|
|
292
|
+
return { usedInBytes, quotaInBytes: Number.MAX_SAFE_INTEGER };
|
|
293
|
+
}
|
|
268
294
|
return { usedInBytes, quotaInBytes: configuredQuotaInBytes ?? 0 };
|
|
269
295
|
},
|
|
270
296
|
openStream: async (item, accountId) => {
|
|
@@ -606,6 +632,23 @@ var GoogleDriveProvider = {
|
|
|
606
632
|
}
|
|
607
633
|
},
|
|
608
634
|
getQuota: async (owner, accountId, _configuredQuotaInBytes) => {
|
|
635
|
+
const config = getDriveConfig();
|
|
636
|
+
const isRootMode = config.mode === "ROOT";
|
|
637
|
+
if (isRootMode) {
|
|
638
|
+
const result = await drive_default.aggregate([
|
|
639
|
+
{
|
|
640
|
+
$match: {
|
|
641
|
+
"information.type": "FILE",
|
|
642
|
+
trashedAt: null,
|
|
643
|
+
"provider.type": "GOOGLE",
|
|
644
|
+
storageAccountId: accountId || null
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
{ $group: { _id: null, total: { $sum: "$information.sizeInBytes" } } }
|
|
648
|
+
]);
|
|
649
|
+
const usedInBytes = result[0]?.total || 0;
|
|
650
|
+
return { usedInBytes, quotaInBytes: Number.MAX_SAFE_INTEGER };
|
|
651
|
+
}
|
|
609
652
|
try {
|
|
610
653
|
const { client } = await createAuthClient(owner, accountId);
|
|
611
654
|
const drive = google.drive({ version: "v3", auth: client });
|
|
@@ -832,7 +875,7 @@ var getNextOrderValue = async (owner) => {
|
|
|
832
875
|
};
|
|
833
876
|
var driveGetUrl = (fileId, options) => {
|
|
834
877
|
const config = getDriveConfig();
|
|
835
|
-
if (!config.security
|
|
878
|
+
if (!config.security?.signedUrls?.enabled) {
|
|
836
879
|
return `/api/drive?action=serve&id=${fileId}`;
|
|
837
880
|
}
|
|
838
881
|
const { secret, expiresIn } = config.security.signedUrls;
|
|
@@ -1119,13 +1162,14 @@ var driveUpload = async (source, key, options) => {
|
|
|
1119
1162
|
};
|
|
1120
1163
|
mimeType = mimeTypes[ext] || "application/octet-stream";
|
|
1121
1164
|
}
|
|
1122
|
-
if (!validateMimeType(mimeType, config.security.allowedMimeTypes)) {
|
|
1165
|
+
if (config.security && !validateMimeType(mimeType, config.security.allowedMimeTypes)) {
|
|
1123
1166
|
throw new Error(`File type ${mimeType} not allowed`);
|
|
1124
1167
|
}
|
|
1125
|
-
if (fileSize > config.security.maxUploadSizeInBytes) {
|
|
1168
|
+
if (config.security && fileSize > config.security.maxUploadSizeInBytes) {
|
|
1126
1169
|
throw new Error(`File size ${fileSize} exceeds maximum allowed size ${config.security.maxUploadSizeInBytes}`);
|
|
1127
1170
|
}
|
|
1128
|
-
|
|
1171
|
+
const isRootMode = config.mode === "ROOT";
|
|
1172
|
+
if (!options.enforce && !isRootMode) {
|
|
1129
1173
|
const quota = await provider.getQuota(key, accountId, void 0);
|
|
1130
1174
|
if (quota.usedInBytes + fileSize > quota.quotaInBytes) {
|
|
1131
1175
|
throw new Error("Storage quota exceeded");
|
|
@@ -1251,7 +1295,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1251
1295
|
}
|
|
1252
1296
|
const drive = await drive_default.findById(id);
|
|
1253
1297
|
if (!drive) return res.status(404).json({ status: 404, message: "File not found" });
|
|
1254
|
-
if (config.security
|
|
1298
|
+
if (config.security?.signedUrls?.enabled) {
|
|
1255
1299
|
if (!token || typeof token !== "string") {
|
|
1256
1300
|
return res.status(401).json({ status: 401, message: "Missing or invalid token" });
|
|
1257
1301
|
}
|
|
@@ -1303,9 +1347,11 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1303
1347
|
}
|
|
1304
1348
|
}
|
|
1305
1349
|
try {
|
|
1350
|
+
const mode = config.mode || "NORMAL";
|
|
1306
1351
|
const information = await getDriveInformation(req);
|
|
1307
1352
|
const { key: owner } = information;
|
|
1308
1353
|
const STORAGE_PATH = config.storage.path;
|
|
1354
|
+
const isRootMode = mode === "ROOT";
|
|
1309
1355
|
if (action === "information") {
|
|
1310
1356
|
const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
|
|
1311
1357
|
const googleConfigured = !!(clientId && clientSecret && redirectUri);
|
|
@@ -1315,7 +1361,8 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1315
1361
|
data: {
|
|
1316
1362
|
providers: {
|
|
1317
1363
|
google: googleConfigured
|
|
1318
|
-
}
|
|
1364
|
+
},
|
|
1365
|
+
mode
|
|
1319
1366
|
}
|
|
1320
1367
|
});
|
|
1321
1368
|
}
|
|
@@ -1326,10 +1373,9 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1326
1373
|
if (provider2 === "GOOGLE") {
|
|
1327
1374
|
const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
|
|
1328
1375
|
if (!clientId || !clientSecret || !redirectUri) return res.status(500).json({ status: 500, message: "Google not configured" });
|
|
1329
|
-
const { google: google2 } = __require("googleapis");
|
|
1330
1376
|
const callbackUri = new URL(redirectUri);
|
|
1331
1377
|
callbackUri.searchParams.set("action", "callback");
|
|
1332
|
-
const oAuth2Client = new
|
|
1378
|
+
const oAuth2Client = new google.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
|
|
1333
1379
|
const state = Buffer.from(JSON.stringify({ owner })).toString("base64");
|
|
1334
1380
|
const url = oAuth2Client.generateAuthUrl({
|
|
1335
1381
|
access_type: "offline",
|
|
@@ -1347,13 +1393,12 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1347
1393
|
if (!code) return res.status(400).json({ status: 400, message: "Missing code" });
|
|
1348
1394
|
const { clientId, clientSecret, redirectUri } = config.storage?.google || {};
|
|
1349
1395
|
if (!clientId || !clientSecret || !redirectUri) return res.status(500).json({ status: 500, message: "Google not configured" });
|
|
1350
|
-
const { google: google2 } = __require("googleapis");
|
|
1351
1396
|
const callbackUri = new URL(redirectUri);
|
|
1352
1397
|
callbackUri.searchParams.set("action", "callback");
|
|
1353
|
-
const oAuth2Client = new
|
|
1398
|
+
const oAuth2Client = new google.auth.OAuth2(clientId, clientSecret, callbackUri.toString());
|
|
1354
1399
|
const { tokens } = await oAuth2Client.getToken(code);
|
|
1355
1400
|
oAuth2Client.setCredentials(tokens);
|
|
1356
|
-
const oauth2 =
|
|
1401
|
+
const oauth2 = google.oauth2({ version: "v2", auth: oAuth2Client });
|
|
1357
1402
|
const userInfo = await oauth2.userinfo.get();
|
|
1358
1403
|
const existing = await account_default.findOne({ owner, "metadata.google.email": userInfo.data.email, "metadata.provider": "GOOGLE" });
|
|
1359
1404
|
if (existing) {
|
|
@@ -1361,7 +1406,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1361
1406
|
existing.markModified("metadata");
|
|
1362
1407
|
await existing.save();
|
|
1363
1408
|
} else {
|
|
1364
|
-
|
|
1409
|
+
const newAccount = new account_default({
|
|
1365
1410
|
owner,
|
|
1366
1411
|
name: userInfo.data.name || "Google Drive",
|
|
1367
1412
|
metadata: {
|
|
@@ -1372,6 +1417,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1372
1417
|
}
|
|
1373
1418
|
}
|
|
1374
1419
|
});
|
|
1420
|
+
await newAccount.save();
|
|
1375
1421
|
}
|
|
1376
1422
|
res.setHeader("Content-Type", "text/html");
|
|
1377
1423
|
return res.send(`<!DOCTYPE html>
|
|
@@ -1449,12 +1495,14 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1449
1495
|
console.error("Sync failed", e);
|
|
1450
1496
|
}
|
|
1451
1497
|
const query = {
|
|
1452
|
-
owner,
|
|
1453
1498
|
"provider.type": provider.name,
|
|
1454
1499
|
storageAccountId: accountId || null,
|
|
1455
1500
|
parentId: folderId === "root" || !folderId ? null : folderId,
|
|
1456
1501
|
trashedAt: null
|
|
1457
1502
|
};
|
|
1503
|
+
if (!isRootMode) {
|
|
1504
|
+
query.owner = owner;
|
|
1505
|
+
}
|
|
1458
1506
|
if (afterId) query._id = { $lt: afterId };
|
|
1459
1507
|
const items = await drive_default.find(query, {}, { sort: { order: 1, _id: -1 }, limit });
|
|
1460
1508
|
const plainItems = await Promise.all(items.map((item) => item.toClient()));
|
|
@@ -1474,12 +1522,14 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1474
1522
|
}
|
|
1475
1523
|
}
|
|
1476
1524
|
const query = {
|
|
1477
|
-
owner,
|
|
1478
1525
|
"provider.type": provider.name,
|
|
1479
1526
|
storageAccountId: accountId || null,
|
|
1480
1527
|
trashedAt: trashed ? { $ne: null } : null,
|
|
1481
1528
|
name: { $regex: q, $options: "i" }
|
|
1482
1529
|
};
|
|
1530
|
+
if (!isRootMode) {
|
|
1531
|
+
query.owner = owner;
|
|
1532
|
+
}
|
|
1483
1533
|
if (folderId && folderId !== "root") query.parentId = folderId;
|
|
1484
1534
|
const items = await drive_default.find(query, {}, { limit, sort: { createdAt: -1 } });
|
|
1485
1535
|
const plainItems = await Promise.all(items.map((i) => i.toClient()));
|
|
@@ -1492,7 +1542,7 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1492
1542
|
if (!fs4.existsSync(systemTmpDir)) fs4.mkdirSync(systemTmpDir, { recursive: true });
|
|
1493
1543
|
const form = formidable({
|
|
1494
1544
|
multiples: false,
|
|
1495
|
-
maxFileSize: config.security
|
|
1545
|
+
maxFileSize: (config.security?.maxUploadSizeInBytes ?? 1024 * 1024 * 1024) * 2,
|
|
1496
1546
|
uploadDir: systemTmpDir,
|
|
1497
1547
|
keepExtensions: true
|
|
1498
1548
|
});
|
|
@@ -1527,14 +1577,18 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1527
1577
|
const tempBaseDir = path3.join(os2.tmpdir(), "next-drive-uploads");
|
|
1528
1578
|
if (!currentUploadId) {
|
|
1529
1579
|
if (chunkIndex !== 0) return res.status(400).json({ message: "Missing upload ID for non-zero chunk" });
|
|
1530
|
-
if (fileType &&
|
|
1531
|
-
|
|
1532
|
-
|
|
1580
|
+
if (fileType && config.security) {
|
|
1581
|
+
if (!validateMimeType(fileType, config.security.allowedMimeTypes)) {
|
|
1582
|
+
cleanupTempFiles(files);
|
|
1583
|
+
return res.status(400).json({ status: 400, message: `File type ${fileType} not allowed` });
|
|
1584
|
+
}
|
|
1533
1585
|
}
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1586
|
+
if (!isRootMode) {
|
|
1587
|
+
const quota = await provider.getQuota(owner, accountId, information.storage.quotaInBytes);
|
|
1588
|
+
if (quota.usedInBytes + fileSizeInBytes > quota.quotaInBytes) {
|
|
1589
|
+
cleanupTempFiles(files);
|
|
1590
|
+
return res.status(413).json({ status: 413, message: "Storage quota exceeded" });
|
|
1591
|
+
}
|
|
1538
1592
|
}
|
|
1539
1593
|
currentUploadId = crypto2.randomUUID();
|
|
1540
1594
|
const uploadDir = path3.join(tempBaseDir, currentUploadId);
|
|
@@ -1799,5 +1853,5 @@ var driveAPIHandler = async (req, res) => {
|
|
|
1799
1853
|
};
|
|
1800
1854
|
|
|
1801
1855
|
export { driveAPIHandler, driveConfiguration, driveDelete, driveFilePath, driveFileSchemaZod, driveGetUrl, driveInfo, driveList, driveReadFile, driveUpload, getDriveConfig, getDriveInformation };
|
|
1802
|
-
//# sourceMappingURL=chunk-
|
|
1803
|
-
//# sourceMappingURL=chunk-
|
|
1856
|
+
//# sourceMappingURL=chunk-32UNO3KE.js.map
|
|
1857
|
+
//# sourceMappingURL=chunk-32UNO3KE.js.map
|