90dc-core 1.18.1 → 1.19.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.
Files changed (28) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/clients/ImagesClient.d.ts +7 -0
  6. package/dist/lib/clients/ImagesClient.d.ts.map +1 -1
  7. package/dist/lib/clients/ImagesClient.js +157 -2
  8. package/dist/lib/clients/ImagesClient.js.map +1 -1
  9. package/dist/lib/clients/types/images.types.d.ts +20 -0
  10. package/dist/lib/clients/types/images.types.d.ts.map +1 -1
  11. package/dist/lib/clients/types/images.types.js.map +1 -1
  12. package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.d.ts +7 -0
  13. package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.d.ts.map +1 -0
  14. package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.js +60 -0
  15. package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.js.map +1 -0
  16. package/dist/lib/db/models/UserProgressPhoto.d.ts +13 -0
  17. package/dist/lib/db/models/UserProgressPhoto.d.ts.map +1 -0
  18. package/dist/lib/db/models/UserProgressPhoto.js +59 -0
  19. package/dist/lib/db/models/UserProgressPhoto.js.map +1 -0
  20. package/dist/lib/db/models/index.d.ts +2 -0
  21. package/dist/lib/db/models/index.d.ts.map +1 -0
  22. package/dist/lib/db/models/index.js +3 -0
  23. package/dist/lib/db/models/index.js.map +1 -0
  24. package/dist/lib/utils/imageValidation.d.ts +5 -0
  25. package/dist/lib/utils/imageValidation.d.ts.map +1 -0
  26. package/dist/lib/utils/imageValidation.js +31 -0
  27. package/dist/lib/utils/imageValidation.js.map +1 -0
  28. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -13,11 +13,13 @@ export * as EmailTypes from "./lib/clients/types/email.types.js";
13
13
  export * as ImagesTypes from "./lib/clients/types/images.types.js";
14
14
  export * as MailingTypes from "./lib/clients/types/mailing.types.js";
15
15
  export type { NotificationPayload } from "./lib/clients/PushNotificationClient.js";
16
+ export type { UserProgressPhotoInput, UserProgressPhotoRecord, UpdateProgressPhotoDateInput, } from "./lib/clients/types/images.types.js";
16
17
  export { AuthenticationUtil } from "./lib/utils/AuthenticationUtil.js";
17
18
  export { NotificationsUtil } from "./lib/utils/NotificationsUtil.js";
18
19
  export { NotificationClient } from "./lib/utils/NotificationClient.js";
19
20
  export { Log } from "./lib/utils/Logger.js";
20
21
  export { SecretManager } from "./lib/utils/SecretManager.js";
22
+ export { validateBase64Image, ImageValidationError } from "./lib/utils/imageValidation.js";
21
23
  export { initializeSentry, isSentryEnabled, scrubObject, captureRequestBody, extractUserContext, buildRequestContext, buildResponseContext, reportErrorToSentry, reportMessageToSentry, type ErrorReportOptions, type SeverityLevel, } from "./lib/utils/SentryUtil.js";
22
24
  export { createMockContext, createMockUser, createAuthenticatedContext, mockDatabase, flushPromises, assertions, executeRoute, type BaseContext, type AuthenticatedContext, type RouterContext, type RouterMiddleware, type RouterLayer, type RouterLike, type SequelizeModelMethods, } from "./lib/testing/testHelpers.js";
23
25
  export { TEST_UUIDS, TEST_DATES, TEST_DATA, isValidUUID, generateTestEmail, createTestUser, createTestCoach, createTestHeadCoach, createTestFinanceUser, createTestAdmin, createTestClient, wait, freezeTime, testAssertions, mockResponses, isObject, isArray, } from "./lib/testing/testFixtures.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wCAAwC,CAAC;AAGvD,cAAc,yBAAyB,CAAA;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAA;AAChF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAA;AAGhG,OAAO,KAAK,UAAU,MAAM,oCAAoC,CAAA;AAChE,OAAO,KAAK,WAAW,MAAM,qCAAqC,CAAA;AAClE,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAGlF,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,GAC3B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,IAAI,EACJ,UAAU,EACV,cAAc,EACd,aAAa,EACb,QAAQ,EACR,OAAO,GACR,MAAM,+BAA+B,CAAA;AAGtC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,aAAa,EACb,wBAAwB,EACxB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,UAAU,EACX,MAAM,0BAA0B,CAAA;AAGjC,OAAO,EACL,eAAe,GAChB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAGvF,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAC,cAAc,EAAE,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,mCAAmC,CAAC;AAClD,cAAc,oCAAoC,CAAC;AACnD,cAAc,mCAAmC,CAAC;AAClD,cAAc,gCAAgC,CAAC;AAC/C,cAAc,wCAAwC,CAAC;AAGvD,cAAc,yBAAyB,CAAA;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yCAAyC,CAAA;AAChF,OAAO,EAAE,8BAA8B,EAAE,MAAM,iDAAiD,CAAA;AAGhG,OAAO,KAAK,UAAU,MAAM,oCAAoC,CAAA;AAChE,OAAO,KAAK,WAAW,MAAM,qCAAqC,CAAA;AAClE,OAAO,KAAK,YAAY,MAAM,sCAAsC,CAAA;AACpE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yCAAyC,CAAA;AAGlF,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,GAC7B,MAAM,qCAAqC,CAAA;AAG5C,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,iBAAiB,EAAC,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAC,kBAAkB,EAAC,MAAM,mCAAmC,CAAA;AACpE,OAAO,EAAC,GAAG,EAAC,MAAM,uBAAuB,CAAA;AACzC,OAAO,EAAC,aAAa,EAAC,MAAM,8BAA8B,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAA;AAC1F,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,2BAA2B,CAAA;AAGlC,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,UAAU,EACV,YAAY,EACZ,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,qBAAqB,GAC3B,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EACL,UAAU,EACV,UAAU,EACV,SAAS,EACT,WAAW,EACX,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,gBAAgB,EAChB,IAAI,EACJ,UAAU,EACV,cAAc,EACd,aAAa,EACb,QAAQ,EACR,OAAO,GACR,MAAM,+BAA+B,CAAA;AAGtC,OAAO,EACL,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,KAAK,UAAU,EAChB,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,QAAQ,EACR,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,aAAa,EACb,wBAAwB,EACxB,cAAc,EACd,mBAAmB,EACnB,uBAAuB,EACvB,aAAa,EACb,gBAAgB,EAChB,UAAU,EACV,kBAAkB,EAClB,UAAU,EACX,MAAM,0BAA0B,CAAA;AAGjC,OAAO,EACL,eAAe,GAChB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAG5F,OAAO,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,qCAAqC,CAAC;AAGvF,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAC,cAAc,EAAE,KAAK,cAAc,EAAC,MAAM,2BAA2B,CAAA"}
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ export { NotificationsUtil } from "./lib/utils/NotificationsUtil.js";
22
22
  export { NotificationClient } from "./lib/utils/NotificationClient.js";
23
23
  export { Log } from "./lib/utils/Logger.js";
24
24
  export { SecretManager } from "./lib/utils/SecretManager.js";
25
+ export { validateBase64Image, ImageValidationError } from "./lib/utils/imageValidation.js";
25
26
  export { initializeSentry, isSentryEnabled, scrubObject, captureRequestBody, extractUserContext, buildRequestContext, buildResponseContext, reportErrorToSentry, reportMessageToSentry } from "./lib/utils/SentryUtil.js";
26
27
  //Testing Utilities
27
28
  export { createMockContext, createMockUser, createAuthenticatedContext, mockDatabase, flushPromises, assertions, executeRoute } from "./lib/testing/testHelpers.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["//Interfaces\nexport * from \"./lib/models/ProgramInterfaces.js\";\nexport * from \"./lib/models/ExerciseInterfaces.js\";\nexport * from \"./lib/models/WorkoutInterfaces.js\";\nexport * from \"./lib/models/UserInterfaces.js\";\nexport * from \"./lib/models/NotificationInterfaces.js\";\n\n//DB Models - Export all models and model arrays\nexport * from \"./lib/dbmodels/index.js\"\n\n//Clients\nexport { EmailClient } from \"./lib/clients/EmailClient.js\"\nexport { ImagesClient } from \"./lib/clients/ImagesClient.js\"\nexport { MailingClient } from \"./lib/clients/MailingClient.js\"\nexport { PushNotificationClient } from \"./lib/clients/PushNotificationClient.js\"\nexport { FirebasePushNotificationClient } from \"./lib/clients/FirebasePushNotificationClient.js\"\n\n//Client Types (namespace imports)\nexport * as EmailTypes from \"./lib/clients/types/email.types.js\"\nexport * as ImagesTypes from \"./lib/clients/types/images.types.js\"\nexport * as MailingTypes from \"./lib/clients/types/mailing.types.js\"\nexport type { NotificationPayload } from \"./lib/clients/PushNotificationClient.js\"\n\n//Utils\nexport {AuthenticationUtil} from \"./lib/utils/AuthenticationUtil.js\"\nexport {NotificationsUtil} from \"./lib/utils/NotificationsUtil.js\"\nexport {NotificationClient} from \"./lib/utils/NotificationClient.js\"\nexport {Log} from \"./lib/utils/Logger.js\"\nexport {SecretManager} from \"./lib/utils/SecretManager.js\"\nexport {\n initializeSentry,\n isSentryEnabled,\n scrubObject,\n captureRequestBody,\n extractUserContext,\n buildRequestContext,\n buildResponseContext,\n reportErrorToSentry,\n reportMessageToSentry,\n type ErrorReportOptions,\n type SeverityLevel,\n} from \"./lib/utils/SentryUtil.js\"\n\n//Testing Utilities\nexport {\n createMockContext,\n createMockUser,\n createAuthenticatedContext,\n mockDatabase,\n flushPromises,\n assertions,\n executeRoute,\n type BaseContext,\n type AuthenticatedContext,\n type RouterContext,\n type RouterMiddleware,\n type RouterLayer,\n type RouterLike,\n type SequelizeModelMethods,\n} from \"./lib/testing/testHelpers.js\"\n\n// Testing Fixtures\nexport {\n TEST_UUIDS,\n TEST_DATES,\n TEST_DATA,\n isValidUUID,\n generateTestEmail,\n createTestUser,\n createTestCoach,\n createTestHeadCoach,\n createTestFinanceUser,\n createTestAdmin,\n createTestClient,\n wait,\n freezeTime,\n testAssertions,\n mockResponses,\n isObject,\n isArray,\n} from \"./lib/testing/testFixtures.js\"\n\n//Config\nexport {\n ConfigValidator,\n BaseConfigSchema,\n CommonSchemas,\n createConfig,\n ConfigurationError,\n type BaseConfig\n} from \"./lib/config/ConfigValidator.js\"\n\n//Errors\nexport {\n AppError,\n ValidationError,\n AuthenticationError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n UnprocessableEntityError,\n RateLimitError,\n InternalServerError,\n ServiceUnavailableError,\n DatabaseError,\n ExternalAPIError,\n isAppError,\n isOperationalError,\n toAppError\n} from \"./lib/Errors/AppError.js\"\n\n//Middlewares\nexport {\n ErrorMiddleware,\n} from \"./lib/middlewares/ErrorMiddleware.js\"\nexport { validate, type ValidationConfig } from \"./lib/middlewares/ValidationMiddleware.js\";\n\n//Controllers\nexport { BaseController, type RouteConfig } from \"./lib/controllers/BaseController.js\";\n\n\nexport {RedisClient} from \"./lib/classes/Redis.js\"\nexport {DatabaseClient, type DatabaseConfig} from \"./lib/classes/Database.js\""],"names":["EmailClient","ImagesClient","MailingClient","PushNotificationClient","FirebasePushNotificationClient","EmailTypes","ImagesTypes","MailingTypes","AuthenticationUtil","NotificationsUtil","NotificationClient","Log","SecretManager","initializeSentry","isSentryEnabled","scrubObject","captureRequestBody","extractUserContext","buildRequestContext","buildResponseContext","reportErrorToSentry","reportMessageToSentry","createMockContext","createMockUser","createAuthenticatedContext","mockDatabase","flushPromises","assertions","executeRoute","TEST_UUIDS","TEST_DATES","TEST_DATA","isValidUUID","generateTestEmail","createTestUser","createTestCoach","createTestHeadCoach","createTestFinanceUser","createTestAdmin","createTestClient","wait","freezeTime","testAssertions","mockResponses","isObject","isArray","ConfigValidator","BaseConfigSchema","CommonSchemas","createConfig","ConfigurationError","AppError","ValidationError","AuthenticationError","ForbiddenError","NotFoundError","ConflictError","UnprocessableEntityError","RateLimitError","InternalServerError","ServiceUnavailableError","DatabaseError","ExternalAPIError","isAppError","isOperationalError","toAppError","ErrorMiddleware","validate","BaseController","RedisClient","DatabaseClient"],"mappings":"AAAA,YAAY;AACZ,cAAc,oCAAoC;AAClD,cAAc,qCAAqC;AACnD,cAAc,oCAAoC;AAClD,cAAc,iCAAiC;AAC/C,cAAc,yCAAyC;AAEvD,gDAAgD;AAChD,cAAc,iBAAyB;AAEvC,SAAS;AACT,SAASA,WAAW,QAAQ,+BAA8B;AAC1D,SAASC,YAAY,QAAQ,gCAA+B;AAC5D,SAASC,aAAa,QAAQ,iCAAgC;AAC9D,SAASC,sBAAsB,QAAQ,0CAAyC;AAChF,SAASC,8BAA8B,QAAQ,kDAAiD;AAEhG,kCAAkC;AAClC,OAAO,KAAKC,UAAU,MAAM,qCAAoC;AAChE,OAAO,KAAKC,WAAW,MAAM,sCAAqC;AAClE,OAAO,KAAKC,YAAY,MAAM,uCAAsC;AAGpE,OAAO;AACP,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,iBAAiB,QAAO,mCAAkC;AAClE,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,GAAG,QAAO,wBAAuB;AACzC,SAAQC,aAAa,QAAO,+BAA8B;AAC1D,SACEC,gBAAgB,EAChBC,eAAe,EACfC,WAAW,EACXC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,EACnBC,oBAAoB,EACpBC,mBAAmB,EACnBC,qBAAqB,QAGhB,4BAA2B;AAElC,mBAAmB;AACnB,SACEC,iBAAiB,EACjBC,cAAc,EACdC,0BAA0B,EAC1BC,YAAY,EACZC,aAAa,EACbC,UAAU,EACVC,YAAY,QAQP,+BAA8B;AAErC,mBAAmB;AACnB,SACEC,UAAU,EACVC,UAAU,EACVC,SAAS,EACTC,WAAW,EACXC,iBAAiB,EACjBC,cAAc,EACdC,eAAe,EACfC,mBAAmB,EACnBC,qBAAqB,EACrBC,eAAe,EACfC,gBAAgB,EAChBC,IAAI,EACJC,UAAU,EACVC,cAAc,EACdC,aAAa,EACbC,QAAQ,EACRC,OAAO,QACF,gCAA+B;AAEtC,QAAQ;AACR,SACEC,eAAe,EACfC,gBAAgB,EAChBC,aAAa,EACbC,YAAY,EACZC,kBAAkB,QAEb,kCAAiC;AAExC,QAAQ;AACR,SACEC,QAAQ,EACRC,eAAe,EACfC,mBAAmB,EACnBC,cAAc,EACdC,aAAa,EACbC,aAAa,EACbC,wBAAwB,EACxBC,cAAc,EACdC,mBAAmB,EACnBC,uBAAuB,EACvBC,aAAa,EACbC,gBAAgB,EAChBC,UAAU,EACVC,kBAAkB,EAClBC,UAAU,QACL,2BAA0B;AAEjC,aAAa;AACb,SACEC,eAAe,QACV,uCAAsC;AAC7C,SAASC,QAAQ,QAA+B,4CAA4C;AAE5F,aAAa;AACb,SAASC,cAAc,QAA0B,sCAAsC;AAGvF,SAAQC,WAAW,QAAO,yBAAwB;AAClD,SAAQC,cAAc,QAA4B,4BAA2B"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["//Interfaces\nexport * from \"./lib/models/ProgramInterfaces.js\";\nexport * from \"./lib/models/ExerciseInterfaces.js\";\nexport * from \"./lib/models/WorkoutInterfaces.js\";\nexport * from \"./lib/models/UserInterfaces.js\";\nexport * from \"./lib/models/NotificationInterfaces.js\";\n\n//DB Models - Export all models and model arrays\nexport * from \"./lib/dbmodels/index.js\"\n\n//Clients\nexport { EmailClient } from \"./lib/clients/EmailClient.js\"\nexport { ImagesClient } from \"./lib/clients/ImagesClient.js\"\nexport { MailingClient } from \"./lib/clients/MailingClient.js\"\nexport { PushNotificationClient } from \"./lib/clients/PushNotificationClient.js\"\nexport { FirebasePushNotificationClient } from \"./lib/clients/FirebasePushNotificationClient.js\"\n\n//Client Types (namespace imports)\nexport * as EmailTypes from \"./lib/clients/types/email.types.js\"\nexport * as ImagesTypes from \"./lib/clients/types/images.types.js\"\nexport * as MailingTypes from \"./lib/clients/types/mailing.types.js\"\nexport type { NotificationPayload } from \"./lib/clients/PushNotificationClient.js\"\n\n// Progress Photos Types\nexport type {\n UserProgressPhotoInput,\n UserProgressPhotoRecord,\n UpdateProgressPhotoDateInput,\n} from \"./lib/clients/types/images.types.js\"\n\n//Utils\nexport {AuthenticationUtil} from \"./lib/utils/AuthenticationUtil.js\"\nexport {NotificationsUtil} from \"./lib/utils/NotificationsUtil.js\"\nexport {NotificationClient} from \"./lib/utils/NotificationClient.js\"\nexport {Log} from \"./lib/utils/Logger.js\"\nexport {SecretManager} from \"./lib/utils/SecretManager.js\"\nexport { validateBase64Image, ImageValidationError } from \"./lib/utils/imageValidation.js\"\nexport {\n initializeSentry,\n isSentryEnabled,\n scrubObject,\n captureRequestBody,\n extractUserContext,\n buildRequestContext,\n buildResponseContext,\n reportErrorToSentry,\n reportMessageToSentry,\n type ErrorReportOptions,\n type SeverityLevel,\n} from \"./lib/utils/SentryUtil.js\"\n\n//Testing Utilities\nexport {\n createMockContext,\n createMockUser,\n createAuthenticatedContext,\n mockDatabase,\n flushPromises,\n assertions,\n executeRoute,\n type BaseContext,\n type AuthenticatedContext,\n type RouterContext,\n type RouterMiddleware,\n type RouterLayer,\n type RouterLike,\n type SequelizeModelMethods,\n} from \"./lib/testing/testHelpers.js\"\n\n// Testing Fixtures\nexport {\n TEST_UUIDS,\n TEST_DATES,\n TEST_DATA,\n isValidUUID,\n generateTestEmail,\n createTestUser,\n createTestCoach,\n createTestHeadCoach,\n createTestFinanceUser,\n createTestAdmin,\n createTestClient,\n wait,\n freezeTime,\n testAssertions,\n mockResponses,\n isObject,\n isArray,\n} from \"./lib/testing/testFixtures.js\"\n\n//Config\nexport {\n ConfigValidator,\n BaseConfigSchema,\n CommonSchemas,\n createConfig,\n ConfigurationError,\n type BaseConfig\n} from \"./lib/config/ConfigValidator.js\"\n\n//Errors\nexport {\n AppError,\n ValidationError,\n AuthenticationError,\n ForbiddenError,\n NotFoundError,\n ConflictError,\n UnprocessableEntityError,\n RateLimitError,\n InternalServerError,\n ServiceUnavailableError,\n DatabaseError,\n ExternalAPIError,\n isAppError,\n isOperationalError,\n toAppError\n} from \"./lib/Errors/AppError.js\"\n\n//Middlewares\nexport {\n ErrorMiddleware,\n} from \"./lib/middlewares/ErrorMiddleware.js\"\nexport { validate, type ValidationConfig } from \"./lib/middlewares/ValidationMiddleware.js\";\n\n//Controllers\nexport { BaseController, type RouteConfig } from \"./lib/controllers/BaseController.js\";\n\n\nexport {RedisClient} from \"./lib/classes/Redis.js\"\nexport {DatabaseClient, type DatabaseConfig} from \"./lib/classes/Database.js\""],"names":["EmailClient","ImagesClient","MailingClient","PushNotificationClient","FirebasePushNotificationClient","EmailTypes","ImagesTypes","MailingTypes","AuthenticationUtil","NotificationsUtil","NotificationClient","Log","SecretManager","validateBase64Image","ImageValidationError","initializeSentry","isSentryEnabled","scrubObject","captureRequestBody","extractUserContext","buildRequestContext","buildResponseContext","reportErrorToSentry","reportMessageToSentry","createMockContext","createMockUser","createAuthenticatedContext","mockDatabase","flushPromises","assertions","executeRoute","TEST_UUIDS","TEST_DATES","TEST_DATA","isValidUUID","generateTestEmail","createTestUser","createTestCoach","createTestHeadCoach","createTestFinanceUser","createTestAdmin","createTestClient","wait","freezeTime","testAssertions","mockResponses","isObject","isArray","ConfigValidator","BaseConfigSchema","CommonSchemas","createConfig","ConfigurationError","AppError","ValidationError","AuthenticationError","ForbiddenError","NotFoundError","ConflictError","UnprocessableEntityError","RateLimitError","InternalServerError","ServiceUnavailableError","DatabaseError","ExternalAPIError","isAppError","isOperationalError","toAppError","ErrorMiddleware","validate","BaseController","RedisClient","DatabaseClient"],"mappings":"AAAA,YAAY;AACZ,cAAc,oCAAoC;AAClD,cAAc,qCAAqC;AACnD,cAAc,oCAAoC;AAClD,cAAc,iCAAiC;AAC/C,cAAc,yCAAyC;AAEvD,gDAAgD;AAChD,cAAc,iBAAyB;AAEvC,SAAS;AACT,SAASA,WAAW,QAAQ,+BAA8B;AAC1D,SAASC,YAAY,QAAQ,gCAA+B;AAC5D,SAASC,aAAa,QAAQ,iCAAgC;AAC9D,SAASC,sBAAsB,QAAQ,0CAAyC;AAChF,SAASC,8BAA8B,QAAQ,kDAAiD;AAEhG,kCAAkC;AAClC,OAAO,KAAKC,UAAU,MAAM,qCAAoC;AAChE,OAAO,KAAKC,WAAW,MAAM,sCAAqC;AAClE,OAAO,KAAKC,YAAY,MAAM,uCAAsC;AAUpE,OAAO;AACP,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,iBAAiB,QAAO,mCAAkC;AAClE,SAAQC,kBAAkB,QAAO,oCAAmC;AACpE,SAAQC,GAAG,QAAO,wBAAuB;AACzC,SAAQC,aAAa,QAAO,+BAA8B;AAC1D,SAASC,mBAAmB,EAAEC,oBAAoB,QAAQ,iCAAgC;AAC1F,SACEC,gBAAgB,EAChBC,eAAe,EACfC,WAAW,EACXC,kBAAkB,EAClBC,kBAAkB,EAClBC,mBAAmB,EACnBC,oBAAoB,EACpBC,mBAAmB,EACnBC,qBAAqB,QAGhB,4BAA2B;AAElC,mBAAmB;AACnB,SACEC,iBAAiB,EACjBC,cAAc,EACdC,0BAA0B,EAC1BC,YAAY,EACZC,aAAa,EACbC,UAAU,EACVC,YAAY,QAQP,+BAA8B;AAErC,mBAAmB;AACnB,SACEC,UAAU,EACVC,UAAU,EACVC,SAAS,EACTC,WAAW,EACXC,iBAAiB,EACjBC,cAAc,EACdC,eAAe,EACfC,mBAAmB,EACnBC,qBAAqB,EACrBC,eAAe,EACfC,gBAAgB,EAChBC,IAAI,EACJC,UAAU,EACVC,cAAc,EACdC,aAAa,EACbC,QAAQ,EACRC,OAAO,QACF,gCAA+B;AAEtC,QAAQ;AACR,SACEC,eAAe,EACfC,gBAAgB,EAChBC,aAAa,EACbC,YAAY,EACZC,kBAAkB,QAEb,kCAAiC;AAExC,QAAQ;AACR,SACEC,QAAQ,EACRC,eAAe,EACfC,mBAAmB,EACnBC,cAAc,EACdC,aAAa,EACbC,aAAa,EACbC,wBAAwB,EACxBC,cAAc,EACdC,mBAAmB,EACnBC,uBAAuB,EACvBC,aAAa,EACbC,gBAAgB,EAChBC,UAAU,EACVC,kBAAkB,EAClBC,UAAU,QACL,2BAA0B;AAEjC,aAAa;AACb,SACEC,eAAe,QACV,uCAAsC;AAC7C,SAASC,QAAQ,QAA+B,4CAA4C;AAE5F,aAAa;AACb,SAASC,cAAc,QAA0B,sCAAsC;AAGvF,SAAQC,WAAW,QAAO,yBAAwB;AAClD,SAAQC,cAAc,QAA4B,4BAA2B"}
@@ -2,7 +2,9 @@ import type * as t from "./types/images.types.js";
2
2
  export declare class ImagesClient {
3
3
  private bucket;
4
4
  private logger;
5
+ private app;
5
6
  constructor(config: t.Config);
7
+ private initializeApp;
6
8
  saveImage(base64: string, name: string): Promise<void>;
7
9
  getImage(name: string): Promise<string | undefined>;
8
10
  saveAvatar(userUuid: string, base64: string): Promise<void>;
@@ -11,6 +13,11 @@ export declare class ImagesClient {
11
13
  getProgressPhotos(userUuid: string, programId: string): Promise<t.ProgressPhoto[]>;
12
14
  saveRecipePhotos(input: t.SaveRecipePhotosInput): Promise<void>;
13
15
  getRecipePhotos(recipeUuid: string): Promise<t.RecipePhotos | undefined>;
16
+ saveUserProgressPhoto(input: t.UserProgressPhotoInput): Promise<t.UserProgressPhotoRecord>;
17
+ getUserProgressPhotos(userUuid: string): Promise<t.UserProgressPhotoRecord[]>;
18
+ getProgressPhotosByProgram(programId: string): Promise<t.UserProgressPhotoRecord[]>;
19
+ updateProgressPhotoDate(input: t.UpdateProgressPhotoDateInput): Promise<t.UserProgressPhotoRecord | null>;
20
+ deleteProgressPhoto(uuid: string, userUuid: string): Promise<boolean>;
14
21
  private base64ToBuffer;
15
22
  private handleError;
16
23
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ImagesClient.d.ts","sourceRoot":"","sources":["../../../src/lib/clients/ImagesClient.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,KAAK,CAAC,MAAM,yBAAyB,CAAC;AAalD,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,MAAM,CAA6C;gBAE/C,MAAM,EAAE,CAAC,CAAC,MAAM;IAKf,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;IAiBrF,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;CAOpB"}
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,6 +1,9 @@
1
1
  import admin from "firebase-admin";
2
+ import { v4 as uuidv4 } from "uuid";
2
3
  import { ExternalAPIError } from "../Errors/AppError.js";
3
4
  import { Log } from "../utils/Logger.js";
5
+ import { validateBase64Image } from "../utils/imageValidation.js";
6
+ import { UserProgressPhoto } from "../db/models/UserProgressPhoto.js";
4
7
  const PROGRESS_DAYS = [
5
8
  1,
6
9
  30,
@@ -20,9 +23,55 @@ const imageName = {
20
23
  export class ImagesClient {
21
24
  bucket;
22
25
  logger = Log.getInstance().extend("images-client");
26
+ app;
23
27
  constructor(config){
24
- const app = config.firebaseApp ?? admin.app();
25
- this.bucket = app.storage().bucket(config.storageBucket);
28
+ if (config.firebaseApp) {
29
+ // Use provided Firebase app
30
+ this.app = config.firebaseApp;
31
+ } else {
32
+ // Initialize Firebase if needed (following FirebasePushNotificationClient pattern)
33
+ this.initializeApp(config.storageBucket);
34
+ this.app = admin.app();
35
+ }
36
+ this.bucket = this.app.storage().bucket(config.storageBucket);
37
+ }
38
+ initializeApp(storageBucket) {
39
+ try {
40
+ // Check if app already exists
41
+ if (admin.apps?.length && admin.apps.length > 0) {
42
+ this.logger.info("Using existing Firebase app");
43
+ return;
44
+ }
45
+ // Try to load service account from environment (for local development)
46
+ const serviceAccountPath = process.env.FIREBASE_SERVICE_ACCOUNT_KEY;
47
+ if (serviceAccountPath) {
48
+ // Local development with service account file
49
+ const fs = require("fs");
50
+ const serviceAccount = JSON.parse(fs.readFileSync(serviceAccountPath, "utf-8"));
51
+ admin.initializeApp({
52
+ credential: admin.credential.cert(serviceAccount),
53
+ storageBucket
54
+ });
55
+ this.logger.info("Firebase initialized with service account file", {
56
+ projectId: serviceAccount.project_id,
57
+ storageBucket
58
+ });
59
+ } else {
60
+ // Production: use application default credentials
61
+ admin.initializeApp({
62
+ credential: admin.credential.applicationDefault(),
63
+ storageBucket
64
+ });
65
+ this.logger.info("Firebase initialized with application default credentials", {
66
+ storageBucket
67
+ });
68
+ }
69
+ } catch (error) {
70
+ this.logger.error("Failed to initialize Firebase Admin SDK", {
71
+ error
72
+ });
73
+ throw new ExternalAPIError("Failed to initialize Firebase", `Error: ${String(error)}`);
74
+ }
26
75
  }
27
76
  async saveImage(base64, name) {
28
77
  try {
@@ -101,6 +150,112 @@ export class ImagesClient {
101
150
  coverBase64: results[2] ?? ""
102
151
  };
103
152
  }
153
+ async saveUserProgressPhoto(input) {
154
+ // Validate image
155
+ validateBase64Image(input.base64);
156
+ // Generate UUID for the photo
157
+ const uuid = uuidv4();
158
+ // Prepare Firebase path
159
+ const firebasePath = `progress-pictures/${input.userUuid}/${uuid}.png`;
160
+ // Upload to Firebase Storage
161
+ const base64Data = input.base64.replace(/^data:image\/\w+;base64,/, "");
162
+ const buffer = Buffer.from(base64Data, "base64");
163
+ const file = this.bucket.file(firebasePath);
164
+ await file.save(buffer, {
165
+ metadata: {
166
+ contentType: "image/png"
167
+ }
168
+ });
169
+ // Save metadata to database
170
+ const photoDate = input.photoDate || new Date();
171
+ const record = await UserProgressPhoto.create({
172
+ uuid,
173
+ userUuid: input.userUuid,
174
+ firebasePath,
175
+ photoDate,
176
+ programId: input.programId || null
177
+ });
178
+ return record.toJSON();
179
+ }
180
+ async getUserProgressPhotos(userUuid) {
181
+ const records = await UserProgressPhoto.findAll({
182
+ where: {
183
+ userUuid
184
+ },
185
+ order: [
186
+ [
187
+ "photoDate",
188
+ "DESC"
189
+ ]
190
+ ],
191
+ raw: true
192
+ });
193
+ return records;
194
+ }
195
+ async getProgressPhotosByProgram(programId) {
196
+ const records = await UserProgressPhoto.findAll({
197
+ where: {
198
+ programId
199
+ },
200
+ order: [
201
+ [
202
+ "photoDate",
203
+ "DESC"
204
+ ]
205
+ ],
206
+ raw: true
207
+ });
208
+ return records;
209
+ }
210
+ async updateProgressPhotoDate(input) {
211
+ const [affectedRows] = await UserProgressPhoto.update({
212
+ photoDate: input.photoDate
213
+ }, {
214
+ where: {
215
+ uuid: input.uuid,
216
+ userUuid: input.userUuid
217
+ }
218
+ });
219
+ if (affectedRows === 0) {
220
+ return null; // Photo not found or user doesn't own it
221
+ }
222
+ const updated = await UserProgressPhoto.findOne({
223
+ where: {
224
+ uuid: input.uuid
225
+ },
226
+ raw: true
227
+ });
228
+ return updated;
229
+ }
230
+ async deleteProgressPhoto(uuid, userUuid) {
231
+ // Find the photo to get the firebasePath
232
+ const photo = await UserProgressPhoto.findOne({
233
+ where: {
234
+ uuid,
235
+ userUuid
236
+ },
237
+ raw: true
238
+ });
239
+ if (!photo) {
240
+ return false; // Photo not found or user doesn't own it
241
+ }
242
+ // Delete from Firebase Storage
243
+ const file = this.bucket.file(photo.firebasePath);
244
+ try {
245
+ await file.delete();
246
+ } catch (error) {
247
+ // If file doesn't exist in storage, continue with database deletion
248
+ console.warn(`Failed to delete file from storage: ${photo.firebasePath}`, error);
249
+ }
250
+ // Delete from database
251
+ await UserProgressPhoto.destroy({
252
+ where: {
253
+ uuid,
254
+ userUuid
255
+ }
256
+ });
257
+ return true;
258
+ }
104
259
  base64ToBuffer(base64) {
105
260
  const data = base64.includes(",") ? base64.split(",")[1] : base64;
106
261
  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 { ExternalAPIError } from \"../Errors/AppError.js\";\nimport { Log } from \"../utils/Logger.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\n constructor(config: t.Config) {\n const app = config.firebaseApp ?? admin.app();\n this.bucket = app.storage().bucket(config.storageBucket);\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 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","ExternalAPIError","Log","PROGRESS_DAYS","RECIPE_PHOTO_TYPES","imageName","avatar","userUuid","progress","day","programId","recipe","recipeUuid","type","ImagesClient","bucket","logger","getInstance","extend","config","app","firebaseApp","storage","storageBucket","saveImage","base64","name","info","buffer","base64ToBuffer","file","save","contentType","error","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","data","includes","split","Buffer","from","method","Error","message","String"],"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 { ExternalAPIError } from \"../Errors/AppError.js\";\nimport { Log } from \"../utils/Logger.js\";\nimport { validateBase64Image } from \"../utils/imageValidation.js\";\nimport { UserProgressPhoto } from \"../db/models/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 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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"}
@@ -18,4 +18,24 @@ export interface SaveRecipePhotosInput {
18
18
  coverBase64: string;
19
19
  methodBase64: string;
20
20
  }
21
+ export interface UserProgressPhotoInput {
22
+ userUuid: string;
23
+ base64: string;
24
+ photoDate?: Date;
25
+ programId?: string;
26
+ }
27
+ export interface UserProgressPhotoRecord {
28
+ uuid: string;
29
+ userUuid: string;
30
+ firebasePath: string;
31
+ photoDate: Date;
32
+ programId: string | null;
33
+ createdAt: Date;
34
+ updatedAt: Date;
35
+ }
36
+ export interface UpdateProgressPhotoDateInput {
37
+ uuid: string;
38
+ userUuid: string;
39
+ photoDate: Date;
40
+ }
21
41
  //# sourceMappingURL=images.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"images.types.d.ts","sourceRoot":"","sources":["../../../../src/lib/clients/types/images.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAExC,MAAM,WAAW,MAAM;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"images.types.d.ts","sourceRoot":"","sources":["../../../../src/lib/clients/types/images.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,gBAAgB,CAAC;AAExC,MAAM,WAAW,MAAM;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,IAAI,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;CACjB"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/lib/clients/types/images.types.ts"],"sourcesContent":["import type admin from \"firebase-admin\";\n\nexport interface Config {\n storageBucket: string;\n firebaseApp?: admin.app.App;\n}\n\nexport interface ProgressPhoto {\n day: number;\n base64: string;\n}\n\nexport interface RecipePhotos {\n ingredientBase64: string;\n coverBase64: string;\n methodBase64: string;\n}\n\nexport interface SaveRecipePhotosInput {\n recipeUuid: string;\n ingredientBase64: string;\n coverBase64: string;\n methodBase64: string;\n}\n"],"names":[],"mappings":"AAkBA,WAKC"}
1
+ {"version":3,"sources":["../../../../src/lib/clients/types/images.types.ts"],"sourcesContent":["import type admin from \"firebase-admin\";\n\nexport interface Config {\n storageBucket: string;\n firebaseApp?: admin.app.App;\n}\n\nexport interface ProgressPhoto {\n day: number;\n base64: string;\n}\n\nexport interface RecipePhotos {\n ingredientBase64: string;\n coverBase64: string;\n methodBase64: string;\n}\n\nexport interface SaveRecipePhotosInput {\n recipeUuid: string;\n ingredientBase64: string;\n coverBase64: string;\n methodBase64: string;\n}\n\nexport interface UserProgressPhotoInput {\n userUuid: string;\n base64: string;\n photoDate?: Date; // Optional, defaults to now\n programId?: string; // Optional, for legacy program association\n}\n\nexport interface UserProgressPhotoRecord {\n uuid: string;\n userUuid: string;\n firebasePath: string;\n photoDate: Date;\n programId: string | null;\n createdAt: Date;\n updatedAt: Date;\n}\n\nexport interface UpdateProgressPhotoDateInput {\n uuid: string;\n userUuid: string;\n photoDate: Date;\n}\n"],"names":[],"mappings":"AA0CA,WAIC"}
@@ -0,0 +1,7 @@
1
+ import { QueryInterface } from "sequelize";
2
+ declare const _default: {
3
+ up(queryInterface: QueryInterface): Promise<void>;
4
+ down(queryInterface: QueryInterface): Promise<void>;
5
+ };
6
+ export default _default;
7
+ //# sourceMappingURL=20260330000000-create-user-progress-photos.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"20260330000000-create-user-progress-photos.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/migrations/20260330000000-create-user-progress-photos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAa,MAAM,WAAW,CAAC;;uBAG3B,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;yBAsD5B,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;;AAvD3D,wBA0DE"}
@@ -0,0 +1,60 @@
1
+ import { DataTypes } from "sequelize";
2
+ export default {
3
+ async up (queryInterface) {
4
+ // Ensure uuid-ossp extension exists
5
+ await queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";');
6
+ await queryInterface.createTable("UserProgressPhotos", {
7
+ uuid: {
8
+ type: DataTypes.UUID,
9
+ defaultValue: queryInterface.sequelize.literal("uuid_generate_v4()"),
10
+ allowNull: false,
11
+ primaryKey: true
12
+ },
13
+ userUuid: {
14
+ type: DataTypes.UUID,
15
+ allowNull: false,
16
+ references: {
17
+ model: "PersistedUsers",
18
+ key: "userUuid"
19
+ },
20
+ onUpdate: "CASCADE",
21
+ onDelete: "CASCADE"
22
+ },
23
+ firebasePath: {
24
+ type: DataTypes.STRING,
25
+ allowNull: false
26
+ },
27
+ photoDate: {
28
+ type: DataTypes.DATE,
29
+ allowNull: false
30
+ },
31
+ programId: {
32
+ type: DataTypes.UUID,
33
+ allowNull: true
34
+ },
35
+ createdAt: {
36
+ type: DataTypes.DATE,
37
+ allowNull: false,
38
+ defaultValue: queryInterface.sequelize.literal("CURRENT_TIMESTAMP")
39
+ },
40
+ updatedAt: {
41
+ type: DataTypes.DATE,
42
+ allowNull: false,
43
+ defaultValue: queryInterface.sequelize.literal("CURRENT_TIMESTAMP")
44
+ }
45
+ });
46
+ // Add index on userUuid for efficient querying
47
+ await queryInterface.addIndex("UserProgressPhotos", [
48
+ "userUuid"
49
+ ]);
50
+ // Add index on programId for legacy support
51
+ await queryInterface.addIndex("UserProgressPhotos", [
52
+ "programId"
53
+ ]);
54
+ },
55
+ async down (queryInterface) {
56
+ await queryInterface.dropTable("UserProgressPhotos");
57
+ }
58
+ };
59
+
60
+ //# sourceMappingURL=20260330000000-create-user-progress-photos.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/lib/db/migrations/20260330000000-create-user-progress-photos.ts"],"sourcesContent":["import { QueryInterface, DataTypes } from \"sequelize\";\n\nexport default {\n async up(queryInterface: QueryInterface): Promise<void> {\n // Ensure uuid-ossp extension exists\n await queryInterface.sequelize.query(\n 'CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";'\n );\n\n await queryInterface.createTable(\"UserProgressPhotos\", {\n uuid: {\n type: DataTypes.UUID,\n defaultValue: queryInterface.sequelize.literal(\"uuid_generate_v4()\"),\n allowNull: false,\n primaryKey: true,\n },\n userUuid: {\n type: DataTypes.UUID,\n allowNull: false,\n references: {\n model: \"PersistedUsers\",\n key: \"userUuid\",\n },\n onUpdate: \"CASCADE\",\n onDelete: \"CASCADE\",\n },\n firebasePath: {\n type: DataTypes.STRING,\n allowNull: false,\n },\n photoDate: {\n type: DataTypes.DATE,\n allowNull: false,\n },\n programId: {\n type: DataTypes.UUID,\n allowNull: true,\n },\n createdAt: {\n type: DataTypes.DATE,\n allowNull: false,\n defaultValue: queryInterface.sequelize.literal(\"CURRENT_TIMESTAMP\"),\n },\n updatedAt: {\n type: DataTypes.DATE,\n allowNull: false,\n defaultValue: queryInterface.sequelize.literal(\"CURRENT_TIMESTAMP\"),\n },\n });\n\n // Add index on userUuid for efficient querying\n await queryInterface.addIndex(\"UserProgressPhotos\", [\"userUuid\"]);\n\n // Add index on programId for legacy support\n await queryInterface.addIndex(\"UserProgressPhotos\", [\"programId\"]);\n },\n\n async down(queryInterface: QueryInterface): Promise<void> {\n await queryInterface.dropTable(\"UserProgressPhotos\");\n },\n};\n"],"names":["DataTypes","up","queryInterface","sequelize","query","createTable","uuid","type","UUID","defaultValue","literal","allowNull","primaryKey","userUuid","references","model","key","onUpdate","onDelete","firebasePath","STRING","photoDate","DATE","programId","createdAt","updatedAt","addIndex","down","dropTable"],"mappings":"AAAA,SAAyBA,SAAS,QAAQ,YAAY;AAEtD,eAAe;IACb,MAAMC,IAAGC,cAA8B;QACrC,oCAAoC;QACpC,MAAMA,eAAeC,SAAS,CAACC,KAAK,CAClC;QAGF,MAAMF,eAAeG,WAAW,CAAC,sBAAsB;YACrDC,MAAM;gBACJC,MAAMP,UAAUQ,IAAI;gBACpBC,cAAcP,eAAeC,SAAS,CAACO,OAAO,CAAC;gBAC/CC,WAAW;gBACXC,YAAY;YACd;YACAC,UAAU;gBACRN,MAAMP,UAAUQ,IAAI;gBACpBG,WAAW;gBACXG,YAAY;oBACVC,OAAO;oBACPC,KAAK;gBACP;gBACAC,UAAU;gBACVC,UAAU;YACZ;YACAC,cAAc;gBACZZ,MAAMP,UAAUoB,MAAM;gBACtBT,WAAW;YACb;YACAU,WAAW;gBACTd,MAAMP,UAAUsB,IAAI;gBACpBX,WAAW;YACb;YACAY,WAAW;gBACThB,MAAMP,UAAUQ,IAAI;gBACpBG,WAAW;YACb;YACAa,WAAW;gBACTjB,MAAMP,UAAUsB,IAAI;gBACpBX,WAAW;gBACXF,cAAcP,eAAeC,SAAS,CAACO,OAAO,CAAC;YACjD;YACAe,WAAW;gBACTlB,MAAMP,UAAUsB,IAAI;gBACpBX,WAAW;gBACXF,cAAcP,eAAeC,SAAS,CAACO,OAAO,CAAC;YACjD;QACF;QAEA,+CAA+C;QAC/C,MAAMR,eAAewB,QAAQ,CAAC,sBAAsB;YAAC;SAAW;QAEhE,4CAA4C;QAC5C,MAAMxB,eAAewB,QAAQ,CAAC,sBAAsB;YAAC;SAAY;IACnE;IAEA,MAAMC,MAAKzB,cAA8B;QACvC,MAAMA,eAAe0B,SAAS,CAAC;IACjC;AACF,EAAE"}
@@ -0,0 +1,13 @@
1
+ import { Model } from "sequelize-typescript";
2
+ import { PersistedUser } from "../../dbmodels/user/PersistedUser";
3
+ export declare class UserProgressPhoto extends Model {
4
+ uuid: string;
5
+ userUuid: string;
6
+ user: PersistedUser;
7
+ firebasePath: string;
8
+ photoDate: Date;
9
+ programId: string | null;
10
+ createdAt: Date;
11
+ updatedAt: Date;
12
+ }
13
+ //# sourceMappingURL=UserProgressPhoto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserProgressPhoto.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/models/UserProgressPhoto.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,EAMN,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAElE,qBAIa,iBAAkB,SAAQ,KAAK;IAQlC,IAAI,EAAE,MAAM,CAAC;IAQb,QAAQ,EAAE,MAAM,CAAC;IAGjB,IAAI,EAAE,aAAa,CAAC;IAOpB,YAAY,EAAE,MAAM,CAAC;IAOrB,SAAS,EAAE,IAAI,CAAC;IAOhB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAEzB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,IAAI,CAAC;CACzB"}
@@ -0,0 +1,59 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ 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;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { Table, Column, Model, DataType, ForeignKey, BelongsTo, Default, AllowNull } from "sequelize-typescript";
8
+ import { PersistedUser } from "../../dbmodels/user/PersistedUser";
9
+ export class UserProgressPhoto extends Model {
10
+ }
11
+ _ts_decorate([
12
+ Default(DataType.UUIDV4),
13
+ Column({
14
+ type: DataType.UUID,
15
+ defaultValue: DataType.UUID,
16
+ allowNull: false,
17
+ primaryKey: true
18
+ })
19
+ ], UserProgressPhoto.prototype, "uuid", void 0);
20
+ _ts_decorate([
21
+ ForeignKey(()=>PersistedUser),
22
+ AllowNull(false),
23
+ Column({
24
+ type: DataType.UUID,
25
+ allowNull: false
26
+ })
27
+ ], UserProgressPhoto.prototype, "userUuid", void 0);
28
+ _ts_decorate([
29
+ BelongsTo(()=>PersistedUser)
30
+ ], UserProgressPhoto.prototype, "user", void 0);
31
+ _ts_decorate([
32
+ AllowNull(false),
33
+ Column({
34
+ type: DataType.STRING,
35
+ allowNull: false
36
+ })
37
+ ], UserProgressPhoto.prototype, "firebasePath", void 0);
38
+ _ts_decorate([
39
+ AllowNull(false),
40
+ Column({
41
+ type: DataType.DATE,
42
+ allowNull: false
43
+ })
44
+ ], UserProgressPhoto.prototype, "photoDate", void 0);
45
+ _ts_decorate([
46
+ AllowNull(true),
47
+ Column({
48
+ type: DataType.UUID,
49
+ allowNull: true
50
+ })
51
+ ], UserProgressPhoto.prototype, "programId", void 0);
52
+ UserProgressPhoto = _ts_decorate([
53
+ Table({
54
+ tableName: "UserProgressPhotos",
55
+ timestamps: true
56
+ })
57
+ ], UserProgressPhoto);
58
+
59
+ //# sourceMappingURL=UserProgressPhoto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/lib/db/models/UserProgressPhoto.ts"],"sourcesContent":["import {\n Table,\n Column,\n Model,\n DataType,\n ForeignKey,\n BelongsTo,\n Default,\n AllowNull,\n} from \"sequelize-typescript\";\nimport { PersistedUser } from \"../../dbmodels/user/PersistedUser\";\n\n@Table({\n tableName: \"UserProgressPhotos\",\n timestamps: true,\n})\nexport class UserProgressPhoto extends Model {\n @Default(DataType.UUIDV4)\n @Column({\n type: DataType.UUID,\n defaultValue: DataType.UUID,\n allowNull: false,\n primaryKey: true,\n })\n declare uuid: string;\n\n @ForeignKey(() => PersistedUser)\n @AllowNull(false)\n @Column({\n type: DataType.UUID,\n allowNull: false,\n })\n declare userUuid: string;\n\n @BelongsTo(() => PersistedUser)\n declare user: PersistedUser;\n\n @AllowNull(false)\n @Column({\n type: DataType.STRING,\n allowNull: false,\n })\n declare firebasePath: string;\n\n @AllowNull(false)\n @Column({\n type: DataType.DATE,\n allowNull: false,\n })\n declare photoDate: Date;\n\n @AllowNull(true)\n @Column({\n type: DataType.UUID,\n allowNull: true,\n })\n declare programId: string | null;\n\n declare createdAt: Date;\n declare updatedAt: Date;\n}\n"],"names":["Table","Column","Model","DataType","ForeignKey","BelongsTo","Default","AllowNull","PersistedUser","UserProgressPhoto","UUIDV4","type","UUID","defaultValue","allowNull","primaryKey","STRING","DATE","tableName","timestamps"],"mappings":";;;;;;AAAA,SACEA,KAAK,EACLC,MAAM,EACNC,KAAK,EACLC,QAAQ,EACRC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,SAAS,QACJ,uBAAuB;AAC9B,SAASC,aAAa,QAAQ,oCAAoC;AAMlE,OAAO,MAAMC,0BAA0BP;AA4CvC;;qBA3CoBQ;;QAEhBC,MAAMR,SAASS,IAAI;QACnBC,cAAcV,SAASS,IAAI;QAC3BE,WAAW;QACXC,YAAY;;;;mBAIIP;;;QAGhBG,MAAMR,SAASS,IAAI;QACnBE,WAAW;;;;kBAIIN;;;;;QAKfG,MAAMR,SAASa,MAAM;QACrBF,WAAW;;;;;;QAMXH,MAAMR,SAASc,IAAI;QACnBH,WAAW;;;;;;QAMXH,MAAMR,SAASS,IAAI;QACnBE,WAAW;;;;;QAzCbI,WAAW;QACXC,YAAY"}
@@ -0,0 +1,2 @@
1
+ export { UserProgressPhoto } from "./UserProgressPhoto";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/db/models/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { UserProgressPhoto } from "./UserProgressPhoto";
2
+
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/lib/db/models/index.ts"],"sourcesContent":["export { UserProgressPhoto } from \"./UserProgressPhoto\";\n"],"names":["UserProgressPhoto"],"mappings":"AAAA,SAASA,iBAAiB,QAAQ,sBAAsB"}
@@ -0,0 +1,5 @@
1
+ export declare class ImageValidationError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare function validateBase64Image(base64: string): void;
5
+ //# sourceMappingURL=imageValidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageValidation.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/imageValidation.ts"],"names":[],"mappings":"AAAA,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAoCxD"}
@@ -0,0 +1,31 @@
1
+ export class ImageValidationError extends Error {
2
+ constructor(message){
3
+ super(message);
4
+ this.name = "ImageValidationError";
5
+ }
6
+ }
7
+ export function validateBase64Image(base64) {
8
+ // Remove data URL prefix if present
9
+ const base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
10
+ // Validate base64 format
11
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {
12
+ throw new ImageValidationError("Invalid base64 format");
13
+ }
14
+ // Decode and check size (15MB = 15 * 1024 * 1024 bytes)
15
+ const sizeInBytes = base64Data.length * 3 / 4;
16
+ const maxSizeInBytes = 15 * 1024 * 1024;
17
+ if (sizeInBytes > maxSizeInBytes) {
18
+ throw new ImageValidationError(`Image size exceeds 15MB limit (${(sizeInBytes / (1024 * 1024)).toFixed(2)}MB)`);
19
+ }
20
+ // Decode to buffer for magic number validation
21
+ const buffer = Buffer.from(base64Data, "base64");
22
+ // Check magic numbers for PNG, JPEG, WebP
23
+ const isPNG = buffer[0] === 0x89 && buffer[1] === 0x50;
24
+ const isJPEG = buffer[0] === 0xff && buffer[1] === 0xd8;
25
+ const isWebP = buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46;
26
+ if (!isPNG && !isJPEG && !isWebP) {
27
+ throw new ImageValidationError("Invalid image format. Only PNG, JPEG, and WebP are supported");
28
+ }
29
+ }
30
+
31
+ //# sourceMappingURL=imageValidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/lib/utils/imageValidation.ts"],"sourcesContent":["export class ImageValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ImageValidationError\";\n }\n}\n\nexport function validateBase64Image(base64: string): void {\n // Remove data URL prefix if present\n const base64Data = base64.replace(/^data:image\\/\\w+;base64,/, \"\");\n\n // Validate base64 format\n if (!/^[A-Za-z0-9+/]*={0,2}$/.test(base64Data)) {\n throw new ImageValidationError(\"Invalid base64 format\");\n }\n\n // Decode and check size (15MB = 15 * 1024 * 1024 bytes)\n const sizeInBytes = (base64Data.length * 3) / 4;\n const maxSizeInBytes = 15 * 1024 * 1024;\n\n if (sizeInBytes > maxSizeInBytes) {\n throw new ImageValidationError(\n `Image size exceeds 15MB limit (${(sizeInBytes / (1024 * 1024)).toFixed(2)}MB)`\n );\n }\n\n // Decode to buffer for magic number validation\n const buffer = Buffer.from(base64Data, \"base64\");\n\n // Check magic numbers for PNG, JPEG, WebP\n const isPNG = buffer[0] === 0x89 && buffer[1] === 0x50;\n const isJPEG = buffer[0] === 0xff && buffer[1] === 0xd8;\n const isWebP =\n buffer[0] === 0x52 &&\n buffer[1] === 0x49 &&\n buffer[2] === 0x46 &&\n buffer[3] === 0x46;\n\n if (!isPNG && !isJPEG && !isWebP) {\n throw new ImageValidationError(\n \"Invalid image format. Only PNG, JPEG, and WebP are supported\"\n );\n }\n}\n"],"names":["ImageValidationError","Error","message","name","validateBase64Image","base64","base64Data","replace","test","sizeInBytes","length","maxSizeInBytes","toFixed","buffer","Buffer","from","isPNG","isJPEG","isWebP"],"mappings":"AAAA,OAAO,MAAMA,6BAA6BC;IACxC,YAAYC,OAAe,CAAE;QAC3B,KAAK,CAACA;QACN,IAAI,CAACC,IAAI,GAAG;IACd;AACF;AAEA,OAAO,SAASC,oBAAoBC,MAAc;IAChD,oCAAoC;IACpC,MAAMC,aAAaD,OAAOE,OAAO,CAAC,4BAA4B;IAE9D,yBAAyB;IACzB,IAAI,CAAC,yBAAyBC,IAAI,CAACF,aAAa;QAC9C,MAAM,IAAIN,qBAAqB;IACjC;IAEA,wDAAwD;IACxD,MAAMS,cAAc,AAACH,WAAWI,MAAM,GAAG,IAAK;IAC9C,MAAMC,iBAAiB,KAAK,OAAO;IAEnC,IAAIF,cAAcE,gBAAgB;QAChC,MAAM,IAAIX,qBACR,CAAC,+BAA+B,EAAE,AAACS,CAAAA,cAAe,CAAA,OAAO,IAAG,CAAC,EAAGG,OAAO,CAAC,GAAG,GAAG,CAAC;IAEnF;IAEA,+CAA+C;IAC/C,MAAMC,SAASC,OAAOC,IAAI,CAACT,YAAY;IAEvC,0CAA0C;IAC1C,MAAMU,QAAQH,MAAM,CAAC,EAAE,KAAK,QAAQA,MAAM,CAAC,EAAE,KAAK;IAClD,MAAMI,SAASJ,MAAM,CAAC,EAAE,KAAK,QAAQA,MAAM,CAAC,EAAE,KAAK;IACnD,MAAMK,SACJL,MAAM,CAAC,EAAE,KAAK,QACdA,MAAM,CAAC,EAAE,KAAK,QACdA,MAAM,CAAC,EAAE,KAAK,QACdA,MAAM,CAAC,EAAE,KAAK;IAEhB,IAAI,CAACG,SAAS,CAACC,UAAU,CAACC,QAAQ;QAChC,MAAM,IAAIlB,qBACR;IAEJ;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "90dc-core",
3
- "version": "1.18.1",
3
+ "version": "1.19.1",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",