90dc-core 1.18.1 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/clients/ImagesClient.d.ts +5 -0
- package/dist/lib/clients/ImagesClient.d.ts.map +1 -1
- package/dist/lib/clients/ImagesClient.js +109 -0
- package/dist/lib/clients/ImagesClient.js.map +1 -1
- package/dist/lib/clients/types/images.types.d.ts +20 -0
- package/dist/lib/clients/types/images.types.d.ts.map +1 -1
- package/dist/lib/clients/types/images.types.js.map +1 -1
- package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.d.ts +7 -0
- package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.d.ts.map +1 -0
- package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.js +60 -0
- package/dist/lib/db/migrations/20260330000000-create-user-progress-photos.js.map +1 -0
- package/dist/lib/db/models/UserProgressPhoto.d.ts +13 -0
- package/dist/lib/db/models/UserProgressPhoto.d.ts.map +1 -0
- package/dist/lib/db/models/UserProgressPhoto.js +59 -0
- package/dist/lib/db/models/UserProgressPhoto.js.map +1 -0
- package/dist/lib/db/models/index.d.ts +2 -0
- package/dist/lib/db/models/index.d.ts.map +1 -0
- package/dist/lib/db/models/index.js +3 -0
- package/dist/lib/db/models/index.js.map +1 -0
- package/dist/lib/utils/imageValidation.d.ts +5 -0
- package/dist/lib/utils/imageValidation.d.ts.map +1 -0
- package/dist/lib/utils/imageValidation.js +31 -0
- package/dist/lib/utils/imageValidation.js.map +1 -0
- 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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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":"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"}
|
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;
|
|
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"}
|
|
@@ -11,6 +11,11 @@ export declare class ImagesClient {
|
|
|
11
11
|
getProgressPhotos(userUuid: string, programId: string): Promise<t.ProgressPhoto[]>;
|
|
12
12
|
saveRecipePhotos(input: t.SaveRecipePhotosInput): Promise<void>;
|
|
13
13
|
getRecipePhotos(recipeUuid: string): Promise<t.RecipePhotos | undefined>;
|
|
14
|
+
saveUserProgressPhoto(input: t.UserProgressPhotoInput): Promise<t.UserProgressPhotoRecord>;
|
|
15
|
+
getUserProgressPhotos(userUuid: string): Promise<t.UserProgressPhotoRecord[]>;
|
|
16
|
+
getProgressPhotosByProgram(programId: string): Promise<t.UserProgressPhotoRecord[]>;
|
|
17
|
+
updateProgressPhotoDate(input: t.UpdateProgressPhotoDateInput): Promise<t.UserProgressPhotoRecord | null>;
|
|
18
|
+
deleteProgressPhoto(uuid: string, userUuid: string): Promise<boolean>;
|
|
14
19
|
private base64ToBuffer;
|
|
15
20
|
private handleError;
|
|
16
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImagesClient.d.ts","sourceRoot":"","sources":["../../../src/lib/clients/ImagesClient.ts"],"names":[],"mappings":"
|
|
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;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;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,
|
|
@@ -101,6 +104,112 @@ export class ImagesClient {
|
|
|
101
104
|
coverBase64: results[2] ?? ""
|
|
102
105
|
};
|
|
103
106
|
}
|
|
107
|
+
async saveUserProgressPhoto(input) {
|
|
108
|
+
// Validate image
|
|
109
|
+
validateBase64Image(input.base64);
|
|
110
|
+
// Generate UUID for the photo
|
|
111
|
+
const uuid = uuidv4();
|
|
112
|
+
// Prepare Firebase path
|
|
113
|
+
const firebasePath = `progress-pictures/${input.userUuid}/${uuid}.png`;
|
|
114
|
+
// Upload to Firebase Storage
|
|
115
|
+
const base64Data = input.base64.replace(/^data:image\/\w+;base64,/, "");
|
|
116
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
117
|
+
const file = this.bucket.file(firebasePath);
|
|
118
|
+
await file.save(buffer, {
|
|
119
|
+
metadata: {
|
|
120
|
+
contentType: "image/png"
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Save metadata to database
|
|
124
|
+
const photoDate = input.photoDate || new Date();
|
|
125
|
+
const record = await UserProgressPhoto.create({
|
|
126
|
+
uuid,
|
|
127
|
+
userUuid: input.userUuid,
|
|
128
|
+
firebasePath,
|
|
129
|
+
photoDate,
|
|
130
|
+
programId: input.programId || null
|
|
131
|
+
});
|
|
132
|
+
return record.toJSON();
|
|
133
|
+
}
|
|
134
|
+
async getUserProgressPhotos(userUuid) {
|
|
135
|
+
const records = await UserProgressPhoto.findAll({
|
|
136
|
+
where: {
|
|
137
|
+
userUuid
|
|
138
|
+
},
|
|
139
|
+
order: [
|
|
140
|
+
[
|
|
141
|
+
"photoDate",
|
|
142
|
+
"DESC"
|
|
143
|
+
]
|
|
144
|
+
],
|
|
145
|
+
raw: true
|
|
146
|
+
});
|
|
147
|
+
return records;
|
|
148
|
+
}
|
|
149
|
+
async getProgressPhotosByProgram(programId) {
|
|
150
|
+
const records = await UserProgressPhoto.findAll({
|
|
151
|
+
where: {
|
|
152
|
+
programId
|
|
153
|
+
},
|
|
154
|
+
order: [
|
|
155
|
+
[
|
|
156
|
+
"photoDate",
|
|
157
|
+
"DESC"
|
|
158
|
+
]
|
|
159
|
+
],
|
|
160
|
+
raw: true
|
|
161
|
+
});
|
|
162
|
+
return records;
|
|
163
|
+
}
|
|
164
|
+
async updateProgressPhotoDate(input) {
|
|
165
|
+
const [affectedRows] = await UserProgressPhoto.update({
|
|
166
|
+
photoDate: input.photoDate
|
|
167
|
+
}, {
|
|
168
|
+
where: {
|
|
169
|
+
uuid: input.uuid,
|
|
170
|
+
userUuid: input.userUuid
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (affectedRows === 0) {
|
|
174
|
+
return null; // Photo not found or user doesn't own it
|
|
175
|
+
}
|
|
176
|
+
const updated = await UserProgressPhoto.findOne({
|
|
177
|
+
where: {
|
|
178
|
+
uuid: input.uuid
|
|
179
|
+
},
|
|
180
|
+
raw: true
|
|
181
|
+
});
|
|
182
|
+
return updated;
|
|
183
|
+
}
|
|
184
|
+
async deleteProgressPhoto(uuid, userUuid) {
|
|
185
|
+
// Find the photo to get the firebasePath
|
|
186
|
+
const photo = await UserProgressPhoto.findOne({
|
|
187
|
+
where: {
|
|
188
|
+
uuid,
|
|
189
|
+
userUuid
|
|
190
|
+
},
|
|
191
|
+
raw: true
|
|
192
|
+
});
|
|
193
|
+
if (!photo) {
|
|
194
|
+
return false; // Photo not found or user doesn't own it
|
|
195
|
+
}
|
|
196
|
+
// Delete from Firebase Storage
|
|
197
|
+
const file = this.bucket.file(photo.firebasePath);
|
|
198
|
+
try {
|
|
199
|
+
await file.delete();
|
|
200
|
+
} catch (error) {
|
|
201
|
+
// If file doesn't exist in storage, continue with database deletion
|
|
202
|
+
console.warn(`Failed to delete file from storage: ${photo.firebasePath}`, error);
|
|
203
|
+
}
|
|
204
|
+
// Delete from database
|
|
205
|
+
await UserProgressPhoto.destroy({
|
|
206
|
+
where: {
|
|
207
|
+
uuid,
|
|
208
|
+
userUuid
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
104
213
|
base64ToBuffer(base64) {
|
|
105
214
|
const data = base64.includes(",") ? base64.split(",")[1] : base64;
|
|
106
215
|
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\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 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","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","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","String"],"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":"
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|