@cuencor/backend-storage 0.0.1

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.
@@ -0,0 +1,5 @@
1
+ export { StorageModule } from './lib/storage.module';
2
+ export { StorageService } from './lib/storage.service';
3
+ export { STORAGE_PROVIDER, IStorageProvider, UploadResult } from './lib/storage.interface';
4
+ export { S3StorageProvider, S3StorageOptions } from './lib/s3-storage.provider';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC3F,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ // @cuencor/backend-storage — S3-compatible object storage for Cuencor Platform
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.S3StorageProvider = exports.STORAGE_PROVIDER = exports.StorageService = exports.StorageModule = void 0;
5
+ var storage_module_1 = require("./lib/storage.module");
6
+ Object.defineProperty(exports, "StorageModule", { enumerable: true, get: function () { return storage_module_1.StorageModule; } });
7
+ var storage_service_1 = require("./lib/storage.service");
8
+ Object.defineProperty(exports, "StorageService", { enumerable: true, get: function () { return storage_service_1.StorageService; } });
9
+ var storage_interface_1 = require("./lib/storage.interface");
10
+ Object.defineProperty(exports, "STORAGE_PROVIDER", { enumerable: true, get: function () { return storage_interface_1.STORAGE_PROVIDER; } });
11
+ var s3_storage_provider_1 = require("./lib/s3-storage.provider");
12
+ Object.defineProperty(exports, "S3StorageProvider", { enumerable: true, get: function () { return s3_storage_provider_1.S3StorageProvider; } });
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,+EAA+E;;;AAE/E,uDAAqD;AAA5C,+GAAA,aAAa,OAAA;AACtB,yDAAuD;AAA9C,iHAAA,cAAc,OAAA;AACvB,6DAA2F;AAAlF,qHAAA,gBAAgB,OAAA;AACzB,iEAAgF;AAAvE,wHAAA,iBAAiB,OAAA"}
@@ -0,0 +1,24 @@
1
+ import type { IStorageProvider, UploadResult } from './storage.interface';
2
+ export interface S3StorageOptions {
3
+ /** S3-compatible endpoint URL (e.g. "https://eu2.contabostorage.com") */
4
+ endpoint: string;
5
+ /** AWS region — ignored by Contabo/MinIO but required by SDK */
6
+ region: string;
7
+ accessKeyId: string;
8
+ secretAccessKey: string;
9
+ bucket: string;
10
+ /** Base public URL for object links (e.g. "https://eu2.contabostorage.com/tenantId:bucket") */
11
+ publicBase: string;
12
+ /** Use path-style URLs (required for Contabo / MinIO) */
13
+ forcePathStyle?: boolean;
14
+ }
15
+ export declare class S3StorageProvider implements IStorageProvider {
16
+ private readonly opts;
17
+ private readonly client;
18
+ private readonly logger;
19
+ constructor(opts: S3StorageOptions);
20
+ uploadFile(buffer: Buffer, folder: string, contentType: string, extension: string): Promise<UploadResult>;
21
+ getFileBuffer(key: string): Promise<Buffer>;
22
+ deleteFile(key: string): Promise<void>;
23
+ }
24
+ //# sourceMappingURL=s3-storage.provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-storage.provider.d.ts","sourceRoot":"","sources":["../../src/lib/s3-storage.provider.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAE1E,MAAM,WAAW,gBAAgB;IAC/B,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,+FAA+F;IAC/F,UAAU,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,qBAAa,iBAAkB,YAAW,gBAAgB;IAI5C,OAAO,CAAC,QAAQ,CAAC,IAAI;IAHjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;gBAEhC,IAAI,EAAE,gBAAgB;IAY7C,UAAU,CACd,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC;IAmBlB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAY3C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAS7C"}
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.S3StorageProvider = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const client_s3_1 = require("@aws-sdk/client-s3");
6
+ const crypto_1 = require("crypto");
7
+ class S3StorageProvider {
8
+ opts;
9
+ client;
10
+ logger = new common_1.Logger(S3StorageProvider.name);
11
+ constructor(opts) {
12
+ this.opts = opts;
13
+ this.client = new client_s3_1.S3Client({
14
+ endpoint: opts.endpoint,
15
+ region: opts.region,
16
+ credentials: {
17
+ accessKeyId: opts.accessKeyId,
18
+ secretAccessKey: opts.secretAccessKey,
19
+ },
20
+ forcePathStyle: opts.forcePathStyle ?? true,
21
+ });
22
+ }
23
+ async uploadFile(buffer, folder, contentType, extension) {
24
+ const key = `${folder}/${(0, crypto_1.randomUUID)()}.${extension}`;
25
+ await this.client.send(new client_s3_1.PutObjectCommand({
26
+ Bucket: this.opts.bucket,
27
+ Key: key,
28
+ Body: buffer,
29
+ ContentType: contentType,
30
+ ContentDisposition: 'inline',
31
+ ACL: 'public-read',
32
+ }));
33
+ const url = `${this.opts.publicBase}/${key}`;
34
+ this.logger.debug(`Uploaded: ${url}`);
35
+ return { key, url };
36
+ }
37
+ async getFileBuffer(key) {
38
+ const response = await this.client.send(new client_s3_1.GetObjectCommand({ Bucket: this.opts.bucket, Key: key }));
39
+ const stream = response.Body;
40
+ const chunks = [];
41
+ for await (const chunk of stream) {
42
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
43
+ }
44
+ return Buffer.concat(chunks);
45
+ }
46
+ async deleteFile(key) {
47
+ try {
48
+ await this.client.send(new client_s3_1.DeleteObjectCommand({ Bucket: this.opts.bucket, Key: key }));
49
+ }
50
+ catch (err) {
51
+ this.logger.warn(`Failed to delete object: ${key}`, err);
52
+ }
53
+ }
54
+ }
55
+ exports.S3StorageProvider = S3StorageProvider;
56
+ //# sourceMappingURL=s3-storage.provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"s3-storage.provider.js","sourceRoot":"","sources":["../../src/lib/s3-storage.provider.ts"],"names":[],"mappings":";;;AAAA,2CAAwC;AACxC,kDAK4B;AAC5B,mCAAoC;AAiBpC,MAAa,iBAAiB;IAIC;IAHZ,MAAM,CAAW;IACjB,MAAM,GAAG,IAAI,eAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;IAE7D,YAA6B,IAAsB;QAAtB,SAAI,GAAJ,IAAI,CAAkB;QACjD,IAAI,CAAC,MAAM,GAAG,IAAI,oBAAQ,CAAC;YACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,WAAW,EAAE;gBACX,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,eAAe,EAAE,IAAI,CAAC,eAAe;aACtC;YACD,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,IAAI;SAC5C,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,UAAU,CACd,MAAc,EACd,MAAc,EACd,WAAmB,EACnB,SAAiB;QAEjB,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,IAAA,mBAAU,GAAE,IAAI,SAAS,EAAE,CAAC;QAErD,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CACpB,IAAI,4BAAgB,CAAC;YACnB,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM;YACxB,GAAG,EAAE,GAAG;YACR,IAAI,EAAE,MAAM;YACZ,WAAW,EAAE,WAAW;YACxB,kBAAkB,EAAE,QAAQ;YAC5B,GAAG,EAAE,aAAa;SACnB,CAAC,CACH,CAAC;QAEF,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,GAAG,EAAE,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,GAAG,EAAE,CAAC,CAAC;QACtC,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IACtB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,GAAW;QAC7B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CACrC,IAAI,4BAAgB,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAC7D,CAAC;QACF,MAAM,MAAM,GAAG,QAAQ,CAAC,IAA6B,CAAC;QACtD,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAA8B,CAAC,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,GAAW;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CACpB,IAAI,+BAAmB,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAChE,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,4BAA4B,GAAG,EAAE,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;CACF;AA7DD,8CA6DC"}
@@ -0,0 +1,12 @@
1
+ export declare const STORAGE_PROVIDER = "STORAGE_PROVIDER";
2
+ export interface UploadResult {
3
+ key: string;
4
+ url: string;
5
+ }
6
+ /** Common interface for any object-storage backend (Contabo, MinIO, AWS S3, etc.) */
7
+ export interface IStorageProvider {
8
+ uploadFile(buffer: Buffer, folder: string, contentType: string, extension: string): Promise<UploadResult>;
9
+ getFileBuffer(key: string): Promise<Buffer>;
10
+ deleteFile(key: string): Promise<void>;
11
+ }
12
+ //# sourceMappingURL=storage.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.interface.d.ts","sourceRoot":"","sources":["../../src/lib/storage.interface.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qFAAqF;AACrF,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC,CAAC;IAEzB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE5C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.STORAGE_PROVIDER = void 0;
4
+ exports.STORAGE_PROVIDER = 'STORAGE_PROVIDER';
5
+ //# sourceMappingURL=storage.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.interface.js","sourceRoot":"","sources":["../../src/lib/storage.interface.ts"],"names":[],"mappings":";;;AAAa,QAAA,gBAAgB,GAAG,kBAAkB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { S3StorageOptions } from './s3-storage.provider';
3
+ export interface StorageModuleOptions {
4
+ /** Use 'contabo' for Contabo S3-compatible, or pass custom S3StorageOptions */
5
+ provider: 'contabo' | S3StorageOptions;
6
+ }
7
+ export declare class StorageModule {
8
+ /**
9
+ * Register StorageModule with a provider.
10
+ *
11
+ * @example Contabo (reads env vars automatically):
12
+ * StorageModule.register({ provider: 'contabo' })
13
+ *
14
+ * @example Custom S3:
15
+ * StorageModule.register({ provider: { endpoint, region, accessKeyId, ... } })
16
+ */
17
+ static register(options?: StorageModuleOptions): DynamicModule;
18
+ }
19
+ //# sourceMappingURL=storage.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.module.d.ts","sourceRoot":"","sources":["../../src/lib/storage.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAU,MAAM,gBAAgB,CAAC;AAIvD,OAAO,EAAqB,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE5E,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,QAAQ,EAAE,SAAS,GAAG,gBAAgB,CAAC;CACxC;AAED,qBACa,aAAa;IACxB;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAE,oBAA8C,GAAG,aAAa;CA6BxF"}
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var StorageModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.StorageModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const config_1 = require("@nestjs/config");
13
+ const storage_service_1 = require("./storage.service");
14
+ const storage_interface_1 = require("./storage.interface");
15
+ const s3_storage_provider_1 = require("./s3-storage.provider");
16
+ let StorageModule = StorageModule_1 = class StorageModule {
17
+ /**
18
+ * Register StorageModule with a provider.
19
+ *
20
+ * @example Contabo (reads env vars automatically):
21
+ * StorageModule.register({ provider: 'contabo' })
22
+ *
23
+ * @example Custom S3:
24
+ * StorageModule.register({ provider: { endpoint, region, accessKeyId, ... } })
25
+ */
26
+ static register(options = { provider: 'contabo' }) {
27
+ return {
28
+ module: StorageModule_1,
29
+ providers: [
30
+ {
31
+ provide: storage_interface_1.STORAGE_PROVIDER,
32
+ inject: [config_1.ConfigService],
33
+ useFactory: (config) => {
34
+ if (options.provider === 'contabo') {
35
+ const bucket = config.getOrThrow('CONTABO_BUCKET');
36
+ const tenantId = config.getOrThrow('CONTABO_TENANT_ID');
37
+ return new s3_storage_provider_1.S3StorageProvider({
38
+ endpoint: 'https://eu2.contabostorage.com',
39
+ region: 'us-east-1',
40
+ accessKeyId: config.getOrThrow('CONTABO_ID'),
41
+ secretAccessKey: config.getOrThrow('CONTABO_SECRET'),
42
+ bucket,
43
+ publicBase: `https://eu2.contabostorage.com/${tenantId}:${bucket}`,
44
+ forcePathStyle: true,
45
+ });
46
+ }
47
+ return new s3_storage_provider_1.S3StorageProvider(options.provider);
48
+ },
49
+ },
50
+ storage_service_1.StorageService,
51
+ ],
52
+ exports: [storage_service_1.StorageService],
53
+ };
54
+ }
55
+ };
56
+ exports.StorageModule = StorageModule;
57
+ exports.StorageModule = StorageModule = StorageModule_1 = __decorate([
58
+ (0, common_1.Module)({})
59
+ ], StorageModule);
60
+ //# sourceMappingURL=storage.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.module.js","sourceRoot":"","sources":["../../src/lib/storage.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAuD;AACvD,2CAA+C;AAC/C,uDAAmD;AACnD,2DAAuD;AACvD,+DAA4E;AAQrE,IAAM,aAAa,qBAAnB,MAAM,aAAa;IACxB;;;;;;;;OAQG;IACH,MAAM,CAAC,QAAQ,CAAC,UAAgC,EAAE,QAAQ,EAAE,SAAS,EAAE;QACrE,OAAO;YACL,MAAM,EAAE,eAAa;YACrB,SAAS,EAAE;gBACT;oBACE,OAAO,EAAE,oCAAgB;oBACzB,MAAM,EAAE,CAAC,sBAAa,CAAC;oBACvB,UAAU,EAAE,CAAC,MAAqB,EAAqB,EAAE;wBACvD,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;4BACnC,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAS,gBAAgB,CAAC,CAAC;4BAC3D,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAS,mBAAmB,CAAC,CAAC;4BAChE,OAAO,IAAI,uCAAiB,CAAC;gCAC3B,QAAQ,EAAS,gCAAgC;gCACjD,MAAM,EAAW,WAAW;gCAC5B,WAAW,EAAM,MAAM,CAAC,UAAU,CAAS,YAAY,CAAC;gCACxD,eAAe,EAAE,MAAM,CAAC,UAAU,CAAS,gBAAgB,CAAC;gCAC5D,MAAM;gCACN,UAAU,EAAO,kCAAkC,QAAQ,IAAI,MAAM,EAAE;gCACvE,cAAc,EAAG,IAAI;6BACtB,CAAC,CAAC;wBACL,CAAC;wBACD,OAAO,IAAI,uCAAiB,CAAC,OAAO,CAAC,QAA4B,CAAC,CAAC;oBACrE,CAAC;iBACF;gBACD,gCAAc;aACf;YACD,OAAO,EAAE,CAAC,gCAAc,CAAC;SAC1B,CAAC;IACJ,CAAC;CACF,CAAA;AAvCY,sCAAa;wBAAb,aAAa;IADzB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,aAAa,CAuCzB"}
@@ -0,0 +1,13 @@
1
+ import { IStorageProvider, UploadResult } from './storage.interface';
2
+ /**
3
+ * StorageService — thin wrapper around any IStorageProvider.
4
+ * Inject this service in your feature modules; swap providers without changing business code.
5
+ */
6
+ export declare class StorageService {
7
+ private readonly provider;
8
+ constructor(provider: IStorageProvider);
9
+ uploadFile(buffer: Buffer, folder: string, contentType: string, extension: string): Promise<UploadResult>;
10
+ getFileBuffer(key: string): Promise<Buffer>;
11
+ deleteFile(key: string): Promise<void>;
12
+ }
13
+ //# sourceMappingURL=storage.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.service.d.ts","sourceRoot":"","sources":["../../src/lib/storage.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,gBAAgB,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEvF;;;GAGG;AACH,qBACa,cAAc;IAEG,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,gBAAgB;IAGvE,UAAU,CACR,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,YAAY,CAAC;IAIxB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI3C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGvC"}
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.StorageService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const storage_interface_1 = require("./storage.interface");
18
+ /**
19
+ * StorageService — thin wrapper around any IStorageProvider.
20
+ * Inject this service in your feature modules; swap providers without changing business code.
21
+ */
22
+ let StorageService = class StorageService {
23
+ provider;
24
+ constructor(provider) {
25
+ this.provider = provider;
26
+ }
27
+ uploadFile(buffer, folder, contentType, extension) {
28
+ return this.provider.uploadFile(buffer, folder, contentType, extension);
29
+ }
30
+ getFileBuffer(key) {
31
+ return this.provider.getFileBuffer(key);
32
+ }
33
+ deleteFile(key) {
34
+ return this.provider.deleteFile(key);
35
+ }
36
+ };
37
+ exports.StorageService = StorageService;
38
+ exports.StorageService = StorageService = __decorate([
39
+ (0, common_1.Injectable)(),
40
+ __param(0, (0, common_1.Inject)(storage_interface_1.STORAGE_PROVIDER)),
41
+ __metadata("design:paramtypes", [Object])
42
+ ], StorageService);
43
+ //# sourceMappingURL=storage.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.service.js","sourceRoot":"","sources":["../../src/lib/storage.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoD;AACpD,2DAAuF;AAEvF;;;GAGG;AAEI,IAAM,cAAc,GAApB,MAAM,cAAc;IAEoB;IAD7C,YAC6C,QAA0B;QAA1B,aAAQ,GAAR,QAAQ,CAAkB;IACpE,CAAC;IAEJ,UAAU,CACR,MAAc,EACd,MAAc,EACd,WAAmB,EACnB,SAAiB;QAEjB,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAC1E,CAAC;IAED,aAAa,CAAC,GAAW;QACvB,OAAO,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,UAAU,CAAC,GAAW;QACpB,OAAO,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;CACF,CAAA;AArBY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,mBAAU,GAAE;IAGR,WAAA,IAAA,eAAM,EAAC,oCAAgB,CAAC,CAAA;;GAFhB,cAAc,CAqB1B"}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@cuencor/backend-storage",
3
+ "version": "0.0.1",
4
+ "description": "S3-compatible object storage provider for Cuencor Platform (Contabo, MinIO, AWS S3)",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "require": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "build:watch": "tsc -p tsconfig.build.json --watch",
21
+ "typecheck": "tsc --noEmit",
22
+ "lint": "eslint src --fix"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public",
26
+ "registry": "https://registry.npmjs.org"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/cuencor/backend-storage"
31
+ },
32
+ "dependencies": {
33
+ "@aws-sdk/client-s3": "^3.1004.0"
34
+ },
35
+ "peerDependencies": {
36
+ "@nestjs/common": ">=11.0.0",
37
+ "@nestjs/config": ">=4.0.0",
38
+ "reflect-metadata": ">=0.2.0"
39
+ },
40
+ "devDependencies": {
41
+ "@nestjs/common": "11.0.7",
42
+ "@nestjs/config": "^4.0.0",
43
+ "@types/node": "^20.0.0",
44
+ "reflect-metadata": "0.2.2",
45
+ "typescript": "~5.7.2",
46
+ "eslint": "^9.20.0",
47
+ "typescript-eslint": "^8.19.0",
48
+ "eslint-plugin-security": "^3.0.1",
49
+ "eslint-plugin-sonarjs": "^3.0.7"
50
+ }
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ // @cuencor/backend-storage — S3-compatible object storage for Cuencor Platform
2
+
3
+ export { StorageModule } from './lib/storage.module';
4
+ export { StorageService } from './lib/storage.service';
5
+ export { STORAGE_PROVIDER, IStorageProvider, UploadResult } from './lib/storage.interface';
6
+ export { S3StorageProvider, S3StorageOptions } from './lib/s3-storage.provider';
@@ -0,0 +1,86 @@
1
+ import { Logger } from '@nestjs/common';
2
+ import {
3
+ S3Client,
4
+ PutObjectCommand,
5
+ GetObjectCommand,
6
+ DeleteObjectCommand,
7
+ } from '@aws-sdk/client-s3';
8
+ import { randomUUID } from 'crypto';
9
+ import type { IStorageProvider, UploadResult } from './storage.interface';
10
+
11
+ export interface S3StorageOptions {
12
+ /** S3-compatible endpoint URL (e.g. "https://eu2.contabostorage.com") */
13
+ endpoint: string;
14
+ /** AWS region — ignored by Contabo/MinIO but required by SDK */
15
+ region: string;
16
+ accessKeyId: string;
17
+ secretAccessKey: string;
18
+ bucket: string;
19
+ /** Base public URL for object links (e.g. "https://eu2.contabostorage.com/tenantId:bucket") */
20
+ publicBase: string;
21
+ /** Use path-style URLs (required for Contabo / MinIO) */
22
+ forcePathStyle?: boolean;
23
+ }
24
+
25
+ export class S3StorageProvider implements IStorageProvider {
26
+ private readonly client: S3Client;
27
+ private readonly logger = new Logger(S3StorageProvider.name);
28
+
29
+ constructor(private readonly opts: S3StorageOptions) {
30
+ this.client = new S3Client({
31
+ endpoint: opts.endpoint,
32
+ region: opts.region,
33
+ credentials: {
34
+ accessKeyId: opts.accessKeyId,
35
+ secretAccessKey: opts.secretAccessKey,
36
+ },
37
+ forcePathStyle: opts.forcePathStyle ?? true,
38
+ });
39
+ }
40
+
41
+ async uploadFile(
42
+ buffer: Buffer,
43
+ folder: string,
44
+ contentType: string,
45
+ extension: string,
46
+ ): Promise<UploadResult> {
47
+ const key = `${folder}/${randomUUID()}.${extension}`;
48
+
49
+ await this.client.send(
50
+ new PutObjectCommand({
51
+ Bucket: this.opts.bucket,
52
+ Key: key,
53
+ Body: buffer,
54
+ ContentType: contentType,
55
+ ContentDisposition: 'inline',
56
+ ACL: 'public-read',
57
+ }),
58
+ );
59
+
60
+ const url = `${this.opts.publicBase}/${key}`;
61
+ this.logger.debug(`Uploaded: ${url}`);
62
+ return { key, url };
63
+ }
64
+
65
+ async getFileBuffer(key: string): Promise<Buffer> {
66
+ const response = await this.client.send(
67
+ new GetObjectCommand({ Bucket: this.opts.bucket, Key: key }),
68
+ );
69
+ const stream = response.Body as NodeJS.ReadableStream;
70
+ const chunks: Buffer[] = [];
71
+ for await (const chunk of stream) {
72
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as unknown as Uint8Array));
73
+ }
74
+ return Buffer.concat(chunks);
75
+ }
76
+
77
+ async deleteFile(key: string): Promise<void> {
78
+ try {
79
+ await this.client.send(
80
+ new DeleteObjectCommand({ Bucket: this.opts.bucket, Key: key }),
81
+ );
82
+ } catch (err) {
83
+ this.logger.warn(`Failed to delete object: ${key}`, err);
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,20 @@
1
+ export const STORAGE_PROVIDER = 'STORAGE_PROVIDER';
2
+
3
+ export interface UploadResult {
4
+ key: string;
5
+ url: string;
6
+ }
7
+
8
+ /** Common interface for any object-storage backend (Contabo, MinIO, AWS S3, etc.) */
9
+ export interface IStorageProvider {
10
+ uploadFile(
11
+ buffer: Buffer,
12
+ folder: string,
13
+ contentType: string,
14
+ extension: string,
15
+ ): Promise<UploadResult>;
16
+
17
+ getFileBuffer(key: string): Promise<Buffer>;
18
+
19
+ deleteFile(key: string): Promise<void>;
20
+ }
@@ -0,0 +1,52 @@
1
+ import { DynamicModule, Module } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+ import { StorageService } from './storage.service';
4
+ import { STORAGE_PROVIDER } from './storage.interface';
5
+ import { S3StorageProvider, S3StorageOptions } from './s3-storage.provider';
6
+
7
+ export interface StorageModuleOptions {
8
+ /** Use 'contabo' for Contabo S3-compatible, or pass custom S3StorageOptions */
9
+ provider: 'contabo' | S3StorageOptions;
10
+ }
11
+
12
+ @Module({})
13
+ export class StorageModule {
14
+ /**
15
+ * Register StorageModule with a provider.
16
+ *
17
+ * @example Contabo (reads env vars automatically):
18
+ * StorageModule.register({ provider: 'contabo' })
19
+ *
20
+ * @example Custom S3:
21
+ * StorageModule.register({ provider: { endpoint, region, accessKeyId, ... } })
22
+ */
23
+ static register(options: StorageModuleOptions = { provider: 'contabo' }): DynamicModule {
24
+ return {
25
+ module: StorageModule,
26
+ providers: [
27
+ {
28
+ provide: STORAGE_PROVIDER,
29
+ inject: [ConfigService],
30
+ useFactory: (config: ConfigService): S3StorageProvider => {
31
+ if (options.provider === 'contabo') {
32
+ const bucket = config.getOrThrow<string>('CONTABO_BUCKET');
33
+ const tenantId = config.getOrThrow<string>('CONTABO_TENANT_ID');
34
+ return new S3StorageProvider({
35
+ endpoint: 'https://eu2.contabostorage.com',
36
+ region: 'us-east-1',
37
+ accessKeyId: config.getOrThrow<string>('CONTABO_ID'),
38
+ secretAccessKey: config.getOrThrow<string>('CONTABO_SECRET'),
39
+ bucket,
40
+ publicBase: `https://eu2.contabostorage.com/${tenantId}:${bucket}`,
41
+ forcePathStyle: true,
42
+ });
43
+ }
44
+ return new S3StorageProvider(options.provider as S3StorageOptions);
45
+ },
46
+ },
47
+ StorageService,
48
+ ],
49
+ exports: [StorageService],
50
+ };
51
+ }
52
+ }
@@ -0,0 +1,30 @@
1
+ import { Inject, Injectable } from '@nestjs/common';
2
+ import { STORAGE_PROVIDER, IStorageProvider, UploadResult } from './storage.interface';
3
+
4
+ /**
5
+ * StorageService — thin wrapper around any IStorageProvider.
6
+ * Inject this service in your feature modules; swap providers without changing business code.
7
+ */
8
+ @Injectable()
9
+ export class StorageService {
10
+ constructor(
11
+ @Inject(STORAGE_PROVIDER) private readonly provider: IStorageProvider,
12
+ ) {}
13
+
14
+ uploadFile(
15
+ buffer: Buffer,
16
+ folder: string,
17
+ contentType: string,
18
+ extension: string,
19
+ ): Promise<UploadResult> {
20
+ return this.provider.uploadFile(buffer, folder, contentType, extension);
21
+ }
22
+
23
+ getFileBuffer(key: string): Promise<Buffer> {
24
+ return this.provider.getFileBuffer(key);
25
+ }
26
+
27
+ deleteFile(key: string): Promise<void> {
28
+ return this.provider.deleteFile(key);
29
+ }
30
+ }