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 +16 -0
- package/index.js +12 -1
- package/lib/bucket.js +21 -4
- package/lib/s3.js +63 -0
- package/package.json +1 -1
- package/public/css/bucketadmin.css +1413 -0
- package/public/js/bucketadmincontent.js +1400 -0
- package/views/bucketadmincontent.handlebars +16 -2696
- package/views/info.handlebars +1056 -0
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.
|
|
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 {
|