mbkbucket 3.6.0 → 3.7.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.
@@ -68,10 +68,18 @@
68
68
  }
69
69
 
70
70
  /* Base Layout & Page Header */
71
+ .page-wrapper,
72
+ .page-wrapper *,
73
+ .page-wrapper *::before,
74
+ .page-wrapper *::after {
75
+ box-sizing: border-box;
76
+ }
77
+
71
78
  .page-wrapper {
72
79
  max-width: 1600px;
73
80
  margin: 0 auto;
74
81
  width: 100%;
82
+ max-width: 100%;
75
83
  }
76
84
 
77
85
  .page-header {
@@ -1363,57 +1371,324 @@
1363
1371
 
1364
1372
  /* Mobile & Tablet Responsiveness */
1365
1373
  @media (max-width: 900px) {
1374
+ .page-wrapper {
1375
+ padding: 0;
1376
+ max-width: 100%;
1377
+ overflow-x: hidden;
1378
+ }
1379
+
1380
+ .card.file-manager {
1381
+ border-radius: var(--radius-md);
1382
+ width: 100%;
1383
+ max-width: 100%;
1384
+ overflow: hidden;
1385
+ }
1386
+
1366
1387
  .fm-layout {
1367
- grid-template-columns: 1fr;
1388
+ grid-template-columns: 100%;
1368
1389
  height: auto;
1390
+ min-height: 0;
1369
1391
  max-height: none;
1392
+ width: 100%;
1393
+ max-width: 100%;
1394
+ overflow: hidden;
1370
1395
  }
1371
1396
 
1372
1397
  .fm-tree-wrap {
1398
+ display: flex;
1399
+ border-right: none;
1400
+ border-bottom: 1px solid var(--border-color);
1401
+ padding: 10px 14px;
1402
+ background: rgba(244, 247, 243, 0.85);
1403
+ width: 100%;
1404
+ max-width: 100%;
1405
+ box-sizing: border-box;
1406
+ }
1407
+
1408
+ .fm-tree-header,
1409
+ .fm-tree {
1373
1410
  display: none;
1374
1411
  }
1375
1412
 
1413
+ .fm-storage {
1414
+ margin-top: 0;
1415
+ padding: 8px 12px;
1416
+ background: rgba(255, 255, 255, 0.95);
1417
+ border: 1px solid var(--border-color);
1418
+ border-radius: var(--radius-sm);
1419
+ box-shadow: 0 1px 4px rgba(15, 118, 110, 0.05);
1420
+ width: 100%;
1421
+ max-width: 100%;
1422
+ box-sizing: border-box;
1423
+ }
1424
+
1425
+ .fm-main {
1426
+ width: 100%;
1427
+ max-width: 100%;
1428
+ min-width: 0;
1429
+ overflow: hidden;
1430
+ }
1431
+
1376
1432
  .fm-grid-wrap {
1377
1433
  min-height: 400px;
1434
+ padding: 12px;
1435
+ width: 100%;
1436
+ max-width: 100%;
1437
+ box-sizing: border-box;
1438
+ overflow-x: hidden;
1439
+ }
1440
+
1441
+ .fm-grid.view-grid {
1442
+ grid-template-columns: repeat(auto-fill, minmax(135px, 1fr));
1443
+ gap: 10px;
1444
+ width: 100%;
1445
+ max-width: 100%;
1446
+ }
1447
+
1448
+ .fm-item {
1449
+ -webkit-tap-highlight-color: transparent;
1450
+ width: 100%;
1451
+ max-width: 100%;
1452
+ min-width: 0;
1453
+ box-sizing: border-box;
1454
+ }
1455
+
1456
+ .fm-item-details {
1457
+ width: 100%;
1458
+ max-width: 100%;
1459
+ min-width: 0;
1460
+ overflow: hidden;
1461
+ }
1462
+
1463
+ .fm-item-name {
1464
+ width: 100%;
1465
+ max-width: 100%;
1466
+ min-width: 0;
1467
+ overflow: hidden;
1468
+ text-overflow: ellipsis;
1469
+ white-space: nowrap;
1470
+ }
1471
+
1472
+ .fm-item:active {
1473
+ transform: scale(0.98);
1474
+ background: var(--primary-light-color-ml);
1475
+ }
1476
+
1477
+ .fm-item-menu {
1478
+ opacity: 1;
1479
+ background: rgba(255, 255, 255, 0.92);
1480
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
1481
+ }
1482
+
1483
+ .fm-star.on {
1484
+ opacity: 1;
1378
1485
  }
1379
1486
  }
1380
1487
 
1381
1488
  @media (max-width: 640px) {
1489
+ .page-header {
1490
+ padding: 0.75rem 1rem;
1491
+ margin-bottom: 0.75rem;
1492
+ border-radius: var(--radius);
1493
+ width: 100%;
1494
+ max-width: 100%;
1495
+ }
1496
+
1382
1497
  .page-header-row {
1383
1498
  flex-direction: column;
1384
- align-items: flex-start;
1499
+ align-items: stretch;
1500
+ gap: 10px;
1501
+ width: 100%;
1385
1502
  }
1386
1503
 
1387
1504
  .page-actions {
1388
1505
  width: 100%;
1389
- justify-content: flex-start;
1506
+ display: flex;
1507
+ justify-content: stretch;
1508
+ gap: 8px;
1509
+ }
1510
+
1511
+ .page-actions .btn {
1512
+ flex: 1 1 0;
1513
+ min-width: 0;
1514
+ justify-content: center;
1515
+ padding: 7px 8px;
1516
+ font-size: 12px;
1390
1517
  }
1391
1518
 
1392
1519
  .fm-toolbar {
1393
1520
  flex-direction: column;
1394
1521
  align-items: stretch;
1522
+ gap: 8px;
1523
+ padding: 8px 10px;
1524
+ width: 100%;
1525
+ max-width: 100%;
1526
+ overflow: hidden;
1527
+ }
1528
+
1529
+ .fm-breadcrumb {
1530
+ max-width: 100%;
1531
+ overflow-x: auto;
1532
+ white-space: nowrap;
1533
+ padding-bottom: 2px;
1534
+ scrollbar-width: none;
1535
+ }
1536
+
1537
+ .fm-breadcrumb::-webkit-scrollbar {
1538
+ display: none;
1395
1539
  }
1396
1540
 
1397
1541
  .fm-toolbar-actions {
1398
1542
  width: 100%;
1399
- justify-content: space-between;
1400
- flex-wrap: wrap;
1543
+ display: flex;
1544
+ flex-direction: column;
1545
+ align-items: stretch;
1546
+ gap: 8px;
1547
+ max-width: 100%;
1548
+ }
1549
+
1550
+ .quick-filter-group {
1551
+ width: 100%;
1552
+ display: flex;
1553
+ overflow-x: auto;
1554
+ scrollbar-width: none;
1555
+ max-width: 100%;
1556
+ }
1557
+
1558
+ .quick-filter-group::-webkit-scrollbar {
1559
+ display: none;
1560
+ }
1561
+
1562
+ .quick-filter-btn {
1563
+ flex: 1 1 0;
1564
+ text-align: center;
1565
+ padding: 5px 6px;
1566
+ font-size: 11.5px;
1567
+ white-space: nowrap;
1401
1568
  }
1402
1569
 
1403
1570
  .search-box {
1404
1571
  width: 100%;
1572
+ max-width: 100%;
1573
+ }
1574
+
1575
+ .search-box input {
1576
+ width: 100%;
1577
+ box-sizing: border-box;
1578
+ }
1579
+
1580
+ /* Grid Mode on Small Phones */
1581
+ .fm-grid.view-grid {
1582
+ grid-template-columns: repeat(2, minmax(0, 1fr));
1583
+ gap: 8px;
1584
+ width: 100%;
1585
+ max-width: 100%;
1586
+ }
1587
+
1588
+ .fm-grid.view-grid .fm-item {
1589
+ padding: 8px;
1590
+ border-radius: var(--radius-md);
1591
+ gap: 6px;
1592
+ width: 100%;
1593
+ max-width: 100%;
1594
+ min-width: 0;
1595
+ }
1596
+
1597
+ .fm-grid.view-grid .fm-item-icon {
1598
+ aspect-ratio: 16 / 11;
1599
+ width: 100%;
1600
+ }
1601
+
1602
+ .fm-item-name {
1603
+ font-size: 12px;
1604
+ line-height: 1.35;
1605
+ }
1606
+
1607
+ .fm-item-meta {
1608
+ font-size: 10px;
1609
+ }
1610
+
1611
+ .fm-item-menu {
1612
+ top: 5px;
1613
+ right: 5px;
1614
+ width: 26px;
1615
+ height: 26px;
1616
+ }
1617
+
1618
+ .fm-star {
1619
+ top: 5px;
1620
+ left: 5px;
1621
+ width: 22px;
1622
+ height: 22px;
1623
+ }
1624
+
1625
+ /* List Mode on Mobile */
1626
+ .fm-grid.view-list {
1627
+ gap: 6px;
1628
+ width: 100%;
1629
+ max-width: 100%;
1405
1630
  }
1406
1631
 
1407
1632
  .fm-grid.view-list .fm-item {
1408
- grid-template-columns: 32px 1fr 32px;
1633
+ display: flex;
1634
+ align-items: center;
1635
+ gap: 8px;
1636
+ padding: 8px 10px;
1637
+ border-radius: var(--radius-md);
1638
+ width: 100%;
1639
+ max-width: 100%;
1640
+ min-width: 0;
1641
+ }
1642
+
1643
+ .fm-grid.view-list .fm-item-checkbox {
1644
+ display: flex;
1645
+ align-items: center;
1646
+ justify-content: center;
1647
+ flex-shrink: 0;
1648
+ }
1649
+
1650
+ .fm-grid.view-list .fm-item-icon {
1651
+ width: 32px;
1652
+ height: 32px;
1653
+ flex-shrink: 0;
1654
+ border-radius: var(--radius-sm);
1655
+ }
1656
+
1657
+ .fm-grid.view-list .fm-item-name {
1658
+ flex: 1 1 auto;
1659
+ min-width: 0;
1660
+ font-size: 12.5px;
1661
+ font-weight: 600;
1662
+ color: var(--dark-color-ml);
1663
+ overflow: hidden;
1664
+ text-overflow: ellipsis;
1665
+ white-space: nowrap;
1409
1666
  }
1410
1667
 
1411
1668
  .fm-grid.view-list .fm-item-size,
1412
1669
  .fm-grid.view-list .fm-item-date,
1413
- .fm-grid.view-list .fm-item-checkbox {
1670
+ .fm-grid.view-list .fm-star-wrap {
1414
1671
  display: none;
1415
1672
  }
1416
1673
 
1674
+ .fm-grid.view-list .fm-item-menu {
1675
+ flex-shrink: 0;
1676
+ width: 28px;
1677
+ height: 28px;
1678
+ display: flex;
1679
+ align-items: center;
1680
+ justify-content: center;
1681
+ opacity: 1;
1682
+ color: var(--gray-color-ml);
1683
+ border-radius: 6px;
1684
+ background: transparent;
1685
+ box-shadow: none;
1686
+ }
1687
+
1688
+ .fm-grid.view-list .fm-item-menu:active {
1689
+ background: var(--light-color-ml);
1690
+ }
1691
+
1417
1692
  .fm-list-header {
1418
1693
  display: none;
1419
1694
  }
@@ -56,13 +56,8 @@ export function isLoggedIn() {
56
56
  return Boolean(token && server_url);
57
57
  }
58
58
 
59
- export function getStoredToken() {
60
- return readConfig().token || null;
61
- }
62
-
63
- export function getStoredServerUrl() {
64
- return readConfig().server_url || null;
65
- }
59
+ export const getStoredToken = () => readConfig().token || null;
60
+ export const getStoredServerUrl = () => readConfig().server_url || null;
66
61
 
67
62
  export async function deviceFlowLogin({ server_url, profile_key }) {
68
63
  const baseUrl = String(server_url).replace(/\/+$/, '');
@@ -87,26 +82,21 @@ export async function deviceFlowLogin({ server_url, profile_key }) {
87
82
  throw err;
88
83
  }
89
84
 
90
- const verification_url = deviceResp.verification_url;
91
- const user_code = deviceResp.user_code;
92
- const device_code = deviceResp.device_code;
93
- const expires_in = deviceResp.expires_in;
94
- const interval = deviceResp.interval;
95
- const profile = deviceResp.profile;
85
+ const { verification_url = '', user_code = '', device_code, expires_in = 900, interval = 5, profile } = deviceResp;
96
86
 
97
87
  console.log('\n┌─────────────────────────────────────────────────────────┐');
98
88
  console.log('│ │');
99
89
  console.log('│ 🌐 Open this URL in your browser to approve: │');
100
- console.log(`│ ${(verification_url || '').padEnd(53)}│`);
90
+ console.log(`│ ${verification_url.padEnd(53)}│`);
101
91
  console.log('│ │');
102
- console.log(`│ 🔢 Or enter this code: ${(user_code || '').padEnd(30)}│`);
92
+ console.log(`│ 🔢 Or enter this code: ${user_code.padEnd(30)}│`);
103
93
  console.log('│ │');
104
94
  console.log('└─────────────────────────────────────────────────────────┘\n');
105
95
 
106
96
  if (profile) console.log(` Profile: ${profile.name} (scope: ${profile.scope || 'full'})`);
107
- console.log(` Waiting for approval (expires in ${Math.floor((expires_in || 900) / 60)} minutes)...\n`);
97
+ console.log(` Waiting for approval (expires in ${Math.floor(expires_in / 60)} minutes)...\n`);
108
98
 
109
- const pollInterval = (interval || 5) * 1000;
99
+ const pollInterval = interval * 1000;
110
100
  const deadline = Date.now() + MAX_POLL_TIME_MS;
111
101
  let attempts = 0;
112
102
 
@@ -129,7 +119,7 @@ export async function deviceFlowLogin({ server_url, profile_key }) {
129
119
  console.log(` Token prefix: ${token_prefix}...`);
130
120
  return {
131
121
  token: pollBody.token,
132
- token_prefix: token_prefix,
122
+ token_prefix,
133
123
  username: pollBody.username,
134
124
  profile: profile || null,
135
125
  };
@@ -148,3 +138,4 @@ export async function deviceFlowLogin({ server_url, profile_key }) {
148
138
 
149
139
  throw new Error('Login timed out. Please try again.');
150
140
  }
141
+
@@ -14,7 +14,6 @@ dotenv.config();
14
14
  const debugConfig = createLogger('config');
15
15
  const require = createRequire(import.meta.url);
16
16
 
17
- // Load mbkbucket package.json
18
17
  let packageJson;
19
18
  try {
20
19
  packageJson = require("mbkbucket/package.json");
@@ -22,7 +21,6 @@ try {
22
21
  packageJson = require("../../../package.json");
23
22
  }
24
23
 
25
- // Parent project version
26
24
  let appVersion = "unknown";
27
25
  try {
28
26
  appVersion = require("../../../../package.json")?.version ?? require(`${process.cwd()}/package.json`)?.version ?? "unknown";
@@ -1,5 +1,6 @@
1
1
  const TRUE_VALUES = new Set(['true', 't', '1', 'yes', 'y', 'on']);
2
2
  const FALSE_VALUES = new Set(['false', 'f', '0', 'no', 'n', 'off']);
3
+ const REQUIRED_BUCKET_FIELDS = ['BUCKET_NAME', 'ACCESS_KEY_ID', 'SECRET_ACCESS_KEY', 'ENDPOINT'];
3
4
 
4
5
  export function normalizeBooleanLike(value) {
5
6
  if (typeof value === 'boolean') return value;
@@ -56,14 +57,12 @@ export function parseAndValidateBucketConnection(rawValue) {
56
57
  throw new Error('BucketConnection must be a non-empty object');
57
58
  }
58
59
 
59
- const requiredFields = ['BUCKET_NAME', 'ACCESS_KEY_ID', 'SECRET_ACCESS_KEY', 'ENDPOINT'];
60
-
61
60
  for (const [bucketName, config] of Object.entries(parsed)) {
62
61
  if (!config || typeof config !== 'object' || Array.isArray(config)) {
63
62
  throw new Error(`Bucket '${bucketName}' configuration must be an object, not a string. Remove quotes around the inner JSON object.`);
64
63
  }
65
64
 
66
- const missingFields = requiredFields.filter(field => !config[field]);
65
+ const missingFields = REQUIRED_BUCKET_FIELDS.filter(field => !config[field]);
67
66
  if (missingFields.length) {
68
67
  throw new Error(`Bucket '${bucketName}' is missing required fields: ${missingFields.join(', ')}`);
69
68
  }
@@ -71,3 +70,4 @@ export function parseAndValidateBucketConnection(rawValue) {
71
70
 
72
71
  return parsed;
73
72
  }
73
+
@@ -292,7 +292,7 @@ export async function createFolder(req, res) {
292
292
  const effectivePrefix = resolvePrefix(req, res, prefix);
293
293
  if (effectivePrefix === null) return;
294
294
 
295
- const cleanName = String(folderName).replace(/^\/+/, '').replace(/\/+$/, '');
295
+ const cleanName = String(folderName).replace(/^\/+|\/+$/g, '');
296
296
  const cleanPrefix = effectivePrefix.replace(/\/+$/, '');
297
297
  const folderKey = (effectivePrefix && !cleanName.startsWith(effectivePrefix))
298
298
  ? `${cleanPrefix}/${cleanName}/`
@@ -15,7 +15,7 @@ export function renderBucketDashboard(req, res) {
15
15
 
16
16
  const mainLayoutPath = path.resolve(process.cwd(), "views", "layouts", "main");
17
17
  const hasMainLayout = fs.existsSync(`${mainLayoutPath}.handlebars`) || fs.existsSync(mainLayoutPath);
18
- const portalLayout = (isPortal && hasMainLayout) ? mainLayoutPath : false;
18
+ const portalLayout = (isPortal && hasMainLayout) && mainLayoutPath;
19
19
 
20
20
  let selectedBucket;
21
21
  let error = req.query.error;
@@ -33,10 +33,10 @@ export function renderBucketDashboard(req, res) {
33
33
  isDefault: name === configuredDefaultBucket
34
34
  }));
35
35
 
36
- renderPage(req, res, bucketfilePath, portalLayout ? true : false, {
36
+ renderPage(req, res, bucketfilePath, Boolean(portalLayout), {
37
37
  page: "Admin Bucket",
38
38
  helpers: commonHandlebarsHelpers,
39
- ...(portalLayout ? { layout: portalLayout } : { layout: false }),
39
+ layout: portalLayout || false,
40
40
  bucketvar: selectedBucket,
41
41
  selectedBucket,
42
42
  bucketOptions,
@@ -46,3 +46,4 @@ export function renderBucketDashboard(req, res) {
46
46
  availableBuckets: allBuckets
47
47
  });
48
48
  }
49
+
@@ -19,10 +19,9 @@ async function getInfoData() {
19
19
  * Render the mbkbucket info page (Handlebars).
20
20
  */
21
21
  export async function infoPage(req, res) {
22
- const data = await getInfoData();
23
22
  renderPage(req, res, "mbkbucket_info.handlebars", false, {
24
23
  page: "MBK Bucket Info",
25
- ...data
24
+ ...(await getInfoData())
26
25
  });
27
26
  }
28
27
 
@@ -60,9 +60,7 @@ async function serveFileInline(req, res, keyToUse, { noindex = true, bucketName,
60
60
  return res.status(500).json({ message: 'Failed to fetch file metadata', error: err.message });
61
61
  }
62
62
 
63
- if (!meta?.exists) {
64
- return res.status(404).json({ message: 'File not found', key: keyToUse });
65
- }
63
+ if (!meta?.exists) return res.status(404).json({ message: 'File not found', key: keyToUse });
66
64
 
67
65
  const etag = `"${meta.LastModified?.getTime() || Date.now()}-${meta.ContentLength || 0}"`;
68
66
  if (ifNoneMatch === etag) return res.status(304).end();
@@ -2,17 +2,14 @@
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
4
  */
5
- export function wrap(handler, onError) {
6
- return (req, res, next) => {
7
- Promise.resolve(handler(req, res, next)).catch(err => (onError ? onError(err, req, res, next) : next(err)));
8
- };
9
- }
5
+ export const wrap = (handler, onError) => (req, res, next) =>
6
+ Promise.resolve(handler(req, res, next)).catch(err => onError ? onError(err, req, res, next) : next(err));
10
7
 
11
8
  /**
12
9
  * Combines wrap() with a standard JSON error response for API routes.
13
10
  */
14
- export function wrapApi(handler, fallbackMessage = 'Request failed') {
15
- return wrap(handler, (err, _req, res) => {
11
+ export const wrapApi = (handler, fallbackMessage = 'Request failed') =>
12
+ wrap(handler, (err, _req, res) => {
16
13
  console.error(`[mbkbucket] ${err.stack || err.message}`);
17
14
  res.status(500).json({
18
15
  success: false,
@@ -20,4 +17,3 @@ export function wrapApi(handler, fallbackMessage = 'Request failed') {
20
17
  error: err.message || fallbackMessage,
21
18
  });
22
19
  });
23
- }
@@ -2,14 +2,11 @@ import { createLogger } from "#logger";
2
2
  import { isJsonRequest, sendError, sanitizeErrorDetails } from "mbkauthe";
3
3
 
4
4
  const debugServer = createLogger('server');
5
-
6
5
  const BASE_ERROR_DATA = { layout: false, pagename: "Home", page: "/" };
7
6
 
8
7
  export function notFoundHandler(req, res, next) {
9
8
  const url = req.originalUrl || req.url || '';
10
- if (!url.startsWith('/mbkbucket') && typeof next === 'function') {
11
- return next();
12
- }
9
+ if (!url.startsWith('/mbkbucket') && typeof next === 'function') return next();
13
10
 
14
11
  debugServer("Path not found: %s %s", req.method, req.url);
15
12
 
@@ -31,7 +28,6 @@ export function notFoundHandler(req, res, next) {
31
28
 
32
29
  export function globalErrorHandler(err, req, res, _next) {
33
30
  console.error(`[mbkbucket] ${err.stack || err}`);
34
-
35
31
  const statusCode = Number(err.status || err.statusCode || 500);
36
32
 
37
33
  if (isJsonRequest(req)) {
@@ -49,6 +45,6 @@ export function globalErrorHandler(err, req, res, _next) {
49
45
  code: statusCode,
50
46
  error: statusCode >= 500 ? "Internal app Error" : (err.name || "Client Error"),
51
47
  message: err.message || "An unexpected error occurred on the app.",
52
- ...(sanitizedDetails ? { details: sanitizedDetails } : {}),
48
+ ...(sanitizedDetails && { details: sanitizedDetails }),
53
49
  });
54
50
  }
@@ -1,7 +1,7 @@
1
1
  import rateLimit from "express-rate-limit";
2
2
 
3
3
  export const generalLimiter = rateLimit({
4
- windowMs: 2 * 60 * 1000, // 2 minutes
4
+ windowMs: 2 * 60 * 1000,
5
5
  max: 150,
6
6
  standardHeaders: true,
7
7
  legacyHeaders: false,
@@ -11,7 +11,7 @@ export const generalLimiter = rateLimit({
11
11
  });
12
12
 
13
13
  export const pviewRateLimit = rateLimit({
14
- windowMs: 60 * 1000, // 1 minute
14
+ windowMs: 60 * 1000,
15
15
  max: 10,
16
16
  standardHeaders: 'draft-7',
17
17
  legacyHeaders: false,
@@ -4,12 +4,7 @@
4
4
  */
5
5
 
6
6
  const BLOCKED_UA_PATTERNS = /curl|wget|python-requests|python-urllib|go-http|scrapy|libwww|java\/|bot|crawl|spider|headless|phantom|selenium|puppeteer|playwright|postman|insomnia|httpie/i;
7
-
8
- const SUSPICIOUS_HEADERS = [
9
- 'x-devtools-emulate-network-conditions-client-id',
10
- 'x-chromedriver-clientid',
11
- 'x-automated-tool'
12
- ];
7
+ const SUSPICIOUS_HEADERS = ['x-devtools-emulate-network-conditions-client-id', 'x-chromedriver-clientid', 'x-automated-tool'];
13
8
 
14
9
  export function pviewSecurity(req, res, next) {
15
10
  const ua = req.headers['user-agent'] || '';
@@ -22,6 +17,5 @@ export function pviewSecurity(req, res, next) {
22
17
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
23
18
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
24
19
  res.removeHeader('Access-Control-Allow-Origin');
25
-
26
20
  next();
27
21
  }
@@ -27,13 +27,12 @@ export function createApiRoutes({
27
27
  deleteAuthorization,
28
28
  } = {}) {
29
29
  const apiRouter = express.Router();
30
- const defaultAuthorization = authorization || sessRole('superadmin');
31
- const viewAuth = viewAuthorization || defaultAuthorization;
32
- const uploadAuth = uploadAuthorization || defaultAuthorization;
33
- const deleteAuth = deleteAuthorization || defaultAuthorization;
30
+ const defaultAuth = authorization ?? sessRole('superadmin');
31
+ const viewAuth = viewAuthorization ?? defaultAuth;
32
+ const uploadAuth = uploadAuthorization ?? defaultAuth;
33
+ const deleteAuth = deleteAuthorization ?? defaultAuth;
34
34
 
35
35
  apiRouter.use(requireBucketApi);
36
-
37
36
  apiRouter.post('/api/cleanup-uploads', deleteAuth, cleanupUploads);
38
37
  apiRouter.get('/api/incomplete-uploads', viewAuth, listIncompleteUploads);
39
38
  apiRouter.get('/api/files', viewAuth, listFiles);