@muhgholy/next-drive 4.23.0 → 4.23.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -461,6 +461,80 @@ const items = await driveList({
461
461
  | `limit` | `number` | No | Maximum items to return (default: 100) |
462
462
  | `afterId` | `string` | No | Last item ID for pagination |
463
463
 
464
+ ### List Files (Paginated)
465
+
466
+ List **files only** (not folders) with offset-based pagination:
467
+
468
+ ```typescript
469
+ import { driveListFiles } from "@muhgholy/next-drive/server";
470
+
471
+ // List all files (page 1, default limit 50)
472
+ const result = await driveListFiles({});
473
+
474
+ // List files for a specific owner
475
+ const result = await driveListFiles({ key: { userId: "123" } });
476
+
477
+ // List files in a folder with pagination
478
+ const result = await driveListFiles({
479
+ key: { userId: "123" },
480
+ folderId: "folderIdHere",
481
+ page: 2,
482
+ limit: 20,
483
+ });
484
+
485
+ // Access pagination info
486
+ console.log(result.pagination);
487
+ // { page: 2, limit: 20, totalCount: 87, totalPages: 5, hasMore: true }
488
+ ```
489
+
490
+ **Options:**
491
+
492
+ | Option | Type | Required | Description |
493
+ | ----------- | -------------------------- | -------- | ---------------------------------------------- |
494
+ | `key` | `Record<string, unknown>` | No | Owner key (omit to list all files) |
495
+ | `folderId` | `string \| null` | No | Folder ID (null/'root' for root, omit for all) |
496
+ | `accountId` | `string` | No | Storage account ID |
497
+ | `page` | `number` | No | Page number (default: 1) |
498
+ | `limit` | `number` | No | Items per page (default: 50, max: 100) |
499
+
500
+ **Returns:** `{ items: TDatabaseDrive[], pagination: { page, limit, totalCount, totalPages, hasMore } }`
501
+
502
+ ### Direct Database Access
503
+
504
+ For custom queries, use the exposed Mongoose model:
505
+
506
+ ```typescript
507
+ import { DatabaseMongoDrive } from "@muhgholy/next-drive/server";
508
+ import type { IDatabaseDriveDocument } from "@muhgholy/next-drive/server";
509
+
510
+ // Custom query
511
+ const files = await DatabaseMongoDrive.find({
512
+ owner: { userId: "123" },
513
+ "information.type": "FILE",
514
+ "information.mime": { $regex: "^image/" },
515
+ });
516
+
517
+ // Aggregation
518
+ const stats = await DatabaseMongoDrive.aggregate([
519
+ { $match: { owner: { userId: "123" }, trashedAt: null } },
520
+ { $group: { _id: "$information.type", count: { $sum: 1 } } },
521
+ ]);
522
+ ```
523
+
524
+ ### Cleanup Orphaned Files
525
+
526
+ Remove orphaned file folders from storage that no longer exist in the database, and clean up leftover temp directories:
527
+
528
+ ```typescript
529
+ import { driveCleanup } from "@muhgholy/next-drive/server";
530
+
531
+ const result = await driveCleanup();
532
+ console.log(`Removed ${result.removed.length} orphaned folders`);
533
+ console.log(`Freed ${result.totalFreedInBytes} bytes`);
534
+ ```
535
+
536
+ **Returns:** `{ removed: string[], totalFreedInBytes: number }`
537
+
464
538
  ### Delete File or Folder
465
539
 
466
540
  Permanently delete a file or folder from the drive system:
@@ -1,7 +1,7 @@
1
1
  import formidable from 'formidable';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
- import os3 from 'os';
4
+ import os2 from 'os';
5
5
  import crypto2 from 'crypto';
6
6
  import mongoose, { Schema, isValidObjectId } from 'mongoose';
7
7
  import sharp2 from 'sharp';
@@ -260,7 +260,7 @@ var driveConfiguration = async (config) => {
260
260
  if (g.migrationPromise) await g.migrationPromise;
261
261
  return g.config;
262
262
  }
263
- const resolvedPath = config.storage?.path || path.join(os3.tmpdir(), "next-drive-data");
263
+ const resolvedPath = config.storage?.path || path.join(os2.tmpdir(), "next-drive-data");
264
264
  const mode = config.mode || "NORMAL";
265
265
  if (mode === "ROOT") {
266
266
  g.config = {
@@ -1451,6 +1451,51 @@ var driveList = async (options) => {
1451
1451
  const items = await drive_default.find(query, {}, { sort: { order: 1, _id: -1 }, limit });
1452
1452
  return await Promise.all(items.map((item) => item.toClient()));
1453
1453
  };
1454
+ var driveListFiles = async (options) => {
1455
+ const { key, folderId, accountId } = options;
1456
+ const page = Math.max(1, options.page ?? 1);
1457
+ const limit = Math.min(Math.max(1, options.limit ?? 50), 100);
1458
+ let providerName = "LOCAL";
1459
+ if (accountId && accountId !== "LOCAL") {
1460
+ const account = await drive_default.db.model("StorageAccount").findOne({ _id: accountId, owner: key });
1461
+ if (!account) {
1462
+ throw new Error("Invalid Storage Account");
1463
+ }
1464
+ if (account.metadata.provider === "GOOGLE") {
1465
+ providerName = "GOOGLE";
1466
+ }
1467
+ }
1468
+ const query = {
1469
+ "provider.type": providerName,
1470
+ "information.type": "FILE",
1471
+ storageAccountId: accountId || null,
1472
+ trashedAt: null
1473
+ };
1474
+ if (key !== void 0) {
1475
+ query.owner = key;
1476
+ }
1477
+ if (folderId && folderId !== "root") {
1478
+ query.parentId = folderId;
1479
+ } else if (folderId === "root" || folderId === null) {
1480
+ query.parentId = null;
1481
+ }
1482
+ const skip = (page - 1) * limit;
1483
+ const [totalCount, items] = await Promise.all([
1484
+ drive_default.countDocuments(query),
1485
+ drive_default.find(query, {}, { sort: { createdAt: -1 }, skip, limit })
1486
+ ]);
1487
+ const totalPages = Math.ceil(totalCount / limit);
1488
+ return {
1489
+ items: await Promise.all(items.map((item) => item.toClient())),
1490
+ pagination: {
1491
+ page,
1492
+ limit,
1493
+ totalCount,
1494
+ totalPages,
1495
+ hasMore: page < totalPages
1496
+ }
1497
+ };
1498
+ };
1454
1499
  var driveDelete = async (source, options) => {
1455
1500
  const { recurse = true } = options || {};
1456
1501
  let drive;
@@ -1549,7 +1594,7 @@ var driveUpload = async (source, key, options) => {
1549
1594
  const stats = fs.statSync(source);
1550
1595
  fileSize = stats.size;
1551
1596
  } else if (Buffer.isBuffer(source)) {
1552
- const tempDir = path.join(os3.tmpdir(), "next-drive-uploads");
1597
+ const tempDir = path.join(os2.tmpdir(), "next-drive-uploads");
1553
1598
  if (!fs.existsSync(tempDir)) {
1554
1599
  fs.mkdirSync(tempDir, { recursive: true });
1555
1600
  }
@@ -1558,7 +1603,7 @@ var driveUpload = async (source, key, options) => {
1558
1603
  sourceFilePath = tempFilePath;
1559
1604
  fileSize = source.length;
1560
1605
  } else {
1561
- const tempDir = path.join(os3.tmpdir(), "next-drive-uploads");
1606
+ const tempDir = path.join(os2.tmpdir(), "next-drive-uploads");
1562
1607
  if (!fs.existsSync(tempDir)) {
1563
1608
  fs.mkdirSync(tempDir, { recursive: true });
1564
1609
  }
@@ -1658,6 +1703,81 @@ var driveUpload = async (source, key, options) => {
1658
1703
  }
1659
1704
  }
1660
1705
  };
1706
+ var driveCleanup = async () => {
1707
+ const config = getDriveConfig();
1708
+ const fileDir = path.join(config.storage.path, "file");
1709
+ if (!fs.existsSync(fileDir)) {
1710
+ return { removed: [], totalFreedInBytes: 0 };
1711
+ }
1712
+ const folderNames = fs.readdirSync(fileDir).filter((name) => {
1713
+ const fullPath = path.join(fileDir, name);
1714
+ return fs.statSync(fullPath).isDirectory();
1715
+ });
1716
+ if (folderNames.length === 0) {
1717
+ return { removed: [], totalFreedInBytes: 0 };
1718
+ }
1719
+ const getDirSize = (dirPath) => {
1720
+ let size = 0;
1721
+ try {
1722
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
1723
+ for (const entry of entries) {
1724
+ const entryPath = path.join(dirPath, entry.name);
1725
+ if (entry.isFile()) {
1726
+ size += fs.statSync(entryPath).size;
1727
+ } else if (entry.isDirectory()) {
1728
+ size += getDirSize(entryPath);
1729
+ }
1730
+ }
1731
+ } catch {
1732
+ }
1733
+ return size;
1734
+ };
1735
+ const BATCH_SIZE = 500;
1736
+ const existingIds = /* @__PURE__ */ new Set();
1737
+ for (let i = 0; i < folderNames.length; i += BATCH_SIZE) {
1738
+ const batch = folderNames.slice(i, i + BATCH_SIZE);
1739
+ const docs = await drive_default.find(
1740
+ { _id: { $in: batch } },
1741
+ { _id: 1 }
1742
+ ).lean();
1743
+ for (const doc of docs) {
1744
+ existingIds.add(doc._id.toString());
1745
+ }
1746
+ }
1747
+ const removed = [];
1748
+ let totalFreedInBytes = 0;
1749
+ for (const name of folderNames) {
1750
+ if (!existingIds.has(name)) {
1751
+ const dirPath = path.join(fileDir, name);
1752
+ try {
1753
+ totalFreedInBytes += getDirSize(dirPath);
1754
+ fs.rmSync(dirPath, { recursive: true, force: true });
1755
+ removed.push(name);
1756
+ } catch (e) {
1757
+ console.error(`[next-drive] Failed to remove orphaned folder ${name}:`, e);
1758
+ }
1759
+ }
1760
+ }
1761
+ const tempDir = path.join(config.storage.path, "temp");
1762
+ if (fs.existsSync(tempDir)) {
1763
+ try {
1764
+ totalFreedInBytes += getDirSize(tempDir);
1765
+ fs.rmSync(tempDir, { recursive: true, force: true });
1766
+ } catch (e) {
1767
+ console.error("[next-drive] Failed to remove temp directory:", e);
1768
+ }
1769
+ }
1770
+ const systemTmpDir = path.join(os2.tmpdir(), "next-drive-uploads");
1771
+ if (fs.existsSync(systemTmpDir)) {
1772
+ try {
1773
+ totalFreedInBytes += getDirSize(systemTmpDir);
1774
+ fs.rmSync(systemTmpDir, { recursive: true, force: true });
1775
+ } catch (e) {
1776
+ console.error("[next-drive] Failed to remove system temp directory:", e);
1777
+ }
1778
+ }
1779
+ return { removed, totalFreedInBytes };
1780
+ };
1661
1781
 
1662
1782
  // src/server/index.ts
1663
1783
  var getProvider = async (req, owner) => {
@@ -2075,7 +2195,7 @@ var driveAPIHandler = async (req, res) => {
2075
2195
  // ** 3. UPLOAD **
2076
2196
  case "upload": {
2077
2197
  if (req.method !== "POST") return res.status(405).json({ status: 405, message: "Only POST allowed" });
2078
- const systemTmpDir = path.join(os3.tmpdir(), "next-drive-uploads");
2198
+ const systemTmpDir = path.join(os2.tmpdir(), "next-drive-uploads");
2079
2199
  if (!fs.existsSync(systemTmpDir)) fs.mkdirSync(systemTmpDir, { recursive: true });
2080
2200
  const form = formidable({
2081
2201
  multiples: false,
@@ -2111,7 +2231,7 @@ var driveAPIHandler = async (req, res) => {
2111
2231
  }
2112
2232
  const { chunkIndex, totalChunks, driveId, fileName, fileSize: fileSizeInBytes, fileType, folderId } = uploadData.data;
2113
2233
  let currentUploadId = driveId;
2114
- const tempBaseDir = path.join(os3.tmpdir(), "next-drive-uploads");
2234
+ const tempBaseDir = path.join(os2.tmpdir(), "next-drive-uploads");
2115
2235
  if (!currentUploadId) {
2116
2236
  if (chunkIndex !== 0) return res.status(400).json({ message: "Missing upload ID for non-zero chunk" });
2117
2237
  if (fileType && config.security) {
@@ -2259,7 +2379,7 @@ var driveAPIHandler = async (req, res) => {
2259
2379
  const cancelData = cancelQuerySchema.safeParse(req.query);
2260
2380
  if (!cancelData.success) return res.status(400).json({ status: 400, message: "Invalid ID" });
2261
2381
  const { id } = cancelData.data;
2262
- const tempUploadDir = path.join(os3.tmpdir(), "next-drive-uploads", id);
2382
+ const tempUploadDir = path.join(os2.tmpdir(), "next-drive-uploads", id);
2263
2383
  if (fs.existsSync(tempUploadDir)) {
2264
2384
  try {
2265
2385
  fs.rmSync(tempUploadDir, { recursive: true, force: true });
@@ -2404,6 +2524,6 @@ var driveAPIHandler = async (req, res) => {
2404
2524
  }
2405
2525
  };
2406
2526
 
2407
- export { driveAPIHandler, driveConfiguration, driveDelete, driveFilePath, driveFileSchemaZod, driveGetUrl, driveInfo, driveList, driveReadFile, driveUpload, getDriveConfig, getDriveInformation };
2408
- //# sourceMappingURL=chunk-CR3QW3QN.js.map
2409
- //# sourceMappingURL=chunk-CR3QW3QN.js.map
2527
+ export { driveAPIHandler, driveCleanup, driveConfiguration, driveDelete, driveFilePath, driveFileSchemaZod, driveGetUrl, driveInfo, driveList, driveListFiles, driveReadFile, driveUpload, drive_default, getDriveConfig, getDriveInformation };
2528
+ //# sourceMappingURL=chunk-R43JCXQB.js.map
2529
+ //# sourceMappingURL=chunk-R43JCXQB.js.map