mbkbucket 1.1.1 → 1.2.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/index.d.ts CHANGED
@@ -73,6 +73,14 @@ export function deleteFiles(keys: string[], bucketName?: string): Promise<{
73
73
  deletedAt: string;
74
74
  }>;
75
75
 
76
+ export function deleteFolder(prefix: string, bucketName?: string): Promise<{
77
+ results?: any[];
78
+ deletedCount: number;
79
+ errors?: any[];
80
+ deletedAt: string;
81
+ prefix?: string;
82
+ }>;
83
+
76
84
  export function listfiles(
77
85
  prefix?: string,
78
86
  options?: {
@@ -186,6 +194,14 @@ declare module "mbkbucket/lib/s3" {
186
194
  errors: any[];
187
195
  deletedAt: string;
188
196
  }>;
197
+
198
+ export function deleteFolder(prefix: string, bucketName?: string): Promise<{
199
+ results?: any[];
200
+ deletedCount: number;
201
+ errors?: any[];
202
+ deletedAt: string;
203
+ prefix?: string;
204
+ }>;
189
205
  export function listfiles(
190
206
  prefix?: string,
191
207
  options?: {
package/index.js CHANGED
@@ -8,16 +8,22 @@ import compression from "compression";
8
8
  import rateLimit from 'express-rate-limit';
9
9
  import bucket from "./lib/bucket.js";
10
10
  import { checkVersion } from "./lib/config/index.js";
11
+ import { appVersion } from "./lib/config/index.js";
11
12
 
12
13
  dotenv.config();
13
14
 
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = path.dirname(__filename);
17
+ const assetVersion = appVersion;
16
18
 
17
19
  const server = express();
18
20
  server.set('trust proxy', 1);
19
21
 
20
22
  server.use(compression());
23
+ server.use((req, res, next) => {
24
+ res.locals.assetVersion = assetVersion;
25
+ next();
26
+ });
21
27
 
22
28
  // Rate limiting: general limiter for typical browsing/API usage and a stricter
23
29
  // limiter for dashboard (admin) routes.
@@ -34,6 +40,9 @@ const generalLimiter = rateLimit({
34
40
  server.use(express.json());
35
41
  server.use(express.urlencoded({ extended: true }));
36
42
 
43
+ // Serve static assets from /public (CSS/JS/images)
44
+ server.use(express.static(path.join(__dirname, 'public'), { maxAge: '365d', etag: true }));
45
+
37
46
  // Configure Handlebars (single setup)
38
47
  server.engine("handlebars", engine({
39
48
  extname: ".handlebars",
@@ -44,6 +53,7 @@ server.engine("handlebars", engine({
44
53
  path.join(__dirname, "views"),
45
54
  path.join(__dirname, "views/partial"),
46
55
  path.join(__dirname, "node_modules/mbkauthe/views"),
56
+ path.join(__dirname, "../mbkauthe/views")
47
57
  ],
48
58
  cache: process.env.NODE_ENV === "production",
49
59
  helpers: {
@@ -132,13 +142,14 @@ server.set("view engine", "handlebars");
132
142
  server.set("views", [
133
143
  path.join(__dirname, "views"),
134
144
  path.join(__dirname, "node_modules/mbkauthe/views"),
145
+ path.join(__dirname, "../mbkauthe/views")
135
146
  ]);
136
147
 
137
148
  server.use(mbkauthe);
138
149
  server.use(generalLimiter);
139
150
  server.use(bucket);
140
151
 
141
- if (process.env.mbkbucket_testl === "dev") {
152
+ if (process.env.mbkbucket_test === "dev") {
142
153
 
143
154
  console.log("[mbkbucket] Dev mode is enabled. Starting server in dev mode.");
144
155
 
package/lib/bucket.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import express from "express";
2
2
  import { validateSessionAndRole, renderPage, mbkautheVar } from "mbkauthe";
3
- import { listfiles, uploadFile, deleteFile, deleteFiles, downloadFile, getFileMetadata, fileExists, ensureKeyHasAppPrefix, ensurePrefix, getAppName, getBucketConfig } from "./s3.js"
3
+ import { listfiles, uploadFile, deleteFile, deleteFiles, deleteFolder, downloadFile, getFileMetadata, fileExists, ensureKeyHasAppPrefix, ensurePrefix, getAppName, getBucketConfig } from "./s3.js";
4
4
  import multer from "multer";
5
5
  import path from "path";
6
6
  import fs from 'fs';
7
7
  import os from 'os';
8
8
  import crypto from 'crypto';
9
+ import { appVersion, getLatestVersion } from "./config/index.js";
9
10
 
10
11
  const router = express.Router();
11
12
  const upload = multer({ storage: multer.memoryStorage() });
@@ -20,6 +21,16 @@ router.get('/mbkbucket', validateSessionAndRole('SuperAdmin'), async (req, res)
20
21
  renderPage(req, res, "bucket.handlebars", false, { page: "Admin Bucket", bucketvar: mbkautheVar?.bucket, message: req.query.message, error: req.query.error });
21
22
  });
22
23
 
24
+ router.get(['/mbkbucket/info', "/mbkbucket/i"], validateSessionAndRole('SuperAdmin'), async (req, res) => {
25
+ let latestVersion = 'unknown';
26
+ try {
27
+ latestVersion = await getLatestVersion();
28
+ } catch (err) {
29
+ console.error("[mbkauthe] Error fetching package-lock.json:", err);
30
+ }
31
+ renderPage(req, res, "info.handlebars", false, { page: "MBK Bucket Info", appVersion: appVersion, latestVersion, APP_NAME: mbkautheVar.APP_NAME });
32
+ });
33
+
23
34
  // API endpoint for fetching files
24
35
  router.get('/mbkbucket/api/files', validateSessionAndRole('SuperAdmin'), async (req, res) => {
25
36
  //mock response for testing
@@ -187,9 +198,9 @@ router.post('/mbkbucket/upload-complete', validateSessionAndRole('SuperAdmin'),
187
198
 
188
199
  // Cleanup chunks
189
200
  for (const f of files) {
190
- await fs.promises.unlink(path.join(dir, f)).catch(() => {});
201
+ await fs.promises.unlink(path.join(dir, f)).catch(() => { });
191
202
  }
192
- await fs.promises.rmdir(dir).catch(() => {});
203
+ await fs.promises.rmdir(dir).catch(() => { });
193
204
 
194
205
  return res.json({ success: true, message: 'File uploaded successfully (assembled)', key });
195
206
  } catch (err) {
@@ -225,7 +236,7 @@ router.post('/mbkbucket/create-folder', validateSessionAndRole('SuperAdmin'), as
225
236
 
226
237
  router.post('/mbkbucket/delete', validateSessionAndRole('SuperAdmin'), async (req, res) => {
227
238
  try {
228
- const { key, keys } = req.body;
239
+ const { key, keys, folder } = req.body;
229
240
 
230
241
  if (keys && Array.isArray(keys) && keys.length) {
231
242
  // Bulk delete
@@ -238,6 +249,12 @@ router.post('/mbkbucket/delete', validateSessionAndRole('SuperAdmin'), async (re
238
249
  let keyToDelete;
239
250
  try { keyToDelete = ensureKeyHasAppPrefix(key); } catch (e) { return res.status(400).json({ success: false, error: e.message }); }
240
251
 
252
+ // If caller indicates it's a folder (or key ends with '/'), delete all objects under the prefix
253
+ if (folder === true || String(keyToDelete).endsWith('/')) {
254
+ await deleteFolder(keyToDelete);
255
+ return res.json({ success: true, message: 'Folder deleted successfully' });
256
+ }
257
+
241
258
  await deleteFile(keyToDelete);
242
259
  res.json({ success: true, message: 'File deleted successfully' });
243
260
  } catch (error) {
package/lib/s3.js CHANGED
@@ -359,6 +359,69 @@ export async function deleteFiles(keys, bucketName = 'r2') {
359
359
  }
360
360
  }
361
361
 
362
+ // List files with enhanced pagination and filtering
363
+ /**
364
+ * Delete all objects under a folder prefix (recursive).
365
+ * Accepts either a folder key (may include trailing slash) or a relative prefix.
366
+ * Normalizes the prefix to include the configured APP_NAME and ensures trailing slash.
367
+ */
368
+ export async function deleteFolder(prefix, bucketName = 'r2') {
369
+ try {
370
+ if (!prefix) {
371
+ throw new Error('Prefix is required');
372
+ }
373
+
374
+ const cleaned = String(prefix).replace(/^\/+/, '').replace(/\/+$/g, '');
375
+ const effectivePrefix = ensurePrefix(cleaned) + '/';
376
+
377
+ const client = getBucketClient(bucketName);
378
+ const config = getBucketConfig(bucketName);
379
+ const bucket = config.BUCKET_NAME;
380
+
381
+ const keysToDelete = [];
382
+ let continuationToken = null;
383
+
384
+ // Paginate through all objects under the prefix
385
+ do {
386
+ const listCmd = new ListObjectsV2Command({
387
+ Bucket: bucket,
388
+ Prefix: effectivePrefix,
389
+ ContinuationToken: continuationToken,
390
+ MaxKeys: 1000
391
+ });
392
+ const resp = await client.send(listCmd);
393
+ const contents = resp.Contents || [];
394
+ for (const obj of contents) {
395
+ if (obj.Key) keysToDelete.push(obj.Key);
396
+ }
397
+ continuationToken = resp.IsTruncated ? resp.NextContinuationToken : null;
398
+ } while (continuationToken);
399
+
400
+ if (!keysToDelete.length) {
401
+ // Nothing found under this prefix; attempt to remove a zero-byte folder marker if present
402
+ const folderMarkerKey = effectivePrefix;
403
+ const exists = await fileExists(folderMarkerKey, bucketName);
404
+ if (exists) {
405
+ await deleteFile(folderMarkerKey, bucketName);
406
+ return { deletedCount: 1, deletedAt: new Date().toISOString(), prefix: effectivePrefix };
407
+ }
408
+ return { deletedCount: 0, deletedAt: new Date().toISOString(), prefix: effectivePrefix };
409
+ }
410
+
411
+ // Use the existing deleteFiles helper which handles batching and error aggregation
412
+ const result = await deleteFiles(keysToDelete, bucketName);
413
+
414
+ return {
415
+ ...result,
416
+ prefix: effectivePrefix,
417
+ deletedAt: new Date().toISOString()
418
+ };
419
+ } catch (error) {
420
+ console.error('Delete folder failed:', error);
421
+ throw new Error(`Delete folder failed: ${error.message}`);
422
+ }
423
+ }
424
+
362
425
  // List files with enhanced pagination and filtering
363
426
  export async function listfiles(prefix = '', options = {}) {
364
427
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkbucket",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "S3 Bucket manager for mbktech.org apps",
5
5
  "author": "Muhammad Bin Khalid",
6
6
  "homepage": "https://github.com/MIbnEKhalid/mbkbucket#readme",