nextly 0.0.1 → 0.0.2-alpha.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/LICENSE +22 -0
- package/README.md +122 -0
- package/dist/_dts-chunks/collections-handler.d-DjgO74Wt.d.ts +20540 -0
- package/dist/_dts-chunks/config.d-DNwsDnjs.d.ts +2589 -0
- package/dist/_dts-chunks/define-component.d-BUgTHmt3.d.ts +1149 -0
- package/dist/_dts-chunks/image-processor.d-OO1PmMrv.d.ts +335 -0
- package/dist/_dts-chunks/index.d-axCAzZ7m.d.ts +17842 -0
- package/dist/_dts-chunks/media.d-DjDOZo4B.d.ts +117 -0
- package/dist/_dts-chunks/on-error.d-CHIKWNxd.d.ts +38 -0
- package/dist/_dts-chunks/storage.d-BUhQ2we_.d.ts +404 -0
- package/dist/actions/index.d.ts +239 -0
- package/dist/actions/index.mjs +281 -0
- package/dist/api/auth-state.d.ts +5 -0
- package/dist/api/auth-state.mjs +131 -0
- package/dist/api/collections-schema-detail.d.ts +56 -0
- package/dist/api/collections-schema-detail.mjs +244 -0
- package/dist/api/collections-schema-export.d.ts +56 -0
- package/dist/api/collections-schema-export.mjs +129 -0
- package/dist/api/collections-schema.d.ts +59 -0
- package/dist/api/collections-schema.mjs +207 -0
- package/dist/api/components-detail.d.ts +50 -0
- package/dist/api/components-detail.mjs +132 -0
- package/dist/api/components.d.ts +69 -0
- package/dist/api/components.mjs +144 -0
- package/dist/api/email-providers-default.d.ts +40 -0
- package/dist/api/email-providers-default.mjs +75 -0
- package/dist/api/email-providers-detail.d.ts +81 -0
- package/dist/api/email-providers-detail.mjs +109 -0
- package/dist/api/email-providers-test.d.ts +43 -0
- package/dist/api/email-providers-test.mjs +114 -0
- package/dist/api/email-providers.d.ts +69 -0
- package/dist/api/email-providers.mjs +110 -0
- package/dist/api/email-send-template.d.ts +41 -0
- package/dist/api/email-send-template.mjs +58 -0
- package/dist/api/email-send.d.ts +42 -0
- package/dist/api/email-send.mjs +58 -0
- package/dist/api/email-templates-detail.d.ts +74 -0
- package/dist/api/email-templates-detail.mjs +112 -0
- package/dist/api/email-templates-layout.d.ts +55 -0
- package/dist/api/email-templates-layout.mjs +92 -0
- package/dist/api/email-templates-preview.d.ts +48 -0
- package/dist/api/email-templates-preview.mjs +93 -0
- package/dist/api/email-templates.d.ts +61 -0
- package/dist/api/email-templates.mjs +118 -0
- package/dist/api/health.d.ts +68 -0
- package/dist/api/health.mjs +67 -0
- package/dist/api/index.d.ts +54 -0
- package/dist/api/index.mjs +16 -0
- package/dist/api/media-bulk.d.ts +74 -0
- package/dist/api/media-bulk.mjs +196 -0
- package/dist/api/media-folders.d.ts +112 -0
- package/dist/api/media-folders.mjs +187 -0
- package/dist/api/media-handlers.d.ts +102 -0
- package/dist/api/media-handlers.mjs +437 -0
- package/dist/api/media.d.ts +117 -0
- package/dist/api/media.mjs +242 -0
- package/dist/api/singles-detail.d.ts +87 -0
- package/dist/api/singles-detail.mjs +170 -0
- package/dist/api/singles-schema-detail.d.ts +54 -0
- package/dist/api/singles-schema-detail.mjs +182 -0
- package/dist/api/singles.d.ts +34 -0
- package/dist/api/singles.mjs +94 -0
- package/dist/api/storage-upload-url.d.ts +48 -0
- package/dist/api/storage-upload-url.mjs +202 -0
- package/dist/api/uploads.d.ts +109 -0
- package/dist/api/uploads.mjs +359 -0
- package/dist/auth/index.d.ts +425 -0
- package/dist/auth/index.mjs +199 -0
- package/dist/boot-apply-PQSYLDIN.mjs +7 -0
- package/dist/chunk-2OALJTK6.mjs +489 -0
- package/dist/chunk-2Q2SX2CS.mjs +365 -0
- package/dist/chunk-2TFX4ND3.mjs +13 -0
- package/dist/chunk-2TWPDSYD.mjs +87 -0
- package/dist/chunk-2W3DVD7S.mjs +647 -0
- package/dist/chunk-2ZFKXPQM.mjs +88 -0
- package/dist/chunk-3FA7FKAV.mjs +832 -0
- package/dist/chunk-3NZ2KMBL.mjs +58 -0
- package/dist/chunk-4MJLT6PZ.mjs +0 -0
- package/dist/chunk-56WO4WX7.mjs +0 -0
- package/dist/chunk-5APFUGAD.mjs +89 -0
- package/dist/chunk-5HMZ644B.mjs +108 -0
- package/dist/chunk-67GXH6PR.mjs +32 -0
- package/dist/chunk-6JNEPWRW.mjs +14368 -0
- package/dist/chunk-6NFHQIJD.mjs +45 -0
- package/dist/chunk-7P6ASYW6.mjs +9 -0
- package/dist/chunk-A3WPLSDT.mjs +1364 -0
- package/dist/chunk-AGJ6F2T3.mjs +144 -0
- package/dist/chunk-AK6Z23OX.mjs +1464 -0
- package/dist/chunk-APKKRD2G.mjs +102 -0
- package/dist/chunk-B2GV2BWH.mjs +73 -0
- package/dist/chunk-D5HQBNUB.mjs +74 -0
- package/dist/chunk-DNNG377Z.mjs +204 -0
- package/dist/chunk-DP3G27G5.mjs +135 -0
- package/dist/chunk-DV6WVX2Q.mjs +0 -0
- package/dist/chunk-DXGGXIUZ.mjs +57 -0
- package/dist/chunk-EGXBZCGC.mjs +943 -0
- package/dist/chunk-ERCNLX3V.mjs +176 -0
- package/dist/chunk-FQULBZ53.mjs +850 -0
- package/dist/chunk-G2AA4QLC.mjs +262 -0
- package/dist/chunk-GDBJ5JCU.mjs +488 -0
- package/dist/chunk-GJNSJU4S.mjs +19 -0
- package/dist/chunk-GZ6DCQKC.mjs +69 -0
- package/dist/chunk-H26B4FYG.mjs +167 -0
- package/dist/chunk-I4JMR3UR.mjs +21 -0
- package/dist/chunk-INV7QKLG.mjs +508 -0
- package/dist/chunk-IUDOC7N7.mjs +46 -0
- package/dist/chunk-IZWPRDC3.mjs +206 -0
- package/dist/chunk-KIMNCZGV.mjs +15 -0
- package/dist/chunk-L6HW2DA7.mjs +15 -0
- package/dist/chunk-LAZXX4HR.mjs +100 -0
- package/dist/chunk-LDKCUMHK.mjs +95 -0
- package/dist/chunk-LRXMECUA.mjs +0 -0
- package/dist/chunk-M52VMPGA.mjs +119 -0
- package/dist/chunk-MGUWEEI6.mjs +160 -0
- package/dist/chunk-NRUWQ5Z7.mjs +419 -0
- package/dist/chunk-NSEFNNU4.mjs +25360 -0
- package/dist/chunk-NTHVDFGO.mjs +138 -0
- package/dist/chunk-O3QHXMOX.mjs +3166 -0
- package/dist/chunk-P7NH2OSC.mjs +2605 -0
- package/dist/chunk-PKMABBB5.mjs +184 -0
- package/dist/chunk-PWS6XGJK.mjs +76 -0
- package/dist/chunk-R6JJQHFC.mjs +20 -0
- package/dist/chunk-RJLLGGPG.mjs +0 -0
- package/dist/chunk-SBACDPNX.mjs +689 -0
- package/dist/chunk-TO5AFLVQ.mjs +124 -0
- package/dist/chunk-TS7GHTG2.mjs +5436 -0
- package/dist/chunk-UJ2IMJ4W.mjs +133 -0
- package/dist/chunk-UOP63Q54.mjs +102 -0
- package/dist/chunk-UUOFWCM6.mjs +78 -0
- package/dist/chunk-V4EQTOA4.mjs +893 -0
- package/dist/chunk-VJ66NCL4.mjs +193 -0
- package/dist/chunk-VQJQHVEV.mjs +29 -0
- package/dist/chunk-VTJADRO3.mjs +141 -0
- package/dist/chunk-VWF3JO32.mjs +0 -0
- package/dist/chunk-W4MGXIRR.mjs +27 -0
- package/dist/chunk-W5KKPZT5.mjs +1204 -0
- package/dist/chunk-WD34YQ6T.mjs +381 -0
- package/dist/chunk-WZBYMYVW.mjs +14 -0
- package/dist/chunk-X23WKS3Z.mjs +50 -0
- package/dist/chunk-X7TXCYYN.mjs +6496 -0
- package/dist/chunk-XGI4EMS3.mjs +140 -0
- package/dist/chunk-XZKLBMN6.mjs +1153 -0
- package/dist/chunk-YB7INWPY.mjs +0 -0
- package/dist/chunk-YV4Y7SDL.mjs +83 -0
- package/dist/chunk-YZNBLFIW.mjs +1688 -0
- package/dist/chunk-YZZCTONM.mjs +263 -0
- package/dist/chunk-ZE6A3FYH.mjs +289 -0
- package/dist/cli/nextly.mjs +68 -0
- package/dist/cli/utils/index.d.ts +449 -0
- package/dist/cli/utils/index.mjs +49 -0
- package/dist/component-schema-service-5577KVW6.mjs +11 -0
- package/dist/config-loader-23YEMC3Z.mjs +23 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.mjs +109 -0
- package/dist/container-ORGFGYSZ.mjs +9 -0
- package/dist/database/index.d.ts +12 -0
- package/dist/database/index.mjs +40 -0
- package/dist/database/seeders/index.d.ts +93 -0
- package/dist/database/seeders/index.mjs +47 -0
- package/dist/db-sync-demote-LJGKLB3S.mjs +117 -0
- package/dist/db-sync-promote-B26VSYQF.mjs +113 -0
- package/dist/dev-reload-broadcaster-B73IQ53V.mjs +25 -0
- package/dist/dist-M2NOU37V.mjs +19 -0
- package/dist/drizzle-kit-lazy-D2M2PXR2.mjs +13 -0
- package/dist/dynamic-collection-schema-service-IEXTPIZ7.mjs +8 -0
- package/dist/errors/index.d.ts +159 -0
- package/dist/errors/index.mjs +10 -0
- package/dist/factory-IWMBKUJM.mjs +15 -0
- package/dist/first-run-QIVKWJIF.mjs +63 -0
- package/dist/fresh-push-NR67DC3R.mjs +8 -0
- package/dist/index.d.ts +4175 -0
- package/dist/index.mjs +1336 -0
- package/dist/local-plugin-PTET4NAT.mjs +7 -0
- package/dist/logger-NU46DXNY.mjs +15 -0
- package/dist/logger-YE4TC7ZN.mjs +9 -0
- package/dist/migration-journal-EP532Y4L.mjs +139 -0
- package/dist/migrations/mysql/0000_eager_sentry.sql +174 -0
- package/dist/migrations/mysql/0001_soft_giant_girl.sql +27 -0
- package/dist/migrations/mysql/0002_media_table.sql +24 -0
- package/dist/migrations/mysql/0003_dynamic_singles.sql +37 -0
- package/dist/migrations/mysql/0004_dynamic_components.sql +35 -0
- package/dist/migrations/mysql/0005_user_management_tables.sql +92 -0
- package/dist/migrations/mysql/0006_api_keys.sql +36 -0
- package/dist/migrations/mysql/0007_general_settings.sql +20 -0
- package/dist/migrations/mysql/0008_site_settings_logo_url.sql +9 -0
- package/dist/migrations/mysql/0009_activity_log.sql +30 -0
- package/dist/migrations/mysql/0010_site_settings_sidebar.sql +13 -0
- package/dist/migrations/mysql/0011_missing_tables_and_columns.sql +54 -0
- package/dist/migrations/mysql/0012_image_sizes_and_focal_point.sql +30 -0
- package/dist/migrations/mysql/0012_media_folders.sql +43 -0
- package/dist/migrations/mysql/0013_user_brute_force_protection.sql +31 -0
- package/dist/migrations/mysql/0014_email_template_attachments.sql +12 -0
- package/dist/migrations/mysql/0015_media_uploaded_by_nullable.sql +15 -0
- package/dist/migrations/mysql/20260429_000000_000_initial_journal.sql +22 -0
- package/dist/migrations/mysql/20260501_000000_journal_batch.sql +17 -0
- package/dist/migrations/mysql/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/mysql/20260504_000000_nextly_meta.sql +21 -0
- package/dist/migrations/mysql/meta/0000_snapshot.json +1005 -0
- package/dist/migrations/mysql/meta/0001_snapshot.json +1099 -0
- package/dist/migrations/mysql/meta/_journal.json +41 -0
- package/dist/migrations/postgresql/0000_misty_king_bedlam.sql +169 -0
- package/dist/migrations/postgresql/0001_perpetual_captain_marvel.sql +8 -0
- package/dist/migrations/postgresql/0002_sad_spectrum.sql +16 -0
- package/dist/migrations/postgresql/0003_hesitant_ultron.sql +17 -0
- package/dist/migrations/postgresql/0004_media_table.sql +24 -0
- package/dist/migrations/postgresql/0005_media_folders.sql +36 -0
- package/dist/migrations/postgresql/0006_dynamic_collections_update.sql +50 -0
- package/dist/migrations/postgresql/0007_dynamic_singles.sql +38 -0
- package/dist/migrations/postgresql/0008_dynamic_components.sql +37 -0
- package/dist/migrations/postgresql/0009_user_management_tables.sql +95 -0
- package/dist/migrations/postgresql/0010_api_keys.sql +34 -0
- package/dist/migrations/postgresql/0011_general_settings.sql +20 -0
- package/dist/migrations/postgresql/0012_site_settings_logo_url.sql +9 -0
- package/dist/migrations/postgresql/0013_activity_log.sql +29 -0
- package/dist/migrations/postgresql/0014_image_sizes_and_focal_point.sql +33 -0
- package/dist/migrations/postgresql/0014_site_settings_sidebar.sql +13 -0
- package/dist/migrations/postgresql/0015_user_brute_force_protection.sql +29 -0
- package/dist/migrations/postgresql/0016_email_template_attachments.sql +12 -0
- package/dist/migrations/postgresql/0017_media_uploaded_by_nullable.sql +15 -0
- package/dist/migrations/postgresql/20260429_000000_000_initial_journal.sql +24 -0
- package/dist/migrations/postgresql/20260501_000000_journal_batch.sql +17 -0
- package/dist/migrations/postgresql/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/postgresql/20260504_000000_nextly_meta.sql +22 -0
- package/dist/migrations/postgresql/meta/0000_snapshot.json +1286 -0
- package/dist/migrations/postgresql/meta/0001_snapshot.json +1407 -0
- package/dist/migrations/postgresql/meta/0002_snapshot.json +1552 -0
- package/dist/migrations/postgresql/meta/0003_snapshot.json +1695 -0
- package/dist/migrations/postgresql/meta/0010_snapshot.json +2345 -0
- package/dist/migrations/postgresql/meta/_journal.json +90 -0
- package/dist/migrations/sqlite/0000_api_keys.sql +34 -0
- package/dist/migrations/sqlite/0001_general_settings.sql +20 -0
- package/dist/migrations/sqlite/0002_site_settings_logo_url.sql +9 -0
- package/dist/migrations/sqlite/0003_activity_log.sql +29 -0
- package/dist/migrations/sqlite/0004_image_sizes_and_focal_point.sql +29 -0
- package/dist/migrations/sqlite/0004_site_settings_sidebar.sql +11 -0
- package/dist/migrations/sqlite/0005_user_brute_force_protection.sql +29 -0
- package/dist/migrations/sqlite/0006_email_template_attachments.sql +12 -0
- package/dist/migrations/sqlite/0007_media_uploaded_by_nullable.sql +111 -0
- package/dist/migrations/sqlite/20260429_000000_000_initial_journal.sql +24 -0
- package/dist/migrations/sqlite/20260501_000000_journal_batch.sql +19 -0
- package/dist/migrations/sqlite/20260501_000001_audit_log.sql +24 -0
- package/dist/migrations/sqlite/20260504_000000_nextly_meta.sql +21 -0
- package/dist/migrations/sqlite/20260505_000000_user_management_tables.sql +77 -0
- package/dist/next.d.ts +57 -0
- package/dist/next.mjs +55 -0
- package/dist/observability/index.d.ts +87 -0
- package/dist/observability/index.mjs +57 -0
- package/dist/permissions-3DZZQZMI.mjs +39 -0
- package/dist/pipeline-YOML7SWF.mjs +29 -0
- package/dist/preview-ZZTR3QGS.mjs +9 -0
- package/dist/program-PW6UB2ZC.mjs +5934 -0
- package/dist/reconcile-single-tables-7ENVXJGB.mjs +7 -0
- package/dist/register-SF6E6FVU.mjs +49 -0
- package/dist/reload-config-HWQ4G5MM.mjs +23 -0
- package/dist/resolve-single-table-name-JSOMUB3R.mjs +7 -0
- package/dist/routeHandler-UNMMJIBM.mjs +77 -0
- package/dist/runtime-schema-generator-NRA6A6Z6.mjs +8 -0
- package/dist/runtime.d.ts +120 -0
- package/dist/runtime.mjs +73 -0
- package/dist/schema-hash-FMMG6VPJ.mjs +13 -0
- package/dist/schema-registry-EQ36FZDP.mjs +7 -0
- package/dist/scripts/load-env.mjs +42 -0
- package/dist/storage/index.d.ts +566 -0
- package/dist/storage/index.mjs +45 -0
- package/dist/super-admin-G5ZK5F4T.mjs +39 -0
- package/dist/system-table-service-WGSRVEGT.mjs +17 -0
- package/dist/users-7KELGRYJ.mjs +38 -0
- package/package.json +308 -9
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
// src/storage/adapters/local-adapter.ts
|
|
2
|
+
import * as fs from "fs/promises";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
// src/storage/adapters/base-adapter.ts
|
|
6
|
+
var BaseStorageAdapter = class {
|
|
7
|
+
/**
|
|
8
|
+
* Get adapter info including capabilities.
|
|
9
|
+
*
|
|
10
|
+
* Default implementation that auto-detects capabilities by checking
|
|
11
|
+
* if getSignedUrl and getPresignedUploadUrl methods are implemented.
|
|
12
|
+
* Override in subclasses for more accurate capability reporting.
|
|
13
|
+
*
|
|
14
|
+
* @returns Adapter info with type, name, and capability flags
|
|
15
|
+
*/
|
|
16
|
+
getInfo() {
|
|
17
|
+
const hasSignedUrls = "getSignedUrl" in this && typeof this.getSignedUrl === "function";
|
|
18
|
+
const hasClientUploads = "getPresignedUploadUrl" in this && typeof this.getPresignedUploadUrl === "function";
|
|
19
|
+
return {
|
|
20
|
+
type: this.getType(),
|
|
21
|
+
name: this.constructor.name,
|
|
22
|
+
supportsSignedUrls: hasSignedUrls,
|
|
23
|
+
supportsClientUploads: hasClientUploads
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Sanitize filename to prevent directory traversal and storage issues.
|
|
28
|
+
*
|
|
29
|
+
* Security measures:
|
|
30
|
+
* - Remove path separators (/, \)
|
|
31
|
+
* - Keep only basename (no directories)
|
|
32
|
+
* - Replace problematic characters with hyphens
|
|
33
|
+
* - Preserve alphanumeric, dots, underscores, hyphens
|
|
34
|
+
*
|
|
35
|
+
* @param filename - Original filename to sanitize
|
|
36
|
+
* @returns Sanitized filename safe for storage
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* this.sanitizeFilename('../../../etc/passwd') // 'passwd'
|
|
41
|
+
* this.sanitizeFilename('my file (1).jpg') // 'my-file--1-.jpg'
|
|
42
|
+
* this.sanitizeFilename('photo.jpg') // 'photo.jpg'
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
sanitizeFilename(filename) {
|
|
46
|
+
const basename = filename.split(/[/\\]/).pop() || filename;
|
|
47
|
+
return basename.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Generate a unique storage key with date-based prefix.
|
|
51
|
+
*
|
|
52
|
+
* Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}
|
|
53
|
+
* This provides:
|
|
54
|
+
* - Unique keys via UUID to prevent collisions
|
|
55
|
+
* - Date-based organization for easier management
|
|
56
|
+
* - Readable filenames for debugging
|
|
57
|
+
*
|
|
58
|
+
* @param filename - Original filename (will be sanitized)
|
|
59
|
+
* @param folder - Optional folder/prefix for organizing uploads
|
|
60
|
+
* @returns Generated storage key
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```typescript
|
|
64
|
+
* this.generateKey('photo.jpg')
|
|
65
|
+
* // 'uploads/2026/01/abc-123-...-photo.jpg'
|
|
66
|
+
*
|
|
67
|
+
* this.generateKey('doc.pdf', 'documents')
|
|
68
|
+
* // 'documents/2026/01/abc-123-...-doc.pdf'
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
generateKey(filename, folder) {
|
|
72
|
+
const sanitized = this.sanitizeFilename(filename);
|
|
73
|
+
const uuid = crypto.randomUUID();
|
|
74
|
+
const date = /* @__PURE__ */ new Date();
|
|
75
|
+
const year = date.getFullYear();
|
|
76
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
77
|
+
const prefix = folder ? `${folder}/${year}/${month}` : `uploads/${year}/${month}`;
|
|
78
|
+
return `${prefix}/${uuid}-${sanitized}`;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/storage/adapters/local-adapter.ts
|
|
83
|
+
var gitignoreUpdated = false;
|
|
84
|
+
var LocalStorageAdapter = class extends BaseStorageAdapter {
|
|
85
|
+
basePath;
|
|
86
|
+
baseUrl;
|
|
87
|
+
constructor(config) {
|
|
88
|
+
super();
|
|
89
|
+
this.basePath = path.resolve(config.basePath);
|
|
90
|
+
this.baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Upload file to local disk.
|
|
94
|
+
* Creates directories as needed and writes the file buffer.
|
|
95
|
+
*/
|
|
96
|
+
async upload(buffer, options) {
|
|
97
|
+
const key = this.generateKey(options.filename, options.folder);
|
|
98
|
+
const fullPath = this.resolveAndValidate(key);
|
|
99
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
100
|
+
await fs.writeFile(fullPath, buffer);
|
|
101
|
+
await this.ensureGitignore();
|
|
102
|
+
return {
|
|
103
|
+
url: this.getPublicUrl(key),
|
|
104
|
+
path: key
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Delete file from local disk.
|
|
109
|
+
* Silently succeeds if the file doesn't exist.
|
|
110
|
+
*/
|
|
111
|
+
async delete(filePath) {
|
|
112
|
+
let fullPath;
|
|
113
|
+
try {
|
|
114
|
+
fullPath = this.resolveAndValidate(filePath);
|
|
115
|
+
} catch {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await fs.unlink(fullPath);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (err.code !== "ENOENT") {
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Bulk delete files from local disk.
|
|
128
|
+
* Uses parallel unlinks with Promise.allSettled for best performance.
|
|
129
|
+
*/
|
|
130
|
+
async bulkDelete(filePaths) {
|
|
131
|
+
const results = await Promise.allSettled(
|
|
132
|
+
filePaths.map(async (filePath) => {
|
|
133
|
+
await this.delete(filePath);
|
|
134
|
+
return filePath;
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
const successful = [];
|
|
138
|
+
const failed = [];
|
|
139
|
+
results.forEach((result, index) => {
|
|
140
|
+
if (result.status === "fulfilled") {
|
|
141
|
+
successful.push(filePaths[index]);
|
|
142
|
+
} else {
|
|
143
|
+
failed.push({
|
|
144
|
+
filePath: filePaths[index],
|
|
145
|
+
error: result.reason?.message || "Unknown error"
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return { successful, failed };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Check if file exists on local disk.
|
|
153
|
+
*/
|
|
154
|
+
async exists(filePath) {
|
|
155
|
+
try {
|
|
156
|
+
const fullPath = this.resolveAndValidate(filePath);
|
|
157
|
+
await fs.access(fullPath);
|
|
158
|
+
return true;
|
|
159
|
+
} catch {
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Get public URL for a file.
|
|
165
|
+
* Returns baseUrl + relative path for Next.js static file serving.
|
|
166
|
+
*/
|
|
167
|
+
getPublicUrl(filePath) {
|
|
168
|
+
const cleanPath = filePath.replace(/^\/+/, "");
|
|
169
|
+
return `${this.baseUrl}/${cleanPath}`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Get storage type identifier.
|
|
173
|
+
*/
|
|
174
|
+
getType() {
|
|
175
|
+
return "local";
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Read file contents from local disk.
|
|
179
|
+
* Returns the file buffer, or null if file not found.
|
|
180
|
+
*/
|
|
181
|
+
async read(filePath) {
|
|
182
|
+
try {
|
|
183
|
+
const fullPath = this.resolveAndValidate(filePath);
|
|
184
|
+
return await fs.readFile(fullPath);
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ============================================================
|
|
190
|
+
// Private Helpers
|
|
191
|
+
// ============================================================
|
|
192
|
+
/**
|
|
193
|
+
* Resolve a relative file path to an absolute path within basePath.
|
|
194
|
+
* Throws if the resolved path would escape basePath (path traversal attack).
|
|
195
|
+
*/
|
|
196
|
+
resolveAndValidate(filePath) {
|
|
197
|
+
const sanitized = filePath.replace(/^[/\\]+/, "").replace(/\.\.[/\\]/g, "");
|
|
198
|
+
const fullPath = path.resolve(this.basePath, sanitized);
|
|
199
|
+
if (!fullPath.startsWith(this.basePath)) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Path traversal detected: ${filePath} resolves outside of storage directory`
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return fullPath;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Auto-add the uploads directory to .gitignore on first upload.
|
|
208
|
+
* Prevents accidentally committing uploaded files to git.
|
|
209
|
+
*/
|
|
210
|
+
async ensureGitignore() {
|
|
211
|
+
if (gitignoreUpdated) return;
|
|
212
|
+
gitignoreUpdated = true;
|
|
213
|
+
try {
|
|
214
|
+
const projectRoot = path.resolve(this.basePath, "..", "..");
|
|
215
|
+
const gitignorePath = path.join(projectRoot, ".gitignore");
|
|
216
|
+
let content = "";
|
|
217
|
+
try {
|
|
218
|
+
content = await fs.readFile(gitignorePath, "utf-8");
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
const uploadsDirRelative = path.relative(projectRoot, this.basePath);
|
|
222
|
+
const ignorePattern = uploadsDirRelative + "/";
|
|
223
|
+
if (!content.includes(ignorePattern)) {
|
|
224
|
+
const newEntry = `
|
|
225
|
+
# Nextly local uploads (auto-added)
|
|
226
|
+
${ignorePattern}
|
|
227
|
+
`;
|
|
228
|
+
await fs.writeFile(gitignorePath, content + newEntry, "utf-8");
|
|
229
|
+
}
|
|
230
|
+
} catch {
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
// src/storage/adapters/local-plugin.ts
|
|
236
|
+
function localStorage(config) {
|
|
237
|
+
if (config.enabled === false) {
|
|
238
|
+
return {
|
|
239
|
+
name: "local-storage",
|
|
240
|
+
type: "local",
|
|
241
|
+
collections: {},
|
|
242
|
+
adapter: null
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const adapter = new LocalStorageAdapter({
|
|
246
|
+
basePath: config.basePath ?? "./public/uploads",
|
|
247
|
+
baseUrl: config.baseUrl ?? "/uploads"
|
|
248
|
+
});
|
|
249
|
+
return {
|
|
250
|
+
name: "local-storage",
|
|
251
|
+
type: "local",
|
|
252
|
+
collections: config.collections,
|
|
253
|
+
adapter
|
|
254
|
+
// Local storage doesn't support presigned URLs or signed downloads
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export {
|
|
259
|
+
BaseStorageAdapter,
|
|
260
|
+
LocalStorageAdapter,
|
|
261
|
+
localStorage
|
|
262
|
+
};
|