90dc-core 1.19.5 → 1.19.7

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.
@@ -15,9 +15,10 @@ export declare class ImagesClient {
15
15
  getRecipePhotos(recipeUuid: string): Promise<t.RecipePhotos | undefined>;
16
16
  saveUserProgressPhoto(input: t.UserProgressPhotoInput): Promise<t.UserProgressPhotoRecord>;
17
17
  getUserProgressPhotos(userUuid: string): Promise<t.UserProgressPhotoRecord[]>;
18
- getProgressPhotosByProgram(programId: string): Promise<t.UserProgressPhotoRecord[]>;
18
+ getProgressPhotosByProgram(programId: string, userUuid?: string): Promise<t.UserProgressPhotoRecord[]>;
19
19
  updateProgressPhotoDate(input: t.UpdateProgressPhotoDateInput): Promise<t.UserProgressPhotoRecord | null>;
20
20
  deleteProgressPhoto(uuid: string, userUuid: string): Promise<boolean>;
21
+ private migrateLegacyPhotosForUser;
21
22
  private base64ToBuffer;
22
23
  private handleError;
23
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ImagesClient.d.ts","sourceRoot":"","sources":["../../../src/lib/clients/ImagesClient.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,CAAC,MAAM,yBAAyB,CAAC;AAalD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,GAAG,CAAgB;gBAEf,MAAM,EAAE,CAAC,CAAC,MAAM;IAa5B,OAAO,CAAC,aAAa;IA6CR,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IActD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAkBnD,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIxD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAalF,gBAAgB,CAAC,KAAK,EAAE,CAAC,CAAC,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,GAAG,SAAS,CAAC;IAiBxE,qBAAqB,CAChC,KAAK,EAAE,CAAC,CAAC,sBAAsB,GAC9B,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC;IAkC/B,qBAAqB,CACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;IAUjC,0BAA0B,CAC9B,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;IAUjC,uBAAuB,CAC3B,KAAK,EAAE,CAAC,CAAC,4BAA4B,GACpC,OAAO,CAAC,CAAC,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAuBtC,mBAAmB,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;IA4BnB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;CAOpB"}
1
+ {"version":3,"file":"ImagesClient.d.ts","sourceRoot":"","sources":["../../../src/lib/clients/ImagesClient.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,CAAC,MAAM,yBAAyB,CAAC;AAalD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA6C;IAC3D,OAAO,CAAC,GAAG,CAAgB;gBAEf,MAAM,EAAE,CAAC,CAAC,MAAM;IAa5B,OAAO,CAAC,aAAa;IA4CR,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IActD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAkBnD,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3D,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAIxD,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAalF,gBAAgB,CAAC,KAAK,EAAE,CAAC,CAAC,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,GAAG,SAAS,CAAC;IAiBxE,qBAAqB,CAChC,KAAK,EAAE,CAAC,CAAC,sBAAsB,GAC9B,OAAO,CAAC,CAAC,CAAC,uBAAuB,CAAC;IAkC/B,qBAAqB,CACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;IA2BjC,0BAA0B,CAC9B,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,CAAC,CAAC,uBAAuB,EAAE,CAAC;IA2BjC,uBAAuB,CAC3B,KAAK,EAAE,CAAC,CAAC,4BAA4B,GACpC,OAAO,CAAC,CAAC,CAAC,uBAAuB,GAAG,IAAI,CAAC;IAuBtC,mBAAmB,CACvB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,OAAO,CAAC;YA4BL,0BAA0B;IAiGxC,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;CAOpB"}
@@ -1,9 +1,10 @@
1
1
  import admin from "firebase-admin";
2
2
  import { v4 as uuidv4 } from "uuid";
3
+ import { readFileSync } from "fs";
3
4
  import { ExternalAPIError } from "../Errors/AppError.js";
4
5
  import { Log } from "../utils/Logger.js";
5
6
  import { validateBase64Image } from "../utils/imageValidation.js";
6
- import { UserProgressPhoto } from "../dbmodels";
7
+ import { UserProgressPhoto } from "../dbmodels/user/UserProgressPhoto.js";
7
8
  const PROGRESS_DAYS = [
8
9
  1,
9
10
  30,
@@ -46,8 +47,7 @@ export class ImagesClient {
46
47
  const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_KEY;
47
48
  if (serviceAccountPath) {
48
49
  // Local development with service account file
49
- const fs = require("fs");
50
- const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountPath, "utf-8"));
50
+ const serviceAccount = JSON.parse(readFileSync(serviceAccountPath, "utf-8"));
51
51
  admin.initializeApp({
52
52
  credential: admin.credential.cert(serviceAccount),
53
53
  storageBucket
@@ -188,11 +188,50 @@ export class ImagesClient {
188
188
  "DESC"
189
189
  ]
190
190
  ],
191
- raw: true
191
+ raw: true,
192
+ attributes: [
193
+ 'uuid',
194
+ 'userUuid',
195
+ 'firebasePath',
196
+ 'photoDate',
197
+ 'programId',
198
+ 'createdAt',
199
+ 'updatedAt'
200
+ ]
192
201
  });
202
+ // If no photos in new system, attempt to migrate from legacy Firebase structure
203
+ if (records.length === 0) {
204
+ this.logger.info("No photos in new system, checking for legacy photos to migrate", {
205
+ userUuid
206
+ });
207
+ await this.migrateLegacyPhotosForUser(userUuid);
208
+ // Re-fetch after migration
209
+ const migratedRecords = await UserProgressPhoto.findAll({
210
+ where: {
211
+ userUuid
212
+ },
213
+ order: [
214
+ [
215
+ "photoDate",
216
+ "DESC"
217
+ ]
218
+ ],
219
+ raw: true,
220
+ attributes: [
221
+ 'uuid',
222
+ 'userUuid',
223
+ 'firebasePath',
224
+ 'photoDate',
225
+ 'programId',
226
+ 'createdAt',
227
+ 'updatedAt'
228
+ ]
229
+ });
230
+ return migratedRecords;
231
+ }
193
232
  return records;
194
233
  }
195
- async getProgressPhotosByProgram(programId) {
234
+ async getProgressPhotosByProgram(programId, userUuid) {
196
235
  const records = await UserProgressPhoto.findAll({
197
236
  where: {
198
237
  programId
@@ -203,8 +242,48 @@ export class ImagesClient {
203
242
  "DESC"
204
243
  ]
205
244
  ],
206
- raw: true
245
+ raw: true,
246
+ attributes: [
247
+ 'uuid',
248
+ 'userUuid',
249
+ 'firebasePath',
250
+ 'photoDate',
251
+ 'programId',
252
+ 'createdAt',
253
+ 'updatedAt'
254
+ ]
207
255
  });
256
+ // If no photos found and userUuid provided, attempt migration
257
+ if (records.length === 0 && userUuid) {
258
+ this.logger.info("No photos found for program, attempting migration", {
259
+ programId,
260
+ userUuid
261
+ });
262
+ await this.migrateLegacyPhotosForUser(userUuid);
263
+ // Re-fetch after migration
264
+ const migratedRecords = await UserProgressPhoto.findAll({
265
+ where: {
266
+ programId
267
+ },
268
+ order: [
269
+ [
270
+ "photoDate",
271
+ "DESC"
272
+ ]
273
+ ],
274
+ raw: true,
275
+ attributes: [
276
+ 'uuid',
277
+ 'userUuid',
278
+ 'firebasePath',
279
+ 'photoDate',
280
+ 'programId',
281
+ 'createdAt',
282
+ 'updatedAt'
283
+ ]
284
+ });
285
+ return migratedRecords;
286
+ }
208
287
  return records;
209
288
  }
210
289
  async updateProgressPhotoDate(input) {
@@ -256,6 +335,101 @@ export class ImagesClient {
256
335
  });
257
336
  return true;
258
337
  }
338
+ async migrateLegacyPhotosForUser(userUuid) {
339
+ try {
340
+ this.logger.info("Starting legacy photo migration", {
341
+ userUuid
342
+ });
343
+ // List all files in the legacy path pattern: {userUuid}--{day}day--{programId}.png
344
+ let files;
345
+ try {
346
+ [files] = await this.bucket.getFiles({
347
+ prefix: `${userUuid}--`
348
+ });
349
+ } catch (authError) {
350
+ this.logger.error("Firebase authentication failed during migration, skipping", {
351
+ userUuid,
352
+ error: authError instanceof Error ? authError.message : String(authError)
353
+ });
354
+ return; // Skip migration if auth fails
355
+ }
356
+ if (files.length === 0) {
357
+ this.logger.info("No legacy photos found for user", {
358
+ userUuid
359
+ });
360
+ return;
361
+ }
362
+ let migratedCount = 0;
363
+ let skippedCount = 0;
364
+ for (const file of files){
365
+ const fileName = file.name;
366
+ // Parse legacy filename: {userUuid}--{day}day--{programId}.png
367
+ const match = fileName.match(/^(.+)--(\d+)day--(.+)\.png$/);
368
+ if (!match) {
369
+ this.logger.warn("Skipping file with unexpected name format", {
370
+ fileName
371
+ });
372
+ continue;
373
+ }
374
+ const [, , dayStr, programId] = match;
375
+ const day = parseInt(dayStr, 10);
376
+ // Check if this photo was already migrated (by programId + userUuid + approximate day)
377
+ const existing = await UserProgressPhoto.findOne({
378
+ where: {
379
+ userUuid,
380
+ programId
381
+ },
382
+ raw: true
383
+ });
384
+ if (existing) {
385
+ this.logger.debug("Photo already migrated, skipping", {
386
+ fileName
387
+ });
388
+ continue;
389
+ }
390
+ // Download the legacy photo
391
+ const [contents] = await file.download();
392
+ // Calculate photoDate based on day offset (we don't have program start date, so use upload time)
393
+ // In reality, this would need Program model to calculate exact date
394
+ const photoDate = new Date(); // Fallback: use current time
395
+ photoDate.setDate(photoDate.getDate() - (90 - day)); // Rough estimate based on day
396
+ // Generate new UUID and path
397
+ const newUuid = uuidv4();
398
+ const newFirebasePath = `progress-pictures/${userUuid}/${newUuid}.png`;
399
+ // Copy to new Firebase location
400
+ await this.bucket.file(newFirebasePath).save(contents, {
401
+ metadata: {
402
+ contentType: "image/png"
403
+ }
404
+ });
405
+ // Create database record
406
+ await UserProgressPhoto.create({
407
+ uuid: newUuid,
408
+ userUuid,
409
+ firebasePath: newFirebasePath,
410
+ photoDate,
411
+ programId
412
+ });
413
+ this.logger.info("Migrated legacy photo", {
414
+ oldPath: fileName,
415
+ newPath: newFirebasePath,
416
+ day,
417
+ programId
418
+ });
419
+ }
420
+ this.logger.info("Legacy photo migration completed", {
421
+ userUuid,
422
+ migrated: migratedCount,
423
+ skipped: skippedCount
424
+ });
425
+ } catch (error) {
426
+ this.logger.error("Failed to migrate legacy photos", {
427
+ userUuid,
428
+ error
429
+ });
430
+ // Don't throw - migration failure shouldn't break the request
431
+ }
432
+ }
259
433
  base64ToBuffer(base64) {
260
434
  const data = base64.includes(",") ? base64.split(",")[1] : base64;
261
435
  return Buffer.from(data, "base64");
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/clients/ImagesClient.ts"],"sourcesContent":["import admin from \"firebase-admin\";\nimport { Bucket } from \"@google-cloud/storage\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { ExternalAPIError } from \"../Errors/AppError.js\";\nimport { Log } from \"../utils/Logger.js\";\nimport { validateBase64Image } from \"../utils/imageValidation.js\";\nimport { UserProgressPhoto } from \"../dbmodels/index.js\";\nimport type * as t from \"./types/images.types.js\";\n\nconst PROGRESS_DAYS = [1, 30, 60, 90];\n\ntype RecipePhotoType = \"ingredients\" | \"cover\" | \"method\";\nconst RECIPE_PHOTO_TYPES: RecipePhotoType[] = [\"ingredients\", \"cover\", \"method\"];\n\nconst imageName = {\n avatar: (userUuid: string) => `${userUuid}--avatar.png`,\n progress: (userUuid: string, day: number, programId: string) => `${userUuid}--${day}day--${programId}.png`,\n recipe: (recipeUuid: string, type: RecipePhotoType) => `${recipeUuid}--${type}.png`,\n};\n\nexport class ImagesClient {\n private bucket: Bucket;\n private logger = Log.getInstance().extend(\"images-client\");\n private app: admin.app.App;\n\n constructor(config: t.Config) {\n if (config.firebaseApp) {\n // Use provided Firebase app\n this.app = config.firebaseApp;\n } else {\n // Initialize Firebase if needed (following FirebasePushNotificationClient pattern)\n this.initializeApp(config.storageBucket);\n this.app = admin.app();\n }\n\n this.bucket = this.app.storage().bucket(config.storageBucket);\n }\n\n private initializeApp(storageBucket: string): void {\n try {\n // Check if app already exists\n if (admin.apps?.length && admin.apps.length > 0) {\n this.logger.info(\"Using existing Firebase app\");\n return;\n }\n\n // Try to load service account from environment (for local development)\n const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_KEY;\n\n if (serviceAccountPath) {\n // Local development with service account file\n const fs = require(\"fs\");\n const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountPath, \"utf-8\"));\n\n admin.initializeApp({\n credential: admin.credential.cert(serviceAccount),\n storageBucket,\n });\n\n this.logger.info(\"Firebase initialized with service account file\", {\n projectId: serviceAccount.project_id,\n storageBucket,\n });\n } else {\n // Production: use application default credentials\n admin.initializeApp({\n credential: admin.credential.applicationDefault(),\n storageBucket,\n });\n\n this.logger.info(\"Firebase initialized with application default credentials\", {\n storageBucket,\n });\n }\n } catch (error) {\n this.logger.error(\"Failed to initialize Firebase Admin SDK\", { error });\n throw new ExternalAPIError(\n \"Failed to initialize Firebase\",\n `Error: ${String(error)}`\n );\n }\n }\n\n public async saveImage(base64: string, name: string): Promise<void> {\n try {\n this.logger.info(\"Saving image\", { name });\n\n const buffer = this.base64ToBuffer(base64);\n const file = this.bucket.file(name);\n await file.save(buffer, { contentType: \"image/png\" });\n\n this.logger.info(\"Image saved successfully\", { name });\n } catch (error) {\n this.handleError(error, \"saveImage\");\n }\n }\n\n public async getImage(name: string): Promise<string | undefined> {\n try {\n this.logger.info(\"Getting image\", { name });\n\n const file = this.bucket.file(name);\n const [exists] = await file.exists();\n if (!exists) {\n return undefined;\n }\n\n const [contents] = await file.download();\n return \"data:image/jpeg;base64,\" + contents.toString(\"base64\");\n } catch (error) {\n this.logger.error(`Failed to get image: ${name}`, { error });\n return undefined;\n }\n }\n\n public async saveAvatar(userUuid: string, base64: string): Promise<void> {\n await this.saveImage(base64, imageName.avatar(userUuid));\n }\n\n public async getAvatar(userUuid: string): Promise<string | undefined> {\n return this.getImage(imageName.avatar(userUuid));\n }\n\n public async saveProgressPhoto(userUuid: string, day: number, programId: string, base64: string): Promise<void> {\n await this.saveImage(base64, imageName.progress(userUuid, day, programId));\n }\n\n public async getProgressPhotos(userUuid: string, programId: string): Promise<t.ProgressPhoto[]> {\n const photos: t.ProgressPhoto[] = [];\n\n for (const day of PROGRESS_DAYS) {\n const base64 = await this.getImage(imageName.progress(userUuid, day, programId));\n if (base64) {\n photos.push({ day, base64 });\n }\n }\n\n return photos;\n }\n\n public async saveRecipePhotos(input: t.SaveRecipePhotosInput): Promise<void> {\n await Promise.all([\n this.saveImage(input.ingredientBase64, imageName.recipe(input.recipeUuid, \"ingredients\")),\n this.saveImage(input.coverBase64, imageName.recipe(input.recipeUuid, \"cover\")),\n this.saveImage(input.methodBase64, imageName.recipe(input.recipeUuid, \"method\")),\n ]);\n }\n\n public async getRecipePhotos(recipeUuid: string): Promise<t.RecipePhotos | undefined> {\n const results = await Promise.all(\n RECIPE_PHOTO_TYPES.map((type) => this.getImage(imageName.recipe(recipeUuid, type)))\n );\n\n if (results.every((r) => !r)) {\n return undefined;\n }\n\n // BUG: cover/method swapped — preserved from original ImageController.ts to keep backward compat with frontend\n return {\n ingredientBase64: results[0] ?? \"\",\n methodBase64: results[1] ?? \"\",\n coverBase64: results[2] ?? \"\",\n };\n }\n\n public async saveUserProgressPhoto(\n input: t.UserProgressPhotoInput\n ): Promise<t.UserProgressPhotoRecord> {\n // Validate image\n validateBase64Image(input.base64);\n\n // Generate UUID for the photo\n const uuid = uuidv4();\n\n // Prepare Firebase path\n const firebasePath = `progress-pictures/${input.userUuid}/${uuid}.png`;\n\n // Upload to Firebase Storage\n const base64Data = input.base64.replace(/^data:image\\/\\w+;base64,/, \"\");\n const buffer = Buffer.from(base64Data, \"base64\");\n\n const file = this.bucket.file(firebasePath);\n await file.save(buffer, {\n metadata: {\n contentType: \"image/png\",\n },\n });\n\n // Save metadata to database\n const photoDate = input.photoDate || new Date();\n const record = await UserProgressPhoto.create({\n uuid,\n userUuid: input.userUuid,\n firebasePath,\n photoDate,\n programId: input.programId || null,\n });\n\n return record.toJSON() as t.UserProgressPhotoRecord;\n }\n\n async getUserProgressPhotos(\n userUuid: string\n ): Promise<t.UserProgressPhotoRecord[]> {\n const records = await UserProgressPhoto.findAll({\n where: { userUuid },\n order: [[\"photoDate\", \"DESC\"]],\n raw: true,\n });\n\n return records as t.UserProgressPhotoRecord[];\n }\n\n async getProgressPhotosByProgram(\n programId: string\n ): Promise<t.UserProgressPhotoRecord[]> {\n const records = await UserProgressPhoto.findAll({\n where: { programId },\n order: [[\"photoDate\", \"DESC\"]],\n raw: true,\n });\n\n return records as t.UserProgressPhotoRecord[];\n }\n\n async updateProgressPhotoDate(\n input: t.UpdateProgressPhotoDateInput\n ): Promise<t.UserProgressPhotoRecord | null> {\n const [affectedRows] = await UserProgressPhoto.update(\n { photoDate: input.photoDate },\n {\n where: {\n uuid: input.uuid,\n userUuid: input.userUuid, // Ensure user owns the photo\n },\n }\n );\n\n if (affectedRows === 0) {\n return null; // Photo not found or user doesn't own it\n }\n\n const updated = await UserProgressPhoto.findOne({\n where: { uuid: input.uuid },\n raw: true,\n });\n\n return updated as t.UserProgressPhotoRecord;\n }\n\n async deleteProgressPhoto(\n uuid: string,\n userUuid: string\n ): Promise<boolean> {\n // Find the photo to get the firebasePath\n const photo = await UserProgressPhoto.findOne({\n where: { uuid, userUuid },\n raw: true,\n });\n\n if (!photo) {\n return false; // Photo not found or user doesn't own it\n }\n\n // Delete from Firebase Storage\n const file = this.bucket.file(photo.firebasePath);\n try {\n await file.delete();\n } catch (error) {\n // If file doesn't exist in storage, continue with database deletion\n console.warn(`Failed to delete file from storage: ${photo.firebasePath}`, error);\n }\n\n // Delete from database\n await UserProgressPhoto.destroy({\n where: { uuid, userUuid },\n });\n\n return true;\n }\n\n private base64ToBuffer(base64: string): Buffer {\n const data = base64.includes(\",\") ? base64.split(\",\")[1]! : base64;\n return Buffer.from(data, \"base64\");\n }\n\n private handleError(error: unknown, method: string): never {\n this.logger.error(`Firebase Storage error in ${method}`, { error });\n throw new ExternalAPIError(\n `Firebase Storage error: ${error instanceof Error ? error.message : String(error)}`,\n `Service: Firebase Storage, Method: ${method}`\n );\n }\n}\n"],"names":["admin","v4","uuidv4","ExternalAPIError","Log","validateBase64Image","UserProgressPhoto","PROGRESS_DAYS","RECIPE_PHOTO_TYPES","imageName","avatar","userUuid","progress","day","programId","recipe","recipeUuid","type","ImagesClient","bucket","logger","getInstance","extend","app","config","firebaseApp","initializeApp","storageBucket","storage","apps","length","info","serviceAccountPath","process","env","FIREBASE_SERVICE_ACCOUNT_KEY","fs","require","serviceAccount","JSON","parse","readFileSync","credential","cert","projectId","project_id","applicationDefault","error","String","saveImage","base64","name","buffer","base64ToBuffer","file","save","contentType","handleError","getImage","exists","undefined","contents","download","toString","saveAvatar","getAvatar","saveProgressPhoto","getProgressPhotos","photos","push","saveRecipePhotos","input","Promise","all","ingredientBase64","coverBase64","methodBase64","getRecipePhotos","results","map","every","r","saveUserProgressPhoto","uuid","firebasePath","base64Data","replace","Buffer","from","metadata","photoDate","Date","record","create","toJSON","getUserProgressPhotos","records","findAll","where","order","raw","getProgressPhotosByProgram","updateProgressPhotoDate","affectedRows","update","updated","findOne","deleteProgressPhoto","photo","delete","console","warn","destroy","data","includes","split","method","Error","message"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/lib/clients/ImagesClient.ts"],"sourcesContent":["import admin from \"firebase-admin\";\nimport { Bucket } from \"@google-cloud/storage\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { readFileSync } from \"fs\";\nimport { ExternalAPIError } from \"../Errors/AppError.js\";\nimport { Log } from \"../utils/Logger.js\";\nimport { validateBase64Image } from \"../utils/imageValidation.js\";\nimport { UserProgressPhoto } from \"../dbmodels/user/UserProgressPhoto.js\";\nimport type * as t from \"./types/images.types.js\";\n\nconst PROGRESS_DAYS = [1, 30, 60, 90];\n\ntype RecipePhotoType = \"ingredients\" | \"cover\" | \"method\";\nconst RECIPE_PHOTO_TYPES: RecipePhotoType[] = [\"ingredients\", \"cover\", \"method\"];\n\nconst imageName = {\n avatar: (userUuid: string) => `${userUuid}--avatar.png`,\n progress: (userUuid: string, day: number, programId: string) => `${userUuid}--${day}day--${programId}.png`,\n recipe: (recipeUuid: string, type: RecipePhotoType) => `${recipeUuid}--${type}.png`,\n};\n\nexport class ImagesClient {\n private bucket: Bucket;\n private logger = Log.getInstance().extend(\"images-client\");\n private app: admin.app.App;\n\n constructor(config: t.Config) {\n if (config.firebaseApp) {\n // Use provided Firebase app\n this.app = config.firebaseApp;\n } else {\n // Initialize Firebase if needed (following FirebasePushNotificationClient pattern)\n this.initializeApp(config.storageBucket);\n this.app = admin.app();\n }\n\n this.bucket = this.app.storage().bucket(config.storageBucket);\n }\n\n private initializeApp(storageBucket: string): void {\n try {\n // Check if app already exists\n if (admin.apps?.length && admin.apps.length > 0) {\n this.logger.info(\"Using existing Firebase app\");\n return;\n }\n\n // Try to load service account from environment (for local development)\n const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_KEY;\n\n if (serviceAccountPath) {\n // Local development with service account file\n const serviceAccount = JSON.parse(readFileSync(serviceAccountPath, \"utf-8\"));\n\n admin.initializeApp({\n credential: admin.credential.cert(serviceAccount),\n storageBucket,\n });\n\n this.logger.info(\"Firebase initialized with service account file\", {\n projectId: serviceAccount.project_id,\n storageBucket,\n });\n } else {\n // Production: use application default credentials\n admin.initializeApp({\n credential: admin.credential.applicationDefault(),\n storageBucket,\n });\n\n this.logger.info(\"Firebase initialized with application default credentials\", {\n storageBucket,\n });\n }\n } catch (error) {\n this.logger.error(\"Failed to initialize Firebase Admin SDK\", { error });\n throw new ExternalAPIError(\n \"Failed to initialize Firebase\",\n `Error: ${String(error)}`\n );\n }\n }\n\n public async saveImage(base64: string, name: string): Promise<void> {\n try {\n this.logger.info(\"Saving image\", { name });\n\n const buffer = this.base64ToBuffer(base64);\n const file = this.bucket.file(name);\n await file.save(buffer, { contentType: \"image/png\" });\n\n this.logger.info(\"Image saved successfully\", { name });\n } catch (error) {\n this.handleError(error, \"saveImage\");\n }\n }\n\n public async getImage(name: string): Promise<string | undefined> {\n try {\n this.logger.info(\"Getting image\", { name });\n\n const file = this.bucket.file(name);\n const [exists] = await file.exists();\n if (!exists) {\n return undefined;\n }\n\n const [contents] = await file.download();\n return \"data:image/jpeg;base64,\" + contents.toString(\"base64\");\n } catch (error) {\n this.logger.error(`Failed to get image: ${name}`, { error });\n return undefined;\n }\n }\n\n public async saveAvatar(userUuid: string, base64: string): Promise<void> {\n await this.saveImage(base64, imageName.avatar(userUuid));\n }\n\n public async getAvatar(userUuid: string): Promise<string | undefined> {\n return this.getImage(imageName.avatar(userUuid));\n }\n\n public async saveProgressPhoto(userUuid: string, day: number, programId: string, base64: string): Promise<void> {\n await this.saveImage(base64, imageName.progress(userUuid, day, programId));\n }\n\n public async getProgressPhotos(userUuid: string, programId: string): Promise<t.ProgressPhoto[]> {\n const photos: t.ProgressPhoto[] = [];\n\n for (const day of PROGRESS_DAYS) {\n const base64 = await this.getImage(imageName.progress(userUuid, day, programId));\n if (base64) {\n photos.push({ day, base64 });\n }\n }\n\n return photos;\n }\n\n public async saveRecipePhotos(input: t.SaveRecipePhotosInput): Promise<void> {\n await Promise.all([\n this.saveImage(input.ingredientBase64, imageName.recipe(input.recipeUuid, \"ingredients\")),\n this.saveImage(input.coverBase64, imageName.recipe(input.recipeUuid, \"cover\")),\n this.saveImage(input.methodBase64, imageName.recipe(input.recipeUuid, \"method\")),\n ]);\n }\n\n public async getRecipePhotos(recipeUuid: string): Promise<t.RecipePhotos | undefined> {\n const results = await Promise.all(\n RECIPE_PHOTO_TYPES.map((type) => this.getImage(imageName.recipe(recipeUuid, type)))\n );\n\n if (results.every((r) => !r)) {\n return undefined;\n }\n\n // BUG: cover/method swapped — preserved from original ImageController.ts to keep backward compat with frontend\n return {\n ingredientBase64: results[0] ?? \"\",\n methodBase64: results[1] ?? \"\",\n coverBase64: results[2] ?? \"\",\n };\n }\n\n public async saveUserProgressPhoto(\n input: t.UserProgressPhotoInput\n ): Promise<t.UserProgressPhotoRecord> {\n // Validate image\n validateBase64Image(input.base64);\n\n // Generate UUID for the photo\n const uuid = uuidv4();\n\n // Prepare Firebase path\n const firebasePath = `progress-pictures/${input.userUuid}/${uuid}.png`;\n\n // Upload to Firebase Storage\n const base64Data = input.base64.replace(/^data:image\\/\\w+;base64,/, \"\");\n const buffer = Buffer.from(base64Data, \"base64\");\n\n const file = this.bucket.file(firebasePath);\n await file.save(buffer, {\n metadata: {\n contentType: \"image/png\",\n },\n });\n\n // Save metadata to database\n const photoDate = input.photoDate || new Date();\n const record = await UserProgressPhoto.create({\n uuid,\n userUuid: input.userUuid,\n firebasePath,\n photoDate,\n programId: input.programId || null,\n });\n\n return record.toJSON() as t.UserProgressPhotoRecord;\n }\n\n async getUserProgressPhotos(\n userUuid: string\n ): Promise<t.UserProgressPhotoRecord[]> {\n const records = await UserProgressPhoto.findAll({\n where: { userUuid },\n order: [[\"photoDate\", \"DESC\"]],\n raw: true,\n attributes: ['uuid', 'userUuid', 'firebasePath', 'photoDate', 'programId', 'createdAt', 'updatedAt'],\n });\n\n // If no photos in new system, attempt to migrate from legacy Firebase structure\n if (records.length === 0) {\n this.logger.info(\"No photos in new system, checking for legacy photos to migrate\", { userUuid });\n await this.migrateLegacyPhotosForUser(userUuid);\n\n // Re-fetch after migration\n const migratedRecords = await UserProgressPhoto.findAll({\n where: { userUuid },\n order: [[\"photoDate\", \"DESC\"]],\n raw: true,\n attributes: ['uuid', 'userUuid', 'firebasePath', 'photoDate', 'programId', 'createdAt', 'updatedAt'],\n });\n\n return migratedRecords as t.UserProgressPhotoRecord[];\n }\n\n return records as t.UserProgressPhotoRecord[];\n }\n\n async getProgressPhotosByProgram(\n programId: string,\n userUuid?: string\n ): Promise<t.UserProgressPhotoRecord[]> {\n const records = await UserProgressPhoto.findAll({\n where: { programId },\n order: [[\"photoDate\", \"DESC\"]],\n raw: true,\n attributes: ['uuid', 'userUuid', 'firebasePath', 'photoDate', 'programId', 'createdAt', 'updatedAt'],\n });\n\n // If no photos found and userUuid provided, attempt migration\n if (records.length === 0 && userUuid) {\n this.logger.info(\"No photos found for program, attempting migration\", { programId, userUuid });\n await this.migrateLegacyPhotosForUser(userUuid);\n\n // Re-fetch after migration\n const migratedRecords = await UserProgressPhoto.findAll({\n where: { programId },\n order: [[\"photoDate\", \"DESC\"]],\n raw: true,\n attributes: ['uuid', 'userUuid', 'firebasePath', 'photoDate', 'programId', 'createdAt', 'updatedAt'],\n });\n\n return migratedRecords as t.UserProgressPhotoRecord[];\n }\n\n return records as t.UserProgressPhotoRecord[];\n }\n\n async updateProgressPhotoDate(\n input: t.UpdateProgressPhotoDateInput\n ): Promise<t.UserProgressPhotoRecord | null> {\n const [affectedRows] = await UserProgressPhoto.update(\n { photoDate: input.photoDate },\n {\n where: {\n uuid: input.uuid,\n userUuid: input.userUuid, // Ensure user owns the photo\n },\n }\n );\n\n if (affectedRows === 0) {\n return null; // Photo not found or user doesn't own it\n }\n\n const updated = await UserProgressPhoto.findOne({\n where: { uuid: input.uuid },\n raw: true,\n });\n\n return updated as t.UserProgressPhotoRecord;\n }\n\n async deleteProgressPhoto(\n uuid: string,\n userUuid: string\n ): Promise<boolean> {\n // Find the photo to get the firebasePath\n const photo = await UserProgressPhoto.findOne({\n where: { uuid, userUuid },\n raw: true,\n });\n\n if (!photo) {\n return false; // Photo not found or user doesn't own it\n }\n\n // Delete from Firebase Storage\n const file = this.bucket.file(photo.firebasePath);\n try {\n await file.delete();\n } catch (error) {\n // If file doesn't exist in storage, continue with database deletion\n console.warn(`Failed to delete file from storage: ${photo.firebasePath}`, error);\n }\n\n // Delete from database\n await UserProgressPhoto.destroy({\n where: { uuid, userUuid },\n });\n\n return true;\n }\n\n private async migrateLegacyPhotosForUser(userUuid: string): Promise<void> {\n try {\n this.logger.info(\"Starting legacy photo migration\", { userUuid });\n\n // List all files in the legacy path pattern: {userUuid}--{day}day--{programId}.png\n let files;\n try {\n [files] = await this.bucket.getFiles({\n prefix: `${userUuid}--`,\n });\n } catch (authError) {\n this.logger.error(\"Firebase authentication failed during migration, skipping\", {\n userUuid,\n error: authError instanceof Error ? authError.message : String(authError),\n });\n return; // Skip migration if auth fails\n }\n\n if (files.length === 0) {\n this.logger.info(\"No legacy photos found for user\", { userUuid });\n return;\n }\n\n let migratedCount = 0;\n let skippedCount = 0;\n\n for (const file of files) {\n const fileName = file.name;\n\n // Parse legacy filename: {userUuid}--{day}day--{programId}.png\n const match = fileName.match(/^(.+)--(\\d+)day--(.+)\\.png$/);\n if (!match) {\n this.logger.warn(\"Skipping file with unexpected name format\", { fileName });\n continue;\n }\n\n const [, , dayStr, programId] = match;\n const day = parseInt(dayStr, 10);\n\n // Check if this photo was already migrated (by programId + userUuid + approximate day)\n const existing = await UserProgressPhoto.findOne({\n where: { userUuid, programId },\n raw: true,\n });\n\n if (existing) {\n this.logger.debug(\"Photo already migrated, skipping\", { fileName });\n continue;\n }\n\n // Download the legacy photo\n const [contents] = await file.download();\n\n // Calculate photoDate based on day offset (we don't have program start date, so use upload time)\n // In reality, this would need Program model to calculate exact date\n const photoDate = new Date(); // Fallback: use current time\n photoDate.setDate(photoDate.getDate() - (90 - day)); // Rough estimate based on day\n\n // Generate new UUID and path\n const newUuid = uuidv4();\n const newFirebasePath = `progress-pictures/${userUuid}/${newUuid}.png`;\n\n // Copy to new Firebase location\n await this.bucket.file(newFirebasePath).save(contents, {\n metadata: {\n contentType: \"image/png\",\n },\n });\n\n // Create database record\n await UserProgressPhoto.create({\n uuid: newUuid,\n userUuid,\n firebasePath: newFirebasePath,\n photoDate,\n programId,\n });\n\n this.logger.info(\"Migrated legacy photo\", {\n oldPath: fileName,\n newPath: newFirebasePath,\n day,\n programId,\n });\n }\n\n this.logger.info(\"Legacy photo migration completed\", {\n userUuid,\n migrated: migratedCount,\n skipped: skippedCount,\n });\n } catch (error) {\n this.logger.error(\"Failed to migrate legacy photos\", { userUuid, error });\n // Don't throw - migration failure shouldn't break the request\n }\n }\n\n private base64ToBuffer(base64: string): Buffer {\n const data = base64.includes(\",\") ? base64.split(\",\")[1]! : base64;\n return Buffer.from(data, \"base64\");\n }\n\n private handleError(error: unknown, method: string): never {\n this.logger.error(`Firebase Storage error in ${method}`, { error });\n throw new ExternalAPIError(\n `Firebase Storage error: ${error instanceof Error ? error.message : String(error)}`,\n `Service: Firebase Storage, Method: ${method}`\n );\n }\n}\n"],"names":["admin","v4","uuidv4","readFileSync","ExternalAPIError","Log","validateBase64Image","UserProgressPhoto","PROGRESS_DAYS","RECIPE_PHOTO_TYPES","imageName","avatar","userUuid","progress","day","programId","recipe","recipeUuid","type","ImagesClient","bucket","logger","getInstance","extend","app","config","firebaseApp","initializeApp","storageBucket","storage","apps","length","info","serviceAccountPath","process","env","FIREBASE_SERVICE_ACCOUNT_KEY","serviceAccount","JSON","parse","credential","cert","projectId","project_id","applicationDefault","error","String","saveImage","base64","name","buffer","base64ToBuffer","file","save","contentType","handleError","getImage","exists","undefined","contents","download","toString","saveAvatar","getAvatar","saveProgressPhoto","getProgressPhotos","photos","push","saveRecipePhotos","input","Promise","all","ingredientBase64","coverBase64","methodBase64","getRecipePhotos","results","map","every","r","saveUserProgressPhoto","uuid","firebasePath","base64Data","replace","Buffer","from","metadata","photoDate","Date","record","create","toJSON","getUserProgressPhotos","records","findAll","where","order","raw","attributes","migrateLegacyPhotosForUser","migratedRecords","getProgressPhotosByProgram","updateProgressPhotoDate","affectedRows","update","updated","findOne","deleteProgressPhoto","photo","delete","console","warn","destroy","files","getFiles","prefix","authError","Error","message","migratedCount","skippedCount","fileName","match","dayStr","parseInt","existing","debug","setDate","getDate","newUuid","newFirebasePath","oldPath","newPath","migrated","skipped","data","includes","split","method"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "90dc-core",
3
- "version": "1.19.5",
3
+ "version": "1.19.7",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",