@muhgholy/next-drive 3.6.0 → 3.7.1

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 CHANGED
@@ -279,18 +279,20 @@ const file = await driveUpload(
279
279
  { userId: "123" },
280
280
  {
281
281
  name: "document.txt",
282
+ mime: "text/plain", // Optional: specify MIME type
282
283
  }
283
284
  );
284
285
  ```
285
286
 
286
287
  **Options:**
287
288
 
288
- | Option | Type | Required | Description |
289
- | ----------- | ---------------- | -------- | ---------------------------------------------- |
290
- | `name` | `string` | Yes | File name with extension |
291
- | `parentId` | `string \| null` | No | Parent folder ID (null or 'root' for root) |
292
- | `accountId` | `string` | No | Storage account ID ('LOCAL' for local storage) |
293
- | `enforce` | `boolean` | No | Bypass quota check (default: false) |
289
+ | Option | Type | Required | Description |
290
+ | ----------- | ---------------- | -------- | -------------------------------------------------------- |
291
+ | `name` | `string` | Yes | File name with extension |
292
+ | `parentId` | `string \| null` | No | Parent folder ID (null or 'root' for root) |
293
+ | `accountId` | `string` | No | Storage account ID ('LOCAL' for local storage) |
294
+ | `mime` | `string` | No | MIME type (auto-detected from extension if not provided) |
295
+ | `enforce` | `boolean` | No | Bypass quota check (default: false) |
294
296
 
295
297
  ### Get Signed URL
296
298
 
@@ -5,8 +5,8 @@ var formidable = require('formidable');
5
5
  var path3 = require('path');
6
6
  var fs4 = require('fs');
7
7
  var os2 = require('os');
8
+ var crypto2 = require('crypto');
8
9
  var mongoose2 = require('mongoose');
9
- var crypto3 = require('crypto');
10
10
  var sharp = require('sharp');
11
11
  var zod = require('zod');
12
12
  var ffmpeg = require('fluent-ffmpeg');
@@ -18,8 +18,8 @@ var formidable__default = /*#__PURE__*/_interopDefault(formidable);
18
18
  var path3__default = /*#__PURE__*/_interopDefault(path3);
19
19
  var fs4__default = /*#__PURE__*/_interopDefault(fs4);
20
20
  var os2__default = /*#__PURE__*/_interopDefault(os2);
21
+ var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
21
22
  var mongoose2__default = /*#__PURE__*/_interopDefault(mongoose2);
22
- var crypto3__default = /*#__PURE__*/_interopDefault(crypto3);
23
23
  var sharp__default = /*#__PURE__*/_interopDefault(sharp);
24
24
  var ffmpeg__default = /*#__PURE__*/_interopDefault(ffmpeg);
25
25
 
@@ -151,7 +151,7 @@ var validateMimeType = (mime, allowedTypes) => {
151
151
  });
152
152
  };
153
153
  var computeFileHash = (filePath) => new Promise((resolve, reject) => {
154
- const hash = crypto3__default.default.createHash("sha256");
154
+ const hash = crypto2__default.default.createHash("sha256");
155
155
  const stream = fs4__default.default.createReadStream(filePath);
156
156
  stream.on("data", (data) => hash.update(data));
157
157
  stream.on("end", () => resolve(hash.digest("hex")));
@@ -194,13 +194,13 @@ var listQuerySchema = zod.z.object({
194
194
  }),
195
195
  afterId: objectIdSchema.optional()
196
196
  });
197
- var serveQuerySchema = zod.z.object({
197
+ zod.z.object({
198
198
  id: objectIdSchema,
199
199
  token: zod.z.string().optional(),
200
200
  q: zod.z.enum(["ultralow", "low", "medium", "high", "normal"]).optional(),
201
201
  format: zod.z.enum(["webp", "jpeg", "png"]).optional()
202
202
  });
203
- var thumbnailQuerySchema = zod.z.object({
203
+ zod.z.object({
204
204
  id: objectIdSchema,
205
205
  size: zod.z.enum(["small", "medium", "large"]).optional().default("medium"),
206
206
  token: zod.z.string().optional()
@@ -857,7 +857,7 @@ var driveGetUrl = (fileId, options) => {
857
857
  } else {
858
858
  expiryTimestamp = Math.floor(Date.now() / 1e3) + expiresIn;
859
859
  }
860
- const signature = crypto3__default.default.createHmac("sha256", secret).update(`${fileId}:${expiryTimestamp}`).digest("hex");
860
+ const signature = crypto2__default.default.createHmac("sha256", secret).update(`${fileId}:${expiryTimestamp}`).digest("hex");
861
861
  const token = Buffer.from(`${expiryTimestamp}:${signature}`).toString("base64url");
862
862
  return `/api/drive?action=serve&id=${fileId}&token=${token}`;
863
863
  };
@@ -1084,7 +1084,7 @@ var driveUpload = async (source, key, options) => {
1084
1084
  if (!fs4__default.default.existsSync(tempDir)) {
1085
1085
  fs4__default.default.mkdirSync(tempDir, { recursive: true });
1086
1086
  }
1087
- tempFilePath = path3__default.default.join(tempDir, `upload-${crypto3__default.default.randomUUID()}.tmp`);
1087
+ tempFilePath = path3__default.default.join(tempDir, `upload-${crypto2__default.default.randomUUID()}.tmp`);
1088
1088
  fs4__default.default.writeFileSync(tempFilePath, source);
1089
1089
  sourceFilePath = tempFilePath;
1090
1090
  fileSize = source.length;
@@ -1093,7 +1093,7 @@ var driveUpload = async (source, key, options) => {
1093
1093
  if (!fs4__default.default.existsSync(tempDir)) {
1094
1094
  fs4__default.default.mkdirSync(tempDir, { recursive: true });
1095
1095
  }
1096
- tempFilePath = path3__default.default.join(tempDir, `upload-${crypto3__default.default.randomUUID()}.tmp`);
1096
+ tempFilePath = path3__default.default.join(tempDir, `upload-${crypto2__default.default.randomUUID()}.tmp`);
1097
1097
  const writeStream = fs4__default.default.createWriteStream(tempFilePath);
1098
1098
  await new Promise((resolve, reject) => {
1099
1099
  source.pipe(writeStream);
@@ -1106,27 +1106,32 @@ var driveUpload = async (source, key, options) => {
1106
1106
  fileSize = stats.size;
1107
1107
  }
1108
1108
  try {
1109
- const ext = path3__default.default.extname(options.name).toLowerCase();
1110
- const mimeTypes = {
1111
- ".jpg": "image/jpeg",
1112
- ".jpeg": "image/jpeg",
1113
- ".png": "image/png",
1114
- ".gif": "image/gif",
1115
- ".webp": "image/webp",
1116
- ".svg": "image/svg+xml",
1117
- ".mp4": "video/mp4",
1118
- ".mov": "video/quicktime",
1119
- ".avi": "video/x-msvideo",
1120
- ".pdf": "application/pdf",
1121
- ".doc": "application/msword",
1122
- ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1123
- ".xls": "application/vnd.ms-excel",
1124
- ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1125
- ".txt": "text/plain",
1126
- ".json": "application/json",
1127
- ".zip": "application/zip"
1128
- };
1129
- const mimeType = mimeTypes[ext] || "application/octet-stream";
1109
+ let mimeType;
1110
+ if (options.mime) {
1111
+ mimeType = options.mime;
1112
+ } else {
1113
+ const ext = path3__default.default.extname(options.name).toLowerCase();
1114
+ const mimeTypes = {
1115
+ ".jpg": "image/jpeg",
1116
+ ".jpeg": "image/jpeg",
1117
+ ".png": "image/png",
1118
+ ".gif": "image/gif",
1119
+ ".webp": "image/webp",
1120
+ ".svg": "image/svg+xml",
1121
+ ".mp4": "video/mp4",
1122
+ ".mov": "video/quicktime",
1123
+ ".avi": "video/x-msvideo",
1124
+ ".pdf": "application/pdf",
1125
+ ".doc": "application/msword",
1126
+ ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
1127
+ ".xls": "application/vnd.ms-excel",
1128
+ ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
1129
+ ".txt": "text/plain",
1130
+ ".json": "application/json",
1131
+ ".zip": "application/zip"
1132
+ };
1133
+ mimeType = mimeTypes[ext] || "application/octet-stream";
1134
+ }
1130
1135
  if (!validateMimeType(mimeType, config.security.allowedMimeTypes)) {
1131
1136
  throw new Error(`File type ${mimeType} not allowed`);
1132
1137
  }
@@ -1251,6 +1256,65 @@ var driveAPIHandler = async (req, res) => {
1251
1256
  res.status(400).json({ status: 400, message: "Missing action query parameter" });
1252
1257
  return;
1253
1258
  }
1259
+ if (action === "serve" || action === "thumbnail") {
1260
+ try {
1261
+ const { id, token } = req.query;
1262
+ if (!id || typeof id !== "string") {
1263
+ return res.status(400).json({ status: 400, message: "Missing or invalid file ID" });
1264
+ }
1265
+ const drive = await drive_default.findById(id);
1266
+ if (!drive) return res.status(404).json({ status: 404, message: "File not found" });
1267
+ if (config.security.signedUrls?.enabled) {
1268
+ if (!token || typeof token !== "string") {
1269
+ return res.status(401).json({ status: 401, message: "Missing or invalid token" });
1270
+ }
1271
+ try {
1272
+ const decoded = Buffer.from(token, "base64url").toString();
1273
+ const [expiryStr, signature] = decoded.split(":");
1274
+ const expiry = parseInt(expiryStr, 10);
1275
+ if (Date.now() / 1e3 > expiry) {
1276
+ return res.status(401).json({ status: 401, message: "Token expired" });
1277
+ }
1278
+ const { secret } = config.security.signedUrls;
1279
+ const expectedSignature = crypto2__default.default.createHmac("sha256", secret).update(`${id}:${expiry}`).digest("hex");
1280
+ if (signature !== expectedSignature) {
1281
+ return res.status(401).json({ status: 401, message: "Invalid token" });
1282
+ }
1283
+ } catch (err) {
1284
+ return res.status(401).json({ status: 401, message: "Invalid token format" });
1285
+ }
1286
+ }
1287
+ const itemProvider = drive.provider?.type === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
1288
+ const itemAccountId = drive.storageAccountId ? drive.storageAccountId.toString() : void 0;
1289
+ if (action === "thumbnail") {
1290
+ const stream = await itemProvider.getThumbnail(drive, itemAccountId);
1291
+ res.setHeader("Content-Type", "image/webp");
1292
+ if (config.cors?.enabled) {
1293
+ res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1294
+ }
1295
+ stream.pipe(res);
1296
+ return;
1297
+ }
1298
+ if (action === "serve") {
1299
+ const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
1300
+ const safeFilename = sanitizeContentDispositionFilename(drive.name);
1301
+ res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
1302
+ res.setHeader("Content-Type", mime);
1303
+ if (config.cors?.enabled) {
1304
+ res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1305
+ }
1306
+ if (size) res.setHeader("Content-Length", size);
1307
+ stream.pipe(res);
1308
+ return;
1309
+ }
1310
+ } catch (error) {
1311
+ console.error(`[next-drive] Error in ${action}:`, error);
1312
+ return res.status(500).json({
1313
+ status: 500,
1314
+ message: error instanceof Error ? error.message : "Unknown error"
1315
+ });
1316
+ }
1317
+ }
1254
1318
  try {
1255
1319
  const information = await getDriveInformation(req);
1256
1320
  const { key: owner } = information;
@@ -1485,7 +1549,7 @@ var driveAPIHandler = async (req, res) => {
1485
1549
  cleanupTempFiles(files);
1486
1550
  return res.status(413).json({ status: 413, message: "Storage quota exceeded" });
1487
1551
  }
1488
- currentUploadId = crypto.randomUUID();
1552
+ currentUploadId = crypto2__default.default.randomUUID();
1489
1553
  const uploadDir = path3__default.default.join(tempBaseDir, currentUploadId);
1490
1554
  fs4__default.default.mkdirSync(uploadDir, { recursive: true });
1491
1555
  const metadata = {
@@ -1738,42 +1802,6 @@ var driveAPIHandler = async (req, res) => {
1738
1802
  return res.status(200).json({ status: 200, message: "Renamed", data: { item } });
1739
1803
  }
1740
1804
  // ** 9. THUMBNAIL **
1741
- case "thumbnail": {
1742
- const thumbQuery = thumbnailQuerySchema.safeParse(req.query);
1743
- if (!thumbQuery.success) return res.status(400).json({ status: 400, message: "Invalid params" });
1744
- const { id } = thumbQuery.data;
1745
- const drive = await drive_default.findById(id);
1746
- if (!drive) return res.status(404).json({ status: 404, message: "Not found" });
1747
- const itemProvider = drive.provider?.type === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
1748
- const itemAccountId = drive.storageAccountId ? drive.storageAccountId.toString() : void 0;
1749
- const stream = await itemProvider.getThumbnail(drive, itemAccountId);
1750
- res.setHeader("Content-Type", "image/webp");
1751
- if (config.cors?.enabled) {
1752
- res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1753
- }
1754
- stream.pipe(res);
1755
- return;
1756
- }
1757
- // ** 10. SERVE / DOWNLOAD **
1758
- case "serve": {
1759
- const serveQuery = serveQuerySchema.safeParse(req.query);
1760
- if (!serveQuery.success) return res.status(400).json({ status: 400, message: "Invalid params" });
1761
- const { id } = serveQuery.data;
1762
- const drive = await drive_default.findById(id);
1763
- if (!drive) return res.status(404).json({ status: 404, message: "Not found" });
1764
- const itemProvider = drive.provider?.type === "GOOGLE" ? GoogleDriveProvider : LocalStorageProvider;
1765
- const itemAccountId = drive.storageAccountId ? drive.storageAccountId.toString() : void 0;
1766
- const { stream, mime, size } = await itemProvider.openStream(drive, itemAccountId);
1767
- const safeFilename = sanitizeContentDispositionFilename(drive.name);
1768
- res.setHeader("Content-Disposition", `inline; filename="${safeFilename}"`);
1769
- res.setHeader("Content-Type", mime);
1770
- if (config.cors?.enabled) {
1771
- res.setHeader("Cross-Origin-Resource-Policy", "cross-origin");
1772
- }
1773
- if (size) res.setHeader("Content-Length", size);
1774
- stream.pipe(res);
1775
- return;
1776
- }
1777
1805
  default:
1778
1806
  res.status(400).json({ status: 400, message: `Unknown action: ${action}` });
1779
1807
  }
@@ -1795,5 +1823,5 @@ exports.driveReadFile = driveReadFile;
1795
1823
  exports.driveUpload = driveUpload;
1796
1824
  exports.getDriveConfig = getDriveConfig;
1797
1825
  exports.getDriveInformation = getDriveInformation;
1798
- //# sourceMappingURL=chunk-UUMAVUTE.cjs.map
1799
- //# sourceMappingURL=chunk-UUMAVUTE.cjs.map
1826
+ //# sourceMappingURL=chunk-LMM5IU5U.cjs.map
1827
+ //# sourceMappingURL=chunk-LMM5IU5U.cjs.map