hazo_files 1.0.1 → 1.1.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/dist/index.js CHANGED
@@ -39,6 +39,7 @@ __export(index_exports, {
39
39
  DirectoryNotFoundError: () => DirectoryNotFoundError,
40
40
  FileExistsError: () => FileExistsError,
41
41
  FileManager: () => FileManager,
42
+ FileMetadataService: () => FileMetadataService,
42
43
  FileNotFoundError: () => FileNotFoundError,
43
44
  FileTooLargeError: () => FileTooLargeError,
44
45
  GoogleDriveAuth: () => GoogleDriveAuth,
@@ -52,18 +53,22 @@ __export(index_exports, {
52
53
  SYSTEM_COUNTER_VARIABLES: () => SYSTEM_COUNTER_VARIABLES,
53
54
  SYSTEM_DATE_VARIABLES: () => SYSTEM_DATE_VARIABLES,
54
55
  SYSTEM_FILE_VARIABLES: () => SYSTEM_FILE_VARIABLES,
56
+ TrackedFileManager: () => TrackedFileManager,
55
57
  clonePattern: () => clonePattern,
56
58
  createAndInitializeModule: () => createAndInitializeModule,
57
59
  createEmptyNamingRuleSchema: () => createEmptyNamingRuleSchema,
58
60
  createFileItem: () => createFileItem,
59
61
  createFileManager: () => createFileManager,
62
+ createFileMetadataService: () => createFileMetadataService,
60
63
  createFolderItem: () => createFolderItem,
61
64
  createGoogleDriveAuth: () => createGoogleDriveAuth,
62
65
  createGoogleDriveModule: () => createGoogleDriveModule,
63
66
  createInitializedFileManager: () => createInitializedFileManager,
67
+ createInitializedTrackedFileManager: () => createInitializedTrackedFileManager,
64
68
  createLiteralSegment: () => createLiteralSegment,
65
69
  createLocalModule: () => createLocalModule,
66
70
  createModule: () => createModule,
71
+ createTrackedFileManager: () => createTrackedFileManager,
67
72
  createVariableSegment: () => createVariableSegment,
68
73
  errorResult: () => errorResult,
69
74
  filterItems: () => filterItems,
@@ -141,12 +146,12 @@ function parseConfig(configContent) {
141
146
  }
142
147
  if (parsed.google_drive) {
143
148
  config.google_drive = {
144
- clientId: parsed.google_drive.client_id || process.env.GOOGLE_DRIVE_CLIENT_ID || "",
145
- clientSecret: parsed.google_drive.client_secret || process.env.GOOGLE_DRIVE_CLIENT_SECRET || "",
146
- redirectUri: parsed.google_drive.redirect_uri || process.env.GOOGLE_DRIVE_REDIRECT_URI || "",
147
- refreshToken: parsed.google_drive.refresh_token || process.env.GOOGLE_DRIVE_REFRESH_TOKEN,
148
- accessToken: parsed.google_drive.access_token || process.env.GOOGLE_DRIVE_ACCESS_TOKEN,
149
- rootFolderId: parsed.google_drive.root_folder_id || process.env.GOOGLE_DRIVE_ROOT_FOLDER_ID
149
+ clientId: parsed.google_drive.client_id || process.env.HAZO_GOOGLE_DRIVE_CLIENT_ID || "",
150
+ clientSecret: parsed.google_drive.client_secret || process.env.HAZO_GOOGLE_DRIVE_CLIENT_SECRET || "",
151
+ redirectUri: parsed.google_drive.redirect_uri || process.env.HAZO_GOOGLE_DRIVE_REDIRECT_URI || "",
152
+ refreshToken: parsed.google_drive.refresh_token || process.env.HAZO_GOOGLE_DRIVE_REFRESH_TOKEN,
153
+ accessToken: parsed.google_drive.access_token || process.env.HAZO_GOOGLE_DRIVE_ACCESS_TOKEN,
154
+ rootFolderId: parsed.google_drive.root_folder_id || process.env.HAZO_GOOGLE_DRIVE_ROOT_FOLDER_ID
150
155
  };
151
156
  }
152
157
  return config;
@@ -202,7 +207,7 @@ max_file_size = 0
202
207
  [google_drive]
203
208
  ; Google Drive OAuth credentials
204
209
  ; These can also be set via environment variables:
205
- ; GOOGLE_DRIVE_CLIENT_ID, GOOGLE_DRIVE_CLIENT_SECRET, etc.
210
+ ; HAZO_GOOGLE_DRIVE_CLIENT_ID, HAZO_GOOGLE_DRIVE_CLIENT_SECRET, etc.
206
211
  client_id =
207
212
  client_secret =
208
213
  redirect_uri = http://localhost:3000/api/auth/callback/google
@@ -2043,6 +2048,447 @@ async function createInitializedFileManager(options) {
2043
2048
  return manager;
2044
2049
  }
2045
2050
 
2051
+ // src/services/file-metadata-service.ts
2052
+ var FileMetadataService = class {
2053
+ constructor(crudService, options = {}) {
2054
+ this.crud = crudService;
2055
+ this.logger = options.logger;
2056
+ this.logErrors = options.logErrors !== false;
2057
+ }
2058
+ /**
2059
+ * Generate ISO timestamp
2060
+ */
2061
+ now() {
2062
+ return (/* @__PURE__ */ new Date()).toISOString();
2063
+ }
2064
+ /**
2065
+ * Log an error if logging is enabled
2066
+ */
2067
+ logError(operation, error) {
2068
+ if (this.logErrors) {
2069
+ const message = error instanceof Error ? error.message : String(error);
2070
+ this.logger?.error?.(`FileMetadataService.${operation} failed`, { error: message });
2071
+ if (!this.logger) {
2072
+ console.error(`[FileMetadataService] ${operation} failed:`, message);
2073
+ }
2074
+ }
2075
+ }
2076
+ /**
2077
+ * Record a file upload
2078
+ */
2079
+ async recordUpload(input) {
2080
+ try {
2081
+ const timestamp = this.now();
2082
+ const record = {
2083
+ filename: input.filename,
2084
+ file_type: input.file_type,
2085
+ file_data: JSON.stringify(input.file_data || {}),
2086
+ file_path: input.file_path,
2087
+ storage_type: input.storage_type,
2088
+ created_at: timestamp,
2089
+ changed_at: timestamp
2090
+ };
2091
+ const results = await this.crud.insert(record);
2092
+ this.logger?.debug?.("Recorded file upload", { path: input.file_path });
2093
+ return results[0] || null;
2094
+ } catch (error) {
2095
+ this.logError("recordUpload", error);
2096
+ return null;
2097
+ }
2098
+ }
2099
+ /**
2100
+ * Record a directory creation
2101
+ */
2102
+ async recordDirectoryCreation(path3, storageType, metadata) {
2103
+ return this.recordUpload({
2104
+ filename: getBaseName(path3),
2105
+ file_type: "folder",
2106
+ file_data: metadata,
2107
+ file_path: path3,
2108
+ storage_type: storageType
2109
+ });
2110
+ }
2111
+ /**
2112
+ * Record a file access (download)
2113
+ */
2114
+ async recordAccess(path3, storageType) {
2115
+ try {
2116
+ const existing = await this.findByPath(path3, storageType);
2117
+ if (existing) {
2118
+ await this.crud.updateById(existing.id, {
2119
+ changed_at: this.now()
2120
+ });
2121
+ this.logger?.debug?.("Recorded file access", { path: path3 });
2122
+ return true;
2123
+ }
2124
+ return false;
2125
+ } catch (error) {
2126
+ this.logError("recordAccess", error);
2127
+ return false;
2128
+ }
2129
+ }
2130
+ /**
2131
+ * Record a file deletion
2132
+ */
2133
+ async recordDelete(path3, storageType) {
2134
+ try {
2135
+ const existing = await this.findByPath(path3, storageType);
2136
+ if (existing) {
2137
+ await this.crud.deleteById(existing.id);
2138
+ this.logger?.debug?.("Recorded file deletion", { path: path3 });
2139
+ return true;
2140
+ }
2141
+ return false;
2142
+ } catch (error) {
2143
+ this.logError("recordDelete", error);
2144
+ return false;
2145
+ }
2146
+ }
2147
+ /**
2148
+ * Record a directory deletion (recursive)
2149
+ */
2150
+ async recordDirectoryDelete(path3, storageType, recursive) {
2151
+ try {
2152
+ if (recursive) {
2153
+ const records = await this.crud.findBy({ storage_type: storageType });
2154
+ const toDelete = records.filter(
2155
+ (r) => r.file_path === path3 || r.file_path.startsWith(path3 + "/")
2156
+ );
2157
+ for (const record of toDelete) {
2158
+ await this.crud.deleteById(record.id);
2159
+ }
2160
+ this.logger?.debug?.("Recorded recursive directory deletion", {
2161
+ path: path3,
2162
+ count: toDelete.length
2163
+ });
2164
+ return true;
2165
+ } else {
2166
+ return this.recordDelete(path3, storageType);
2167
+ }
2168
+ } catch (error) {
2169
+ this.logError("recordDirectoryDelete", error);
2170
+ return false;
2171
+ }
2172
+ }
2173
+ /**
2174
+ * Record a file or folder move
2175
+ */
2176
+ async recordMove(sourcePath, destinationPath, storageType) {
2177
+ try {
2178
+ const existing = await this.findByPath(sourcePath, storageType);
2179
+ if (existing) {
2180
+ await this.crud.updateById(existing.id, {
2181
+ file_path: destinationPath,
2182
+ filename: getBaseName(destinationPath),
2183
+ changed_at: this.now()
2184
+ });
2185
+ this.logger?.debug?.("Recorded file move", { from: sourcePath, to: destinationPath });
2186
+ return true;
2187
+ }
2188
+ return false;
2189
+ } catch (error) {
2190
+ this.logError("recordMove", error);
2191
+ return false;
2192
+ }
2193
+ }
2194
+ /**
2195
+ * Record a file or folder rename
2196
+ */
2197
+ async recordRename(path3, newName, storageType) {
2198
+ try {
2199
+ const existing = await this.findByPath(path3, storageType);
2200
+ if (existing) {
2201
+ const parentPath = getDirName(path3);
2202
+ const newPath = parentPath === "/" ? `/${newName}` : `${parentPath}/${newName}`;
2203
+ await this.crud.updateById(existing.id, {
2204
+ filename: newName,
2205
+ file_path: newPath,
2206
+ changed_at: this.now()
2207
+ });
2208
+ this.logger?.debug?.("Recorded file rename", { path: path3, newName });
2209
+ return true;
2210
+ }
2211
+ return false;
2212
+ } catch (error) {
2213
+ this.logError("recordRename", error);
2214
+ return false;
2215
+ }
2216
+ }
2217
+ /**
2218
+ * Find a record by path and storage type
2219
+ */
2220
+ async findByPath(path3, storageType) {
2221
+ try {
2222
+ return await this.crud.findOneBy({
2223
+ file_path: path3,
2224
+ storage_type: storageType
2225
+ });
2226
+ } catch (error) {
2227
+ this.logError("findByPath", error);
2228
+ return null;
2229
+ }
2230
+ }
2231
+ /**
2232
+ * Find all records for a storage type
2233
+ */
2234
+ async findByStorageType(storageType) {
2235
+ try {
2236
+ return await this.crud.findBy({ storage_type: storageType });
2237
+ } catch (error) {
2238
+ this.logError("findByStorageType", error);
2239
+ return [];
2240
+ }
2241
+ }
2242
+ /**
2243
+ * Find all records in a directory
2244
+ */
2245
+ async findInDirectory(directoryPath, storageType) {
2246
+ try {
2247
+ const all = await this.findByStorageType(storageType);
2248
+ const prefix = directoryPath === "/" ? "/" : directoryPath + "/";
2249
+ return all.filter((r) => {
2250
+ if (directoryPath === "/") {
2251
+ const segments = r.file_path.split("/").filter(Boolean);
2252
+ return segments.length === 1;
2253
+ }
2254
+ if (!r.file_path.startsWith(prefix)) return false;
2255
+ const relativePath = r.file_path.slice(prefix.length);
2256
+ return !relativePath.includes("/");
2257
+ });
2258
+ } catch (error) {
2259
+ this.logError("findInDirectory", error);
2260
+ return [];
2261
+ }
2262
+ }
2263
+ /**
2264
+ * Update custom metadata for a file
2265
+ */
2266
+ async updateMetadata(path3, storageType, metadata) {
2267
+ try {
2268
+ const existing = await this.findByPath(path3, storageType);
2269
+ if (existing) {
2270
+ const currentData = JSON.parse(existing.file_data || "{}");
2271
+ const newData = { ...currentData, ...metadata };
2272
+ await this.crud.updateById(existing.id, {
2273
+ file_data: JSON.stringify(newData),
2274
+ changed_at: this.now()
2275
+ });
2276
+ this.logger?.debug?.("Updated file metadata", { path: path3 });
2277
+ return true;
2278
+ }
2279
+ return false;
2280
+ } catch (error) {
2281
+ this.logError("updateMetadata", error);
2282
+ return false;
2283
+ }
2284
+ }
2285
+ };
2286
+ function createFileMetadataService(crudService, options) {
2287
+ return new FileMetadataService(crudService, options);
2288
+ }
2289
+
2290
+ // src/services/tracked-file-manager.ts
2291
+ var TrackedFileManager = class extends FileManager {
2292
+ constructor(options = {}) {
2293
+ super(options);
2294
+ this.metadataService = null;
2295
+ this.trackingConfig = {
2296
+ enabled: options.tracking?.enabled ?? false,
2297
+ tableName: options.tracking?.tableName ?? "hazo_files",
2298
+ trackDownloads: options.tracking?.trackDownloads ?? true,
2299
+ logErrors: options.tracking?.logErrors ?? true
2300
+ };
2301
+ if (options.crudService && this.trackingConfig.enabled) {
2302
+ this.metadataService = new FileMetadataService(options.crudService, {
2303
+ tableName: this.trackingConfig.tableName,
2304
+ logErrors: this.trackingConfig.logErrors
2305
+ });
2306
+ }
2307
+ }
2308
+ /**
2309
+ * Check if tracking is enabled and service is available
2310
+ */
2311
+ isTrackingEnabled() {
2312
+ return this.trackingConfig.enabled && this.metadataService !== null;
2313
+ }
2314
+ /**
2315
+ * Get the current storage provider type
2316
+ */
2317
+ getStorageType() {
2318
+ return this.getProvider() || "local";
2319
+ }
2320
+ // ============ Tracked Directory Operations ============
2321
+ /**
2322
+ * Create a directory and record it in the database
2323
+ */
2324
+ async createDirectory(path3) {
2325
+ const result = await super.createDirectory(path3);
2326
+ if (result.success && this.isTrackingEnabled()) {
2327
+ this.metadataService.recordDirectoryCreation(
2328
+ path3,
2329
+ this.getStorageType(),
2330
+ result.data?.metadata
2331
+ ).catch(() => {
2332
+ });
2333
+ }
2334
+ return result;
2335
+ }
2336
+ /**
2337
+ * Remove a directory and delete its record from the database
2338
+ */
2339
+ async removeDirectory(path3, recursive = false) {
2340
+ const result = await super.removeDirectory(path3, recursive);
2341
+ if (result.success && this.isTrackingEnabled()) {
2342
+ this.metadataService.recordDirectoryDelete(
2343
+ path3,
2344
+ this.getStorageType(),
2345
+ recursive
2346
+ ).catch(() => {
2347
+ });
2348
+ }
2349
+ return result;
2350
+ }
2351
+ // ============ Tracked File Operations ============
2352
+ /**
2353
+ * Upload a file and record it in the database
2354
+ */
2355
+ async uploadFile(source, remotePath, options) {
2356
+ const result = await super.uploadFile(source, remotePath, options);
2357
+ if (result.success && this.isTrackingEnabled() && result.data) {
2358
+ const fileItem = result.data;
2359
+ this.metadataService.recordUpload({
2360
+ filename: fileItem.name,
2361
+ file_type: fileItem.mimeType || getMimeType(fileItem.name),
2362
+ file_data: options?.metadata || fileItem.metadata,
2363
+ file_path: remotePath,
2364
+ storage_type: this.getStorageType()
2365
+ }).catch(() => {
2366
+ });
2367
+ }
2368
+ return result;
2369
+ }
2370
+ /**
2371
+ * Download a file and optionally track access
2372
+ */
2373
+ async downloadFile(remotePath, localPath, options) {
2374
+ const result = await super.downloadFile(remotePath, localPath, options);
2375
+ if (result.success && this.isTrackingEnabled() && this.trackingConfig.trackDownloads) {
2376
+ this.metadataService.recordAccess(
2377
+ remotePath,
2378
+ this.getStorageType()
2379
+ ).catch(() => {
2380
+ });
2381
+ }
2382
+ return result;
2383
+ }
2384
+ /**
2385
+ * Move a file or folder and update its path in the database
2386
+ */
2387
+ async moveItem(sourcePath, destinationPath, options) {
2388
+ const result = await super.moveItem(sourcePath, destinationPath, options);
2389
+ if (result.success && this.isTrackingEnabled()) {
2390
+ this.metadataService.recordMove(
2391
+ sourcePath,
2392
+ destinationPath,
2393
+ this.getStorageType()
2394
+ ).catch(() => {
2395
+ });
2396
+ }
2397
+ return result;
2398
+ }
2399
+ /**
2400
+ * Delete a file and remove its record from the database
2401
+ */
2402
+ async deleteFile(path3) {
2403
+ const result = await super.deleteFile(path3);
2404
+ if (result.success && this.isTrackingEnabled()) {
2405
+ this.metadataService.recordDelete(
2406
+ path3,
2407
+ this.getStorageType()
2408
+ ).catch(() => {
2409
+ });
2410
+ }
2411
+ return result;
2412
+ }
2413
+ /**
2414
+ * Rename a file and update its record in the database
2415
+ */
2416
+ async renameFile(path3, newName, options) {
2417
+ const result = await super.renameFile(path3, newName, options);
2418
+ if (result.success && this.isTrackingEnabled()) {
2419
+ this.metadataService.recordRename(
2420
+ path3,
2421
+ newName,
2422
+ this.getStorageType()
2423
+ ).catch(() => {
2424
+ });
2425
+ }
2426
+ return result;
2427
+ }
2428
+ /**
2429
+ * Rename a folder and update its record in the database
2430
+ */
2431
+ async renameFolder(path3, newName, options) {
2432
+ const result = await super.renameFolder(path3, newName, options);
2433
+ if (result.success && this.isTrackingEnabled()) {
2434
+ this.metadataService.recordRename(
2435
+ path3,
2436
+ newName,
2437
+ this.getStorageType()
2438
+ ).catch(() => {
2439
+ });
2440
+ }
2441
+ return result;
2442
+ }
2443
+ // ============ Tracked Convenience Methods ============
2444
+ /**
2445
+ * Write a file with string content and track it
2446
+ */
2447
+ async writeFile(path3, content, options) {
2448
+ const buffer = Buffer.from(content, "utf-8");
2449
+ return this.uploadFile(buffer, path3, options);
2450
+ }
2451
+ /**
2452
+ * Read a file and optionally track access
2453
+ */
2454
+ async readFile(path3) {
2455
+ return super.readFile(path3);
2456
+ }
2457
+ /**
2458
+ * Copy a file and track the new file
2459
+ */
2460
+ async copyFile(sourcePath, destinationPath, options) {
2461
+ return super.copyFile(sourcePath, destinationPath, options);
2462
+ }
2463
+ // ============ Metadata Service Access ============
2464
+ /**
2465
+ * Get the metadata service for direct access
2466
+ */
2467
+ getMetadataService() {
2468
+ return this.metadataService;
2469
+ }
2470
+ /**
2471
+ * Check if database tracking is enabled
2472
+ */
2473
+ isTrackingActive() {
2474
+ return this.isTrackingEnabled();
2475
+ }
2476
+ /**
2477
+ * Get tracking configuration
2478
+ */
2479
+ getTrackingConfig() {
2480
+ return { ...this.trackingConfig };
2481
+ }
2482
+ };
2483
+ function createTrackedFileManager(options) {
2484
+ return new TrackedFileManager(options);
2485
+ }
2486
+ async function createInitializedTrackedFileManager(options) {
2487
+ const manager = new TrackedFileManager(options);
2488
+ await manager.initialize();
2489
+ return manager;
2490
+ }
2491
+
2046
2492
  // src/common/naming-utils.ts
2047
2493
  var DEFAULT_DATE_FORMATS = [
2048
2494
  "YYYY",
@@ -2374,6 +2820,7 @@ function generatePreviewName(pattern, userVariables, options = {}) {
2374
2820
  DirectoryNotFoundError,
2375
2821
  FileExistsError,
2376
2822
  FileManager,
2823
+ FileMetadataService,
2377
2824
  FileNotFoundError,
2378
2825
  FileTooLargeError,
2379
2826
  GoogleDriveAuth,
@@ -2387,18 +2834,22 @@ function generatePreviewName(pattern, userVariables, options = {}) {
2387
2834
  SYSTEM_COUNTER_VARIABLES,
2388
2835
  SYSTEM_DATE_VARIABLES,
2389
2836
  SYSTEM_FILE_VARIABLES,
2837
+ TrackedFileManager,
2390
2838
  clonePattern,
2391
2839
  createAndInitializeModule,
2392
2840
  createEmptyNamingRuleSchema,
2393
2841
  createFileItem,
2394
2842
  createFileManager,
2843
+ createFileMetadataService,
2395
2844
  createFolderItem,
2396
2845
  createGoogleDriveAuth,
2397
2846
  createGoogleDriveModule,
2398
2847
  createInitializedFileManager,
2848
+ createInitializedTrackedFileManager,
2399
2849
  createLiteralSegment,
2400
2850
  createLocalModule,
2401
2851
  createModule,
2852
+ createTrackedFileManager,
2402
2853
  createVariableSegment,
2403
2854
  errorResult,
2404
2855
  filterItems,
@@ -2454,4 +2905,3 @@ function generatePreviewName(pattern, userVariables, options = {}) {
2454
2905
  validateNamingRuleSchema,
2455
2906
  validatePath
2456
2907
  });
2457
- //# sourceMappingURL=index.js.map