fiberx-backend-toolkit 0.1.10 → 0.1.12

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.
@@ -71,3 +71,21 @@ export declare const CONTENT_LANG_KEY = "CONTENT_LANG";
71
71
  export declare const DEFAULT_CONTENT_URL = "http://localhost:4000/content/{{type}}_content/{{lang}}.json";
72
72
  export declare const DEFAULT_CONTENT_LANG = "en-GB";
73
73
  export declare const DEFAULT_CONTENT_CACHE_TTL: number;
74
+ export declare const FILE_UPLOAD_LIMITS: {
75
+ IMAGE_MAX_SIZE: number;
76
+ VIDEO_MAX_SIZE: number;
77
+ DOCUMENT_MAX_SIZE: number;
78
+ };
79
+ export declare const FILE_UPLOAD_ENV_KEYS: {
80
+ IMAGE_MAX_SIZE: string;
81
+ VIDEO_MAX_SIZE: string;
82
+ DOCUMENT_MAX_SIZE: string;
83
+ IMAGE_MIME_TYPES: string;
84
+ VIDEO_MIME_TYPES: string;
85
+ DOCUMENT_MIME_TYPES: string;
86
+ };
87
+ export declare const FILE_UPLOAD_ALLOWED_MIME_TYPES: {
88
+ image: string[];
89
+ video: string[];
90
+ document: string[];
91
+ };
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.DEFAULT_CONTENT_CACHE_TTL = exports.DEFAULT_CONTENT_LANG = exports.DEFAULT_CONTENT_URL = exports.CONTENT_LANG_KEY = exports.CONTENT_URL_KEY = exports.CONTENT_CACHE_TTL_KEY = exports.EMAIL_ENQUEUE_UTIL_FILE_NAME = exports.EMAIL_ENQUEUE_TYPES_FILE_NAME = exports.DEFAULT_MAILER_CACHE_TTL = exports.DEFAULT_MAILER_CACHE_KEY = exports.DEFAULT_RBAC_CACHE_TTL = exports.DEFAULT_RBAC_CACHE_KEY = exports.DEFAULT_TOTP_WINDOW = exports.DEFAULT_TOTP_DIGITS = exports.DEFAULT_TOTP_STEP = exports.ALPHABET_CORPUS = exports.CHARACTER_CORPUS = exports.REQUEST_RATE_LIMITTER_OPTIONS = exports.CORS_MAX_AGE_IN_MICRO_SECONDS = exports.CORS_MAX_AGE_IN_SECONDS = exports.CORS_ALLOWED_HEADERS = exports.HEADERS_KEY_NAME = exports.CORS_ALLOWED_METHODS = exports.DEVICE_ID_HEADERS_NAME = exports.DEVICE_ID_COOKIE_NAME = exports.DEVICE_ID_COOKIE_MAX_AGE = exports.REQUEST_ID_HEADERS_NAME = exports.REQUEST_ID_COOKIE_NAME = exports.REQUEST_ID_COOKIE_MAX_AGE = exports.SEQUELIZE_SEEDER_META_TABLE_NAME = exports.SEQUELIZE_META_TABLE_NAME = exports.EMAIL_PREVIEW_DIR = exports.EMAIL_ENQUEUE_DIR = exports.SEEDERS_DIR = exports.MIGRATIONS_DIR = exports.MODELS_DIR = exports.SCHEMA_SNAPSHOTS_DIR = exports.SCHEMAS_DIR = exports.ENV_VAR_DIR = exports.LOG_DIR = exports.BASE_DIR = void 0;
6
+ exports.FILE_UPLOAD_ALLOWED_MIME_TYPES = exports.FILE_UPLOAD_ENV_KEYS = exports.FILE_UPLOAD_LIMITS = exports.DEFAULT_CONTENT_CACHE_TTL = exports.DEFAULT_CONTENT_LANG = exports.DEFAULT_CONTENT_URL = exports.CONTENT_LANG_KEY = exports.CONTENT_URL_KEY = exports.CONTENT_CACHE_TTL_KEY = exports.EMAIL_ENQUEUE_UTIL_FILE_NAME = exports.EMAIL_ENQUEUE_TYPES_FILE_NAME = exports.DEFAULT_MAILER_CACHE_TTL = exports.DEFAULT_MAILER_CACHE_KEY = exports.DEFAULT_RBAC_CACHE_TTL = exports.DEFAULT_RBAC_CACHE_KEY = exports.DEFAULT_TOTP_WINDOW = exports.DEFAULT_TOTP_DIGITS = exports.DEFAULT_TOTP_STEP = exports.ALPHABET_CORPUS = exports.CHARACTER_CORPUS = exports.REQUEST_RATE_LIMITTER_OPTIONS = exports.CORS_MAX_AGE_IN_MICRO_SECONDS = exports.CORS_MAX_AGE_IN_SECONDS = exports.CORS_ALLOWED_HEADERS = exports.HEADERS_KEY_NAME = exports.CORS_ALLOWED_METHODS = exports.DEVICE_ID_HEADERS_NAME = exports.DEVICE_ID_COOKIE_NAME = exports.DEVICE_ID_COOKIE_MAX_AGE = exports.REQUEST_ID_HEADERS_NAME = exports.REQUEST_ID_COOKIE_NAME = exports.REQUEST_ID_COOKIE_MAX_AGE = exports.SEQUELIZE_SEEDER_META_TABLE_NAME = exports.SEQUELIZE_META_TABLE_NAME = exports.EMAIL_PREVIEW_DIR = exports.EMAIL_ENQUEUE_DIR = exports.SEEDERS_DIR = exports.MIGRATIONS_DIR = exports.MODELS_DIR = exports.SCHEMA_SNAPSHOTS_DIR = exports.SCHEMAS_DIR = exports.ENV_VAR_DIR = exports.LOG_DIR = exports.BASE_DIR = void 0;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  exports.BASE_DIR = process.cwd();
9
9
  exports.LOG_DIR = path_1.default.join(exports.BASE_DIR, "logs");
@@ -120,3 +120,23 @@ exports.CONTENT_LANG_KEY = "CONTENT_LANG";
120
120
  exports.DEFAULT_CONTENT_URL = "http://localhost:4000/content/{{type}}_content/{{lang}}.json";
121
121
  exports.DEFAULT_CONTENT_LANG = "en-GB";
122
122
  exports.DEFAULT_CONTENT_CACHE_TTL = (1000 * 60 * 60 * 24); // 24 hours
123
+ // src/config/constants.ts
124
+ // src/config/constants.ts
125
+ exports.FILE_UPLOAD_LIMITS = {
126
+ IMAGE_MAX_SIZE: 5 * 1024 * 1024,
127
+ VIDEO_MAX_SIZE: 50 * 1024 * 1024,
128
+ DOCUMENT_MAX_SIZE: 10 * 1024 * 1024
129
+ };
130
+ exports.FILE_UPLOAD_ENV_KEYS = {
131
+ IMAGE_MAX_SIZE: "FILE_UPLOAD_IMAGE_MAX_SIZE",
132
+ VIDEO_MAX_SIZE: "FILE_UPLOAD_VIDEO_MAX_SIZE",
133
+ DOCUMENT_MAX_SIZE: "FILE_UPLOAD_DOCUMENT_MAX_SIZE",
134
+ IMAGE_MIME_TYPES: "FILE_UPLOAD_IMAGE_MIME_TYPES",
135
+ VIDEO_MIME_TYPES: "FILE_UPLOAD_VIDEO_MIME_TYPES",
136
+ DOCUMENT_MIME_TYPES: "FILE_UPLOAD_DOCUMENT_MIME_TYPES"
137
+ };
138
+ exports.FILE_UPLOAD_ALLOWED_MIME_TYPES = {
139
+ image: ["image/png", "image/jpeg", "image/webp"],
140
+ video: ["video/mp4", "video/webm"],
141
+ document: ["application/pdf", "application/msword"]
142
+ };
@@ -28,10 +28,10 @@ declare class MailerDataLoaderUtil<TMailerConfig, TBaseTemplate, TNotificationTy
28
28
  * ===============================
29
29
  */
30
30
  private getSnapshot;
31
- getMailerConfig(): TMailerConfig | null;
32
- getBaseTemplate(): TBaseTemplate | null;
33
- getNotificationTypes(): TNotificationType[];
34
- getEmailTemplates(): TEmailContentTempltae[];
31
+ getMailerConfig(): Promise<TMailerConfig | null>;
32
+ getBaseTemplate(): Promise<TBaseTemplate | null>;
33
+ getNotificationTypes(): Promise<TNotificationType[]>;
34
+ getEmailTemplates(): Promise<TEmailContentTempltae[]>;
35
35
  getTemplateByNotificationCode(code: string): Promise<TEmailContentTempltae | null>;
36
36
  refresh(): Promise<void>;
37
37
  }
@@ -92,27 +92,41 @@ class MailerDataLoaderUtil {
92
92
  * ACCESSORS
93
93
  * ===============================
94
94
  */
95
- getSnapshot() {
96
- const snapshot = this.cache.get(this.CACHE_KEY);
95
+ async getSnapshot() {
96
+ let snapshot = this.cache.get(this.CACHE_KEY);
97
+ // ✅ If exists → return immediately
98
+ if (snapshot) {
99
+ return snapshot;
100
+ }
101
+ this.logger.alert("Mailer snapshot missing or expired. Rebuilding...");
102
+ // ✅ Trigger load (handles concurrency via loading_promise)
103
+ await this.load();
104
+ snapshot = this.cache.get(this.CACHE_KEY);
105
+ // ❌ Still missing → real failure
97
106
  if (!snapshot) {
98
- throw new Error("Mailer Data Loader Util snapshot not loaded. Call load() first.");
107
+ throw new Error("Failed to rebuild Mailer snapshot.");
99
108
  }
100
109
  return snapshot;
101
110
  }
102
- getMailerConfig() {
103
- return this.getSnapshot().mailer_config;
111
+ async getMailerConfig() {
112
+ const snapshot = await this.getSnapshot();
113
+ return snapshot.mailer_config;
104
114
  }
105
- getBaseTemplate() {
106
- return this.getSnapshot().base_template;
115
+ async getBaseTemplate() {
116
+ const snapshot = await this.getSnapshot();
117
+ return snapshot.base_template;
107
118
  }
108
- getNotificationTypes() {
109
- return this.getSnapshot().notification_types;
119
+ async getNotificationTypes() {
120
+ const snapshot = await this.getSnapshot();
121
+ return snapshot.notification_types;
110
122
  }
111
- getEmailTemplates() {
112
- return this.getSnapshot().email_contents;
123
+ async getEmailTemplates() {
124
+ const snapshot = await this.getSnapshot();
125
+ return snapshot.email_contents;
113
126
  }
114
127
  async getTemplateByNotificationCode(code) {
115
- const templates_by_notification_code = this.getSnapshot().templates_by_notification_code;
128
+ const snapshot = await this.getSnapshot();
129
+ const templates_by_notification_code = snapshot.templates_by_notification_code;
116
130
  if (templates_by_notification_code.has(code)) {
117
131
  return templates_by_notification_code.get(code) ?? null;
118
132
  }
@@ -28,10 +28,10 @@ declare class RBACLoaderUtil<TSafeRole> {
28
28
  * ===============================
29
29
  */
30
30
  private getSnapshot;
31
- getRoleById(role_id: number): TSafeRole | undefined;
32
- getRoleBySymbol(symbol: string): TSafeRole | undefined;
33
- getPermissionsForRole(role_id: number): string[];
34
- roleHasPermission(role_id: number, permission_key: string): boolean;
31
+ getRoleById(role_id: number): Promise<TSafeRole | undefined>;
32
+ getRoleBySymbol(symbol: string): Promise<TSafeRole | undefined>;
33
+ getPermissionsForRole(role_id: number): Promise<string[]>;
34
+ roleHasPermission(role_id: number, permission_key: string): Promise<boolean>;
35
35
  refresh(): Promise<void>;
36
36
  }
37
37
  export default RBACLoaderUtil;
@@ -109,27 +109,35 @@ class RBACLoaderUtil {
109
109
  * ACCESSORS
110
110
  * ===============================
111
111
  */
112
- getSnapshot() {
113
- const snapshot = this.cache.get(this.RBAC_CACHE_KEY);
112
+ async getSnapshot() {
113
+ let snapshot = this.cache.get(this.RBAC_CACHE_KEY);
114
+ if (snapshot) {
115
+ return snapshot;
116
+ }
117
+ // Cache miss (expired or never loaded)
118
+ this.logger.alert("RBAC snapshot cache miss. Rebuilding...");
119
+ await this.load(); // <-- handles concurrency already
120
+ snapshot = this.cache.get(this.RBAC_CACHE_KEY);
114
121
  if (!snapshot) {
115
- throw new Error("RBAC snapshot not loaded. Call load() first.");
122
+ throw new Error("RBAC snapshot rebuild failed.");
116
123
  }
117
124
  return snapshot;
118
125
  }
119
- getRoleById(role_id) {
120
- return this.getSnapshot().role_by_id.get(role_id);
126
+ async getRoleById(role_id) {
127
+ const snapshot = await this.getSnapshot();
128
+ return snapshot.role_by_id.get(role_id);
121
129
  }
122
- getRoleBySymbol(symbol) {
123
- return this.getSnapshot().role_by_symbol.get(symbol);
130
+ async getRoleBySymbol(symbol) {
131
+ const snapshot = await this.getSnapshot();
132
+ return snapshot.role_by_symbol.get(symbol);
124
133
  }
125
- getPermissionsForRole(role_id) {
126
- return Array.from(this.getSnapshot().permissions_by_role_id.get(role_id) ?? []);
134
+ async getPermissionsForRole(role_id) {
135
+ const snapshot = await this.getSnapshot();
136
+ return Array.from(snapshot.permissions_by_role_id.get(role_id) ?? []);
127
137
  }
128
- roleHasPermission(role_id, permission_key) {
129
- return (this.getSnapshot()
130
- .permissions_by_role_id
131
- .get(role_id)
132
- ?.has(permission_key) ?? false);
138
+ async roleHasPermission(role_id, permission_key) {
139
+ const snapshot = await this.getSnapshot();
140
+ return (snapshot.permissions_by_role_id.get(role_id)?.has(permission_key) ?? false);
133
141
  }
134
142
  async refresh() {
135
143
  await this.load(true);
@@ -0,0 +1,11 @@
1
+ import { StorageDriver, StorageFile, StorageInitConfig } from "../../types/storage_type";
2
+ declare abstract class BaseStorageDriver implements StorageDriver {
3
+ abstract initialize(config: StorageInitConfig): Promise<void>;
4
+ abstract upload(...args: any[]): Promise<StorageFile>;
5
+ abstract get(key: string): Promise<StorageFile | null>;
6
+ abstract exists(key: string): Promise<boolean>;
7
+ abstract delete(key: string): Promise<void>;
8
+ abstract list(prefix?: string): Promise<StorageFile[]>;
9
+ abstract getPublicUrl(key: string): string;
10
+ }
11
+ export default BaseStorageDriver;
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ class BaseStorageDriver {
4
+ }
5
+ exports.default = BaseStorageDriver;
@@ -0,0 +1,21 @@
1
+ import BaseStorageDriver from "./base_storage_driver";
2
+ import { StorageInitConfig } from "../../types/storage_type";
3
+ declare class GCSStorageDriver extends BaseStorageDriver {
4
+ private bucket;
5
+ initialize(config: StorageInitConfig): Promise<void>;
6
+ upload(key: string, file: Buffer, mime_type: string, is_public?: boolean): Promise<{
7
+ key: string;
8
+ url: string;
9
+ mime_type: string;
10
+ size: number;
11
+ }>;
12
+ getPublicUrl(key: string): string;
13
+ exists(key: string): Promise<boolean>;
14
+ delete(key: string): Promise<void>;
15
+ get(key: string): Promise<{
16
+ key: string;
17
+ url: string;
18
+ } | null>;
19
+ list(prefix?: string): Promise<any>;
20
+ }
21
+ export default GCSStorageDriver;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const storage_1 = require("@google-cloud/storage");
7
+ const base_storage_driver_1 = __importDefault(require("./base_storage_driver"));
8
+ class GCSStorageDriver extends base_storage_driver_1.default {
9
+ bucket;
10
+ async initialize(config) {
11
+ if (config.type !== "gcs") {
12
+ throw new Error("Invalid config for GCS");
13
+ }
14
+ const storage = new storage_1.Storage({
15
+ projectId: config.project_id,
16
+ credentials: config.credentials
17
+ });
18
+ this.bucket = storage.bucket(config.bucket_name);
19
+ }
20
+ async upload(key, file, mime_type, is_public = true) {
21
+ const file_ref = this.bucket.file(key);
22
+ await file_ref.save(file, {
23
+ contentType: mime_type
24
+ });
25
+ if (is_public) {
26
+ await file_ref.makePublic();
27
+ }
28
+ return {
29
+ key,
30
+ url: this.getPublicUrl(key),
31
+ mime_type,
32
+ size: file.length
33
+ };
34
+ }
35
+ getPublicUrl(key) {
36
+ return `https://storage.googleapis.com/${this.bucket.name}/${key}`;
37
+ }
38
+ async exists(key) {
39
+ const [exists] = await this.bucket.file(key).exists();
40
+ return exists;
41
+ }
42
+ async delete(key) {
43
+ await this.bucket.file(key).delete();
44
+ }
45
+ async get(key) {
46
+ const exists = await this.exists(key);
47
+ if (!exists)
48
+ return null;
49
+ return {
50
+ key,
51
+ url: this.getPublicUrl(key)
52
+ };
53
+ }
54
+ async list(prefix = "") {
55
+ const [files] = await this.bucket.getFiles({ prefix });
56
+ return files.map((f) => ({
57
+ key: f.name,
58
+ url: this.getPublicUrl(f.name)
59
+ }));
60
+ }
61
+ }
62
+ exports.default = GCSStorageDriver;
@@ -0,0 +1,22 @@
1
+ import BaseStorageDriver from "./base_storage_driver";
2
+ import { StorageInitConfig } from "../../types/storage_type";
3
+ declare class LocalStorageDriver extends BaseStorageDriver {
4
+ private base_dir;
5
+ private public_base_url;
6
+ initialize(config: StorageInitConfig): Promise<void>;
7
+ upload(key: string, file: Buffer, mime_type: string): Promise<{
8
+ key: string;
9
+ url: string;
10
+ mime_type: string;
11
+ size: number;
12
+ }>;
13
+ exists(key: string): Promise<boolean>;
14
+ getPublicUrl(key: string): string;
15
+ get(key: string): Promise<{
16
+ key: string;
17
+ url: string;
18
+ } | null>;
19
+ delete(key: string): Promise<void>;
20
+ list(prefix?: string): Promise<never[]>;
21
+ }
22
+ export default LocalStorageDriver;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const path_1 = __importDefault(require("path"));
7
+ const promises_1 = __importDefault(require("fs/promises"));
8
+ const base_storage_driver_1 = __importDefault(require("./base_storage_driver"));
9
+ class LocalStorageDriver extends base_storage_driver_1.default {
10
+ base_dir;
11
+ public_base_url;
12
+ async initialize(config) {
13
+ if (config.type !== "local") {
14
+ throw new Error("Invalid config for LocalStorageDriver");
15
+ }
16
+ this.base_dir = config.base_dir;
17
+ this.public_base_url = config.public_base_url || "";
18
+ await promises_1.default.mkdir(this.base_dir, { recursive: true });
19
+ }
20
+ async upload(key, file, mime_type) {
21
+ const full_path = path_1.default.join(this.base_dir, key);
22
+ await promises_1.default.mkdir(path_1.default.dirname(full_path), { recursive: true });
23
+ await promises_1.default.writeFile(full_path, file);
24
+ return {
25
+ key,
26
+ url: this.getPublicUrl(key),
27
+ mime_type,
28
+ size: file.length
29
+ };
30
+ }
31
+ async exists(key) {
32
+ try {
33
+ await promises_1.default.access(path_1.default.join(this.base_dir, key));
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ getPublicUrl(key) {
41
+ return `${this.public_base_url}/${key}`;
42
+ }
43
+ async get(key) {
44
+ const exists = await this.exists(key);
45
+ if (!exists)
46
+ return null;
47
+ return {
48
+ key,
49
+ url: this.getPublicUrl(key)
50
+ };
51
+ }
52
+ async delete(key) {
53
+ await promises_1.default.unlink(path_1.default.join(this.base_dir, key));
54
+ }
55
+ async list(prefix = "") {
56
+ // optional recursive implementation
57
+ return [];
58
+ }
59
+ }
60
+ exports.default = LocalStorageDriver;
@@ -0,0 +1,5 @@
1
+ import BaseStorageDriver from "./drivers/base_storage_driver";
2
+ import GCSStorageDriver from "./drivers/gcs_storage_driver";
3
+ import LocalStorageDriver from "./drivers/local_storage_driver";
4
+ import FileUploadProcessor from "./processors/file_upload_processor";
5
+ export { BaseStorageDriver, GCSStorageDriver, LocalStorageDriver, FileUploadProcessor, };
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.FileUploadProcessor = exports.LocalStorageDriver = exports.GCSStorageDriver = exports.BaseStorageDriver = void 0;
7
+ const base_storage_driver_1 = __importDefault(require("./drivers/base_storage_driver"));
8
+ exports.BaseStorageDriver = base_storage_driver_1.default;
9
+ const gcs_storage_driver_1 = __importDefault(require("./drivers/gcs_storage_driver"));
10
+ exports.GCSStorageDriver = gcs_storage_driver_1.default;
11
+ const local_storage_driver_1 = __importDefault(require("./drivers/local_storage_driver"));
12
+ exports.LocalStorageDriver = local_storage_driver_1.default;
13
+ const file_upload_processor_1 = __importDefault(require("./processors/file_upload_processor"));
14
+ exports.FileUploadProcessor = file_upload_processor_1.default;
@@ -0,0 +1,16 @@
1
+ import { FileUploadInput, FileUploadResult, StorageDriver } from "../../types/storage_type";
2
+ declare class FileUploadProcessor {
3
+ name: string;
4
+ private static instance;
5
+ private readonly driver;
6
+ private readonly logger;
7
+ private readonly env;
8
+ private readonly validator;
9
+ private constructor();
10
+ static initialize(driver: StorageDriver): FileUploadProcessor;
11
+ static getInstance(): FileUploadProcessor;
12
+ private getExtension;
13
+ private generateKey;
14
+ upload(input: FileUploadInput): Promise<FileUploadResult>;
15
+ }
16
+ export default FileUploadProcessor;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const logger_util_1 = __importDefault(require("../../utils/logger_util"));
7
+ const env_manager_util_1 = __importDefault(require("../../utils/env_manager_util"));
8
+ const uuid_1 = require("uuid");
9
+ const file_validator_util_1 = __importDefault(require("../../validators/file_validator_util"));
10
+ class FileUploadProcessor {
11
+ name = "file_upload_processor";
12
+ static instance = null;
13
+ driver;
14
+ logger = new logger_util_1.default(this.name);
15
+ env = env_manager_util_1.default.getInstance();
16
+ validator = new file_validator_util_1.default();
17
+ constructor(driver) {
18
+ this.driver = driver;
19
+ }
20
+ static initialize(driver) {
21
+ if (this.instance) {
22
+ throw new Error("Already initialized");
23
+ }
24
+ this.instance = new FileUploadProcessor(driver);
25
+ return this.instance;
26
+ }
27
+ static getInstance() {
28
+ if (!this.instance) {
29
+ throw new Error("Not initialized");
30
+ }
31
+ return this.instance;
32
+ }
33
+ // ==============================
34
+ // EXTENSION HELPER
35
+ // ==============================
36
+ getExtension(file_name) {
37
+ const ext = file_name.split(".").pop();
38
+ return ext ? ext : "bin";
39
+ }
40
+ // ==============================
41
+ // KEY GENERATION
42
+ // ==============================
43
+ generateKey(category, ext, folder) {
44
+ return `${folder || "uploads"}/${category}/${(0, uuid_1.v4)()}.${ext}`;
45
+ }
46
+ // ==============================
47
+ // MAIN METHOD
48
+ // ==============================
49
+ async upload(input) {
50
+ try {
51
+ const category = this.validator.getCategory(input.mime_type);
52
+ // ✅ validate AFTER category detection
53
+ const { v_state, v_msg } = this.validator.validate(input, category);
54
+ if (!v_state) {
55
+ return { status: false, msg: v_msg };
56
+ }
57
+ const ext = this.getExtension(input.original_name);
58
+ const key = this.generateKey(category, ext, input.folder);
59
+ const stored = await this.driver.upload(key, input.file, input.mime_type, input.is_public ?? true);
60
+ if (!stored) {
61
+ return { status: false, msg: "failed_to_store_file" };
62
+ }
63
+ const result = {
64
+ key,
65
+ original_name: input.original_name,
66
+ mime_type: input.mime_type,
67
+ category,
68
+ size: input.size,
69
+ url: stored.url
70
+ };
71
+ this.logger.info("File uploaded successfully", {
72
+ key,
73
+ category,
74
+ size: input.size
75
+ });
76
+ return { status: true, msg: "file_uploaded_successfully", data: result };
77
+ }
78
+ catch (error) {
79
+ this.logger.error("Upload failed", {
80
+ error,
81
+ input_meta: {
82
+ name: input.original_name,
83
+ size: input.size,
84
+ mime: input.mime_type
85
+ }
86
+ });
87
+ return { status: false, msg: "file_upload_error_occured" };
88
+ }
89
+ }
90
+ }
91
+ exports.default = FileUploadProcessor;
@@ -8,3 +8,4 @@ export * from "./express_decelaration";
8
8
  export * from "./rbac_type";
9
9
  export * from "./mailer_type";
10
10
  export * from "./validator_type";
11
+ export * from "./storage_type";
@@ -24,3 +24,4 @@ __exportStar(require("./express_decelaration"), exports);
24
24
  __exportStar(require("./rbac_type"), exports);
25
25
  __exportStar(require("./mailer_type"), exports);
26
26
  __exportStar(require("./validator_type"), exports);
27
+ __exportStar(require("./storage_type"), exports);
@@ -0,0 +1,47 @@
1
+ export type StorageInitConfig = {
2
+ type: "gcs";
3
+ bucket_name: string;
4
+ project_id: string;
5
+ credentials: Record<string, any>;
6
+ } | {
7
+ type: "local";
8
+ base_dir: string;
9
+ public_base_url?: string;
10
+ };
11
+ export interface StorageFile {
12
+ key: string;
13
+ url: string;
14
+ size?: number;
15
+ mime_type?: string;
16
+ }
17
+ export interface StorageDriver {
18
+ initialize(config: StorageInitConfig): Promise<void>;
19
+ upload(key: string, file: Buffer, mime_type: string, is_public?: boolean): Promise<StorageFile | null>;
20
+ get(key: string): Promise<StorageFile | null>;
21
+ exists(key: string): Promise<boolean>;
22
+ delete(key: string): Promise<void>;
23
+ list(prefix?: string): Promise<StorageFile[]>;
24
+ getPublicUrl(key: string): string;
25
+ }
26
+ export type FileCategory = "image" | "video" | "document";
27
+ export interface FileUploadInput {
28
+ file: Buffer;
29
+ original_name: string;
30
+ mime_type: string;
31
+ size: number;
32
+ folder?: string;
33
+ is_public?: boolean;
34
+ }
35
+ export interface FileUploadDataPayloadInterface {
36
+ key: string;
37
+ original_name: string;
38
+ mime_type: string;
39
+ category: FileCategory;
40
+ size: number;
41
+ url: string;
42
+ }
43
+ export interface FileUploadResult {
44
+ status: boolean;
45
+ msg: string;
46
+ data?: FileUploadDataPayloadInterface;
47
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // src/types/storage_type.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,12 @@
1
+ import { FileCategory, FileUploadInput } from "../types/storage_type";
2
+ import { QueryValidationResultInterface } from "../types/validator_type";
3
+ declare class FileValidatorUtil {
4
+ name: string;
5
+ private readonly logger;
6
+ private readonly env;
7
+ getCategory(mime: string): FileCategory;
8
+ private getMaxSize;
9
+ private getAllowedMimeTypes;
10
+ validate(input: FileUploadInput, category: FileCategory): QueryValidationResultInterface<null>;
11
+ }
12
+ export default FileValidatorUtil;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ // src/storage/utils/file_validator_util.ts
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const logger_util_1 = __importDefault(require("../utils/logger_util"));
8
+ const env_manager_util_1 = __importDefault(require("../utils/env_manager_util"));
9
+ const constants_1 = require("../config/constants");
10
+ class FileValidatorUtil {
11
+ name = "file_validator_util";
12
+ logger = new logger_util_1.default(this.name);
13
+ env = env_manager_util_1.default.getInstance();
14
+ // ==============================
15
+ // CATEGORY
16
+ // ==============================
17
+ getCategory(mime) {
18
+ if (mime.startsWith("image/"))
19
+ return "image";
20
+ if (mime.startsWith("video/"))
21
+ return "video";
22
+ return "document";
23
+ }
24
+ // ==============================
25
+ // MAX SIZE
26
+ // ==============================
27
+ getMaxSize(category) {
28
+ switch (category) {
29
+ case "image":
30
+ return Number(this.env.getEnvVar(constants_1.FILE_UPLOAD_ENV_KEYS.IMAGE_MAX_SIZE, constants_1.FILE_UPLOAD_LIMITS.IMAGE_MAX_SIZE));
31
+ case "video":
32
+ return Number(this.env.getEnvVar(constants_1.FILE_UPLOAD_ENV_KEYS.VIDEO_MAX_SIZE, constants_1.FILE_UPLOAD_LIMITS.VIDEO_MAX_SIZE));
33
+ case "document":
34
+ default:
35
+ return Number(this.env.getEnvVar(constants_1.FILE_UPLOAD_ENV_KEYS.DOCUMENT_MAX_SIZE, constants_1.FILE_UPLOAD_LIMITS.DOCUMENT_MAX_SIZE));
36
+ }
37
+ }
38
+ // ==============================
39
+ // MIME TYPES
40
+ // ==============================
41
+ getAllowedMimeTypes(category) {
42
+ const env_value = this.env.getEnvVar(constants_1.FILE_UPLOAD_ENV_KEYS[`${category.toUpperCase()}_MIME_TYPES`]);
43
+ if (env_value) {
44
+ // allow both CSV string or array
45
+ if (Array.isArray(env_value))
46
+ return env_value;
47
+ return String(env_value)
48
+ .split(",")
49
+ .map((m) => m.trim());
50
+ }
51
+ return constants_1.FILE_UPLOAD_ALLOWED_MIME_TYPES[category];
52
+ }
53
+ // ==============================
54
+ // MAIN VALIDATION
55
+ // ==============================
56
+ validate(input, category) {
57
+ if (!input.mime_type) {
58
+ return { v_state: false, v_msg: "missing_mime_type" };
59
+ }
60
+ if (!input.original_name) {
61
+ return { v_state: false, v_msg: "missing_original_file_name" };
62
+ }
63
+ // 🔹 size validation
64
+ const max_size = this.getMaxSize(category);
65
+ if (input.size > max_size) {
66
+ this.logger.error("File size exceeds limit", {
67
+ size: input.size,
68
+ max_size,
69
+ category
70
+ });
71
+ return { v_state: false, v_msg: "file_size_to_large" };
72
+ }
73
+ // 🔹 mime validation
74
+ const allowed = this.getAllowedMimeTypes(category);
75
+ if (!allowed.includes(input.mime_type)) {
76
+ this.logger.error("Invalid mime type", {
77
+ mime: input.mime_type,
78
+ allowed,
79
+ category
80
+ });
81
+ return { v_state: false, v_msg: "invalid_file_mime_type" };
82
+ }
83
+ return { v_state: true, v_msg: "file_input_valid" };
84
+ }
85
+ }
86
+ exports.default = FileValidatorUtil;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fiberx-backend-toolkit",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "A TypeScript backend toolkit providing shared domain logic, infrastructure helpers, and utilities for FiberX server-side applications and services.",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
@@ -23,6 +23,7 @@
23
23
  "license": "ISC",
24
24
  "author": "David Matt-Ojo",
25
25
  "dependencies": {
26
+ "@google-cloud/storage": "^7.19.0",
26
27
  "@types/nodemailer": "^7.0.11",
27
28
  "axios": "^1.11.0",
28
29
  "bcrypt": "^6.0.0",