mbkbucket 3.3.0 → 3.3.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.
Files changed (39) hide show
  1. package/index.js +8 -18
  2. package/lib/cli.js +642 -0
  3. package/{public → lib/public}/bucketadmin.js +1 -1
  4. package/{src → lib/src}/app.js +37 -50
  5. package/{src → lib/src}/cli/auth-helper.js +24 -103
  6. package/lib/src/config/index.js +108 -0
  7. package/{src → lib/src}/config/validation.js +14 -30
  8. package/{src → lib/src}/controllers/api.controller.js +85 -176
  9. package/{src → lib/src}/controllers/dashboard.controller.js +12 -23
  10. package/{src → lib/src}/controllers/info.controller.js +17 -20
  11. package/{src → lib/src}/controllers/view.controller.js +23 -70
  12. package/{src → lib/src}/middleware/async-handler.js +1 -16
  13. package/{src → lib/src}/middleware/bucket-resolver.js +7 -13
  14. package/{src → lib/src}/middleware/error-handler.js +6 -14
  15. package/{src → lib/src}/middleware/rate-limiter.js +2 -10
  16. package/{src → lib/src}/middleware/security.js +1 -17
  17. package/lib/src/routes/api.routes.js +40 -0
  18. package/{src → lib/src}/routes/index.js +1 -9
  19. package/{src → lib/src}/routes/info.routes.js +2 -3
  20. package/{src → lib/src}/routes/view.routes.js +3 -8
  21. package/{src → lib/src}/services/s3.service.js +157 -349
  22. package/lib/src/utils/errors.js +45 -0
  23. package/lib/src/utils/helpers.js +251 -0
  24. package/package.json +18 -3
  25. package/.github/PACKAGE.md +0 -19
  26. package/.github/workflows/publish.yml +0 -88
  27. package/cli.js +0 -977
  28. package/index.d.ts +0 -444
  29. package/lib/index.js +0 -17
  30. package/src/config/index.js +0 -133
  31. package/src/routes/api.routes.js +0 -66
  32. package/src/utils/errors.js +0 -76
  33. package/src/utils/helpers.js +0 -197
  34. package/test/config-validation.test.js +0 -116
  35. /package/{public → lib/public}/bucketadmin.css +0 -0
  36. /package/{public → lib/public}/mbkbucket-helper-demo.html +0 -0
  37. /package/{public → lib/public}/mbkbucket-helper.css +0 -0
  38. /package/{public → lib/public}/mbkbucket-helper.js +0 -0
  39. /package/{src → lib/src}/utils/logger.js +0 -0
@@ -1,28 +1,12 @@
1
- import {
2
- downloadFile, getFileMetadata, ensureKeyHasAppPrefix,
3
- generateSignedUrl, resolveBucketName
4
- } from "../services/s3.service.js";
1
+ import { downloadFile, getFileMetadata, ensureKeyHasAppPrefix, generateSignedUrl, resolveBucketName } from "../services/s3.service.js";
5
2
  import { mbkbucketVar } from "../config/index.js";
6
- import {
7
- escapeHtml, getBaseName, VIEWABLE_TYPES, VIEWABLE_TYPES_SORTED,
8
- STATIC_ASSET_TYPES, VIDEO_TYPES, AUDIO_TYPES, MIME_TYPES,
9
- buildCacheControl, parseRangeHeader, renderPlayerPage
10
- } from "../utils/helpers.js";
11
- import { createLogger } from "../utils/logger.js";
3
+ import { escapeHtml, getBaseName, getFileExt, getMimeType, normalizeKeyParam, destroyStream, VIEWABLE_TYPES, VIEWABLE_TYPES_SORTED, STATIC_ASSET_TYPES, VIDEO_TYPES, AUDIO_TYPES, buildCacheControl, parseRangeHeader, renderPlayerPage } from "../utils/helpers.js";
4
+ import { createLogger } from "#logger";
12
5
 
13
6
  const debugView = createLogger('view');
14
7
 
15
- // ---------------------------------------------------------------------------
16
- // Stream helpers
17
- // ---------------------------------------------------------------------------
18
-
19
8
  function attachStreamCleanup(req, res, stream, timeoutMs = 120000) {
20
- const cleanup = () => {
21
- if (stream && typeof stream.destroy === 'function') {
22
- stream.removeAllListeners();
23
- stream.destroy();
24
- }
25
- };
9
+ const cleanup = () => destroyStream(stream);
26
10
 
27
11
  stream.on('error', (err) => {
28
12
  console.error('Stream error:', err);
@@ -32,25 +16,18 @@ function attachStreamCleanup(req, res, stream, timeoutMs = 120000) {
32
16
 
33
17
  req.on('close', cleanup);
34
18
  req.on('aborted', cleanup);
35
-
36
19
  req.setTimeout(timeoutMs, () => {
37
20
  console.error('Request timeout for stream');
38
21
  cleanup();
39
22
  if (!res.headersSent) res.status(408).end('Request timeout');
40
23
  });
41
-
42
24
  res.on('finish', cleanup);
43
25
  res.on('close', cleanup);
44
26
  }
45
27
 
46
- // ---------------------------------------------------------------------------
47
- // Inline file serving (used by /view/:key and /p_view/:key)
48
- // ---------------------------------------------------------------------------
49
-
50
28
  async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName, publicCache = false } = {}) {
51
29
  const fileName = getBaseName(keyToUse);
52
- const dotIndex = fileName.lastIndexOf('.');
53
- const fileExtension = dotIndex === -1 ? '' : fileName.substring(dotIndex + 1).toLowerCase();
30
+ const fileExtension = getFileExt(fileName);
54
31
 
55
32
  debugView('Request for %s (type: %s, range: %s)', fileName, fileExtension, req.headers.range || 'none');
56
33
 
@@ -70,7 +47,7 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
70
47
  const isAudio = AUDIO_TYPES.has(fileExtension);
71
48
  const isPdf = fileExtension === 'pdf';
72
49
  const supportsRanges = isVideo || isAudio || isPdf;
73
- const contentType = MIME_TYPES[fileExtension] || 'application/octet-stream';
50
+ const contentType = getMimeType(fileName);
74
51
 
75
52
  // Fast path for range requests (video / audio / PDF streaming)
76
53
  if (supportsRanges && rangeHeader) {
@@ -83,7 +60,7 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
83
60
  return res.status(500).json({ message: 'Failed to fetch file metadata', error: err.message });
84
61
  }
85
62
 
86
- if (!meta || !meta.exists) {
63
+ if (!meta?.exists) {
87
64
  return res.status(404).json({ message: 'File not found', key: keyToUse });
88
65
  }
89
66
 
@@ -91,21 +68,16 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
91
68
  if (ifNoneMatch === etag) return res.status(304).end();
92
69
 
93
70
  const total = Number(meta.ContentLength);
94
- if (!total) {
95
- // No content-length — fall through to full-file path
96
- } else {
71
+ if (total) {
97
72
  const range = parseRangeHeader(rangeHeader, total);
98
73
  if (!range) {
99
- // Invalid or unsatisfiable range
100
74
  res.status(416).setHeader('Content-Range', `bytes */${total}`);
101
75
  return res.end();
102
76
  }
103
77
 
104
78
  const { start, end, size: chunksize } = range;
105
-
106
79
  try {
107
80
  const rangeResult = await downloadFile(keyToUse, { range: `bytes=${start}-${end}`, bucketName });
108
-
109
81
  if (typeof rangeResult.ContentLength === 'number' && rangeResult.ContentLength !== chunksize) {
110
82
  debugView('Range size mismatch for %s: expected=%s got=%s', keyToUse, chunksize, rangeResult.ContentLength);
111
83
  }
@@ -125,10 +97,8 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
125
97
 
126
98
  attachStreamCleanup(req, res, rangeResult.Body);
127
99
  return rangeResult.Body.pipe(res);
128
-
129
100
  } catch (rangeError) {
130
101
  console.error('Range request failed:', rangeError);
131
- // fall through to full-file path
132
102
  }
133
103
  }
134
104
  }
@@ -140,9 +110,7 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
140
110
  bucketName
141
111
  };
142
112
 
143
- if (isPdf) {
144
- debugView('Serving full PDF (no range header) for %s', fileName);
145
- }
113
+ if (isPdf) debugView('Serving full PDF (no range header) for %s', fileName);
146
114
 
147
115
  let result;
148
116
  try {
@@ -150,14 +118,15 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
150
118
  } catch (err) {
151
119
  if (err.name === 'NoSuchKey' || err.name === 'NotFound') {
152
120
  return res.status(404).json({ message: 'File not found', key: keyToUse });
153
- } else if (err.name === 'AccessDenied') {
121
+ }
122
+ if (err.name === 'AccessDenied') {
154
123
  return res.status(403).json({ message: 'Access denied' });
155
124
  }
156
125
  console.error('[mbkbucket] serveFileInline download error:', err);
157
126
  return res.status(500).json({ message: 'Failed to fetch file', error: err.message });
158
127
  }
159
128
 
160
- if (result && result.notModified) return res.status(304).end();
129
+ if (result?.notModified) return res.status(304).end();
161
130
 
162
131
  const etag = `"${result.LastModified?.getTime() || Date.now()}-${result.ContentLength || 0}"`;
163
132
  if (ifNoneMatch === etag) return res.status(304).end();
@@ -174,26 +143,17 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
174
143
  res.setHeader('Content-Security-Policy', "default-src 'self'; script-src 'none'; object-src 'none';");
175
144
  }
176
145
  if (supportsRanges) res.setHeader('Accept-Ranges', 'bytes');
177
-
178
146
  res.setHeader('Cache-Control', buildCacheControl(contentType, { isStaticAsset, supportsRanges, publicCache }));
179
-
180
147
  res.status(200);
181
148
 
182
149
  const timeoutDuration = result.ContentLength > 50 * 1024 * 1024 ? 300000 : 120000;
183
150
  attachStreamCleanup(req, res, result.Body, timeoutDuration);
184
-
185
151
  result.Body.pipe(res);
186
152
  }
187
153
 
188
- // ---------------------------------------------------------------------------
189
- // Controller handlers
190
- // ---------------------------------------------------------------------------
191
-
192
154
  export async function viewFile(req, res) {
193
155
  try {
194
- const bucketName = req.activeBucket;
195
- const rawKey = req.params.key;
196
- const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
156
+ const key = normalizeKeyParam(req.params.key);
197
157
  let keyToUse;
198
158
  try {
199
159
  keyToUse = ensureKeyHasAppPrefix(key);
@@ -201,8 +161,7 @@ export async function viewFile(req, res) {
201
161
  return res.status(400).json({ message: e.message });
202
162
  }
203
163
 
204
- await serveFileInline(req, res, keyToUse, { noindex: true, bucketName, publicCache: false });
205
-
164
+ await serveFileInline(req, res, keyToUse, { noindex: true, bucketName: req.activeBucket, publicCache: false });
206
165
  } catch (error) {
207
166
  console.error("Error viewing file:", error);
208
167
  if (error.name === 'NoSuchKey' || error.name === 'NotFound') {
@@ -217,19 +176,19 @@ export async function viewFile(req, res) {
217
176
 
218
177
  export async function playerPage(req, res) {
219
178
  try {
220
- const bucketName = req.activeBucket;
221
- const rawKey = req.params.key;
222
- const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
179
+ const key = normalizeKeyParam(req.params.key);
223
180
  let keyToUse;
224
181
  try {
225
182
  keyToUse = ensureKeyHasAppPrefix(key);
226
183
  } catch (e) {
227
184
  return res.status(400).send(e.message);
228
185
  }
229
- const encoded = encodeURIComponent(keyToUse);
230
- const safeFileName = escapeHtml(getBaseName(keyToUse));
231
- const bucketQuery = `?bucket=${encodeURIComponent(bucketName)}`;
232
- const html = renderPlayerPage(safeFileName, encoded, bucketQuery);
186
+
187
+ const html = renderPlayerPage(
188
+ escapeHtml(getBaseName(keyToUse)),
189
+ encodeURIComponent(keyToUse),
190
+ `?bucket=${encodeURIComponent(req.activeBucket)}`
191
+ );
233
192
 
234
193
  res.setHeader('Content-Type', 'text/html; charset=utf-8');
235
194
  res.setHeader('X-Content-Type-Options', 'nosniff');
@@ -243,10 +202,8 @@ export async function playerPage(req, res) {
243
202
  export async function publicView(req, res) {
244
203
  try {
245
204
  const bucketName = resolveBucketName();
246
- const rawKey = req.params.key;
247
- const key = Array.isArray(rawKey) ? rawKey.join('/') : (rawKey || '');
205
+ const key = normalizeKeyParam(req.params.key);
248
206
 
249
- // No directory indexing
250
207
  if (!key || key.endsWith('/')) {
251
208
  return res.status(403).json({ message: 'Directory indexing is not allowed' });
252
209
  }
@@ -258,10 +215,7 @@ export async function publicView(req, res) {
258
215
  return res.status(400).json({ message: e.message });
259
216
  }
260
217
 
261
- let p_view_inline = !!mbkbucketVar?.p_view_inline;
262
-
263
- if (!p_view_inline) {
264
- // Signed URL mode
218
+ if (!mbkbucketVar?.p_view_inline) {
265
219
  try {
266
220
  const signed = await generateSignedUrl(keyToUse, 'getObject', 3600, bucketName);
267
221
  return res.redirect(302, signed.url);
@@ -271,7 +225,6 @@ export async function publicView(req, res) {
271
225
  }
272
226
  }
273
227
 
274
- // Direct serve mode
275
228
  await serveFileInline(req, res, keyToUse, { noindex: true, bucketName, publicCache: true });
276
229
  } catch (error) {
277
230
  console.error('[mbkbucket] p_view error:', error);
@@ -1,30 +1,15 @@
1
1
  /**
2
2
  * Wraps an async Express route handler so that any thrown/rejected errors
3
3
  * are forwarded to `next(err)` instead of crashing the process.
4
- *
5
- * Usage:
6
- * router.get('/path', wrap(myAsyncHandler));
7
- *
8
- * If a custom error handler is provided, it receives (error, req, res, next)
9
- * and can send a custom response instead of forwarding to Express error middleware.
10
4
  */
11
5
  export function wrap(handler, onError) {
12
6
  return (req, res, next) => {
13
- const result = handler(req, res, next);
14
- if (result && typeof result.catch === 'function') {
15
- result.catch((err) => {
16
- if (onError) {
17
- return onError(err, req, res, next);
18
- }
19
- next(err);
20
- });
21
- }
7
+ Promise.resolve(handler(req, res, next)).catch(err => (onError ? onError(err, req, res, next) : next(err)));
22
8
  };
23
9
  }
24
10
 
25
11
  /**
26
12
  * Combines wrap() with a standard JSON error response for API routes.
27
- * The `fallbackMessage` is used when the error has no message.
28
13
  */
29
14
  export function wrapApi(handler, fallbackMessage = 'Request failed') {
30
15
  return wrap(handler, (err, _req, res) => {
@@ -1,9 +1,10 @@
1
1
  import { resolveBucketName } from "../services/s3.service.js";
2
2
  import { sendApiError, sendViewError } from "../utils/errors.js";
3
3
 
4
+ const NO_BUCKET_MSG = 'No bucket selected. Provide ?bucket=<name> or configure a default bucket in mbkautheVar.bucket.';
5
+
4
6
  /**
5
- * Middleware that resolves `?bucket=<name>` query param and attaches
6
- * `req.activeBucket` and `req.bucketResolveError` to every request under /mbkbucket.
7
+ * Resolves `?bucket=<name>` query param and attaches `req.activeBucket` and `req.bucketResolveError`.
7
8
  */
8
9
  export function bucketResolver(req, _res, next) {
9
10
  try {
@@ -20,24 +21,17 @@ export function bucketResolver(req, _res, next) {
20
21
  * Guard middleware for API routes — returns 400/503 JSON if bucket is invalid.
21
22
  */
22
23
  export function requireBucketApi(req, res, next) {
23
- if (req.bucketResolveError) {
24
- return sendApiError(res, req.bucketResolveError, 'Invalid bucket selection');
25
- }
26
- if (!req.activeBucket) {
27
- return sendApiError(res, new Error('No bucket selected. Provide ?bucket=<name> or configure a default bucket in mbkautheVar.bucket.'));
28
- }
24
+ if (req.bucketResolveError) return sendApiError(res, req.bucketResolveError, 'Invalid bucket selection');
25
+ if (!req.activeBucket) return sendApiError(res, new Error(NO_BUCKET_MSG));
29
26
  next();
30
27
  }
31
28
 
32
29
  /**
33
- * Guard middleware for view routes — returns 400/503 JSON if bucket is invalid.
34
- * Skips public view routes (/p_view/...) which use the default bucket.
30
+ * Guard middleware for view routes — returns 400/503 JSON if bucket is invalid (skips /p_view/...).
35
31
  */
36
32
  export function requireBucketView(req, res, next) {
37
33
  if (req.path.startsWith('/p_view/')) return next();
38
34
  if (req.bucketResolveError) return sendViewError(res, req.bucketResolveError, 'Invalid bucket selection');
39
- if (!req.activeBucket) {
40
- return sendViewError(res, new Error('No bucket selected. Provide ?bucket=<name> or configure a default bucket in mbkautheVar.bucket.'));
41
- }
35
+ if (!req.activeBucket) return sendViewError(res, new Error(NO_BUCKET_MSG));
42
36
  next();
43
37
  }
@@ -1,34 +1,26 @@
1
- import { createLogger } from "../utils/logger.js";
1
+ import { createLogger } from "#logger";
2
2
 
3
3
  const debugServer = createLogger('server');
4
4
 
5
- /**
6
- * Global 404 handler — renders error page via Handlebars.
7
- */
5
+ const BASE_ERROR_DATA = { layout: false, pagename: "Home", page: "/" };
6
+
8
7
  export function notFoundHandler(req, res) {
9
8
  debugServer("Path not found: %s %s", req.method, req.url);
10
9
  return res.status(404).render("Error/dError.handlebars", {
11
- layout: false,
10
+ ...BASE_ERROR_DATA,
12
11
  code: 404,
13
12
  error: "Not Found",
14
13
  message: "The requested page was not found.",
15
- pagename: "Home",
16
- page: "/",
17
14
  });
18
15
  }
19
16
 
20
- /**
21
- * Global error handler — catches unhandled errors from routes/middleware.
22
- */
23
- export function globalErrorHandler(err, req, res, _next) {
17
+ export function globalErrorHandler(err, _req, res, _next) {
24
18
  console.error(`[mbkbucket] ${err.stack}`);
25
19
  return res.status(500).render("Error/dError.handlebars", {
26
- layout: false,
20
+ ...BASE_ERROR_DATA,
27
21
  code: 500,
28
22
  error: "Internal app Error",
29
23
  message: "An unexpected error occurred on the app.",
30
24
  details: err.message,
31
- pagename: "Home",
32
- page: "/",
33
25
  });
34
26
  }
@@ -1,29 +1,21 @@
1
1
  import rateLimit from "express-rate-limit";
2
2
 
3
- /**
4
- * General rate limiter for typical browsing/API usage.
5
- * 150 requests per 2 minutes per IP.
6
- */
7
3
  export const generalLimiter = rateLimit({
8
4
  windowMs: 2 * 60 * 1000, // 2 minutes
9
5
  max: 150,
10
6
  standardHeaders: true,
11
7
  legacyHeaders: false,
12
- handler: (req, res) => {
8
+ handler: (_req, res) => {
13
9
  res.status(429).render('error.handlebars', { message: 'Too many requests from your IP. Try again later.', code: 429 });
14
10
  }
15
11
  });
16
12
 
17
- /**
18
- * Stricter rate limiter for public view (p_view) routes.
19
- * 10 requests per minute per IP.
20
- */
21
13
  export const pviewRateLimit = rateLimit({
22
14
  windowMs: 60 * 1000, // 1 minute
23
15
  max: 10,
24
16
  standardHeaders: 'draft-7',
25
17
  legacyHeaders: false,
26
- keyGenerator: (req) => req.headers['x-forwarded-for']?.split(',')[0].trim() || req.socket.remoteAddress || 'unknown',
18
+ keyGenerator: req => req.headers['x-forwarded-for']?.split(',')[0].trim() || req.socket.remoteAddress || 'unknown',
27
19
  handler: (_req, res) => {
28
20
  res.status(429).json({ message: 'Too many requests. Please slow down and try again later.' });
29
21
  }
@@ -11,29 +11,13 @@ const SUSPICIOUS_HEADERS = [
11
11
  'x-automated-tool'
12
12
  ];
13
13
 
14
- /**
15
- * p_view security middleware: blocks bots, sets security headers.
16
- */
17
14
  export function pviewSecurity(req, res, next) {
18
15
  const ua = req.headers['user-agent'] || '';
19
16
 
20
- // Block empty or obviously automated UAs
21
- if (!ua || BLOCKED_UA_PATTERNS.test(ua)) {
22
- return res.status(403).json({ message: 'Forbidden' });
23
- }
24
-
25
- // Block suspicious headers that indicate automation
26
- const hasSuspiciousHeaders = SUSPICIOUS_HEADERS.some(header => header in req.headers);
27
- if (hasSuspiciousHeaders) {
28
- return res.status(403).json({ message: 'Forbidden' });
29
- }
30
-
31
- // Verify Accept header is present (legitimate browsers always send this)
32
- if (!req.headers['accept']) {
17
+ if (!ua || BLOCKED_UA_PATTERNS.test(ua) || !req.headers.accept || SUSPICIOUS_HEADERS.some(h => h in req.headers)) {
33
18
  return res.status(403).json({ message: 'Forbidden' });
34
19
  }
35
20
 
36
- // Set security headers for the public view response
37
21
  res.setHeader('X-Content-Type-Options', 'nosniff');
38
22
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
39
23
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
@@ -0,0 +1,40 @@
1
+ import express from "express";
2
+ import { sessRole } from "mbkauthe";
3
+ import multer from "multer";
4
+ import { requireBucketApi } from "../middleware/bucket-resolver.js";
5
+ import { cleanupUploads, listIncompleteUploads, listFiles, uploadSingleFile, initiateMultipartUpload, uploadChunk, completeUpload, abortUpload, createFolder, deleteItems, downloadFileHandler } from "../controllers/api.controller.js";
6
+
7
+ const router = express.Router();
8
+ const MAX_CHUNK_SIZE = 50 * 1024 * 1024; // 50 MB
9
+ const upload = multer({ storage: multer.memoryStorage(), limits: { fileSize: MAX_CHUNK_SIZE } });
10
+
11
+ const uploadChunkMiddleware = (req, res, next) => {
12
+ upload.single('chunk')(req, res, (err) => {
13
+ if (err?.code === 'LIMIT_FILE_SIZE') {
14
+ return res.status(413).json({
15
+ success: false,
16
+ error: `Chunk too large (max ${Math.round(MAX_CHUNK_SIZE / (1024 * 1024))} MB).`,
17
+ });
18
+ }
19
+ if (err) return next(err);
20
+ next();
21
+ });
22
+ };
23
+
24
+ const adminAuth = sessRole('SuperAdmin');
25
+
26
+ router.use(requireBucketApi);
27
+
28
+ router.post('/api/cleanup-uploads', adminAuth, cleanupUploads);
29
+ router.get('/api/incomplete-uploads', adminAuth, listIncompleteUploads);
30
+ router.get('/api/files', adminAuth, listFiles);
31
+ router.post('/upload', adminAuth, upload.single('file'), uploadSingleFile);
32
+ router.post('/upload-init', adminAuth, initiateMultipartUpload);
33
+ router.post('/upload-chunk', adminAuth, uploadChunkMiddleware, uploadChunk);
34
+ router.post('/upload-complete', adminAuth, completeUpload);
35
+ router.post('/upload-abort', adminAuth, abortUpload);
36
+ router.post('/create-folder', adminAuth, createFolder);
37
+ router.post('/delete', adminAuth, deleteItems);
38
+ router.get('/download/*key', adminAuth, downloadFileHandler);
39
+
40
+ export default router;
@@ -8,15 +8,7 @@ import apiRoutes from "./api.routes.js";
8
8
 
9
9
  const router = express.Router();
10
10
 
11
- // Dashboard home
12
11
  router.get('/mbkbucket', sessRole('SuperAdmin'), renderBucketDashboard);
13
-
14
- // Central bucket resolution for all /mbkbucket subroutes
15
- router.use('/mbkbucket', bucketResolver);
16
-
17
- // Mount sub-routers
18
- router.use("/mbkbucket", infoRoutes);
19
- router.use("/mbkbucket", viewRoutes);
20
- router.use("/mbkbucket", apiRoutes);
12
+ router.use('/mbkbucket', bucketResolver, infoRoutes, viewRoutes, apiRoutes);
21
13
 
22
14
  export default router;
@@ -1,10 +1,9 @@
1
1
  import express from "express";
2
- import { sessRole } from "mbkauthe";
3
2
  import { infoPage, infoJson } from "../controllers/info.controller.js";
4
3
 
5
4
  const router = express.Router();
6
5
 
7
- router.get(['/info', "/i"], infoPage);
8
- router.get(['/info.json', "/i.json"], infoJson);
6
+ router.get(['/info', '/i'], infoPage);
7
+ router.get(['/info.json', '/i.json'], infoJson);
9
8
 
10
9
  export default router;
@@ -7,17 +7,12 @@ import { pviewSecurity } from "../middleware/security.js";
7
7
  import { viewFile, playerPage, publicView } from "../controllers/view.controller.js";
8
8
 
9
9
  const router = express.Router();
10
+ const adminAuth = sessRole('SuperAdmin');
10
11
 
11
- // Bucket guard for view routes (skips p_view)
12
12
  router.use(requireBucketView);
13
+ router.get('/player/*key', adminAuth, playerPage);
14
+ router.get('/view/*key', adminAuth, viewFile);
13
15
 
14
- // Player page for video/audio
15
- router.get('/player/*key', sessRole('SuperAdmin'), playerPage);
16
-
17
- // Inline file view
18
- router.get('/view/*key', sessRole('SuperAdmin'), viewFile);
19
-
20
- // Public view (conditionally enabled)
21
16
  if (mbkbucketVar?.publiView_enabled) {
22
17
  router.get('/p_view/*key', pviewRateLimit, pviewSecurity, publicView);
23
18
  }