From 133bcec868a29d89abffc1b94439a29e6be765e3 Mon Sep 17 00:00:00 2001 From: David Arranz Date: Mon, 23 Sep 2024 18:37:08 +0200 Subject: [PATCH] . --- .gitignore | 5 +- .../src/lib/axios/createAxiosDataProvider.ts | 2 + server/.gitignore | 1 - server/package.json | 1 + .../express/ExpressController.ts | 2 +- .../application/UploadProfileLogo.useCase.ts | 17 +++++- .../profile/domain/entities/Profile.ts | 9 ++-- .../domain/entities/ProfileLogotype.ts | 53 +++++++++++++++++++ .../presenter/GetProfile.presenter.ts | 14 +---- .../presenter/GetProfileLogo.presenter.ts | 5 +- .../UploadProfileLogo.controller.ts | 2 +- .../infrastructure/mappers/profile.mapper.ts | 5 +- server/yarn.lock | 5 ++ 13 files changed, 92 insertions(+), 29 deletions(-) delete mode 100644 server/.gitignore create mode 100644 server/src/contexts/profile/domain/entities/ProfileLogotype.ts diff --git a/.gitignore b/.gitignore index 2e8774d..ff263d0 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,10 @@ debug*.log* error*.log* .*-audit.json +# uploads +server/uploads/dealer-logos/ # misc .DS_Store -*.pem \ No newline at end of file +*.pem + diff --git a/client/src/lib/axios/createAxiosDataProvider.ts b/client/src/lib/axios/createAxiosDataProvider.ts index d1557ef..efc5eb4 100644 --- a/client/src/lib/axios/createAxiosDataProvider.ts +++ b/client/src/lib/axios/createAxiosDataProvider.ts @@ -127,6 +127,8 @@ export const createAxiosDataProvider = ( const formData = new FormData(); formData.append(key || "file", file); + console.log(file); + const response = await httpClient.post(url, formData, { headers: { "Content-Type": "multipart/form-data", diff --git a/server/.gitignore b/server/.gitignore deleted file mode 100644 index e9f2b8b..0000000 --- a/server/.gitignore +++ /dev/null @@ -1 +0,0 @@ -./uploads/dealer-logos \ No newline at end of file diff --git a/server/package.json b/server/package.json index b0e464c..11941c7 100644 --- a/server/package.json +++ b/server/package.json @@ -57,6 +57,7 @@ "dependencies": { "@joi/date": "^2.1.0", "@reis/joi-luxon": "^3.0.0", + "@types/mime-types": "^2.1.4", "bcrypt": "^5.1.1", "cls-rtracer": "^2.6.3", "cors": "^2.8.5", diff --git a/server/src/contexts/common/infrastructure/express/ExpressController.ts b/server/src/contexts/common/infrastructure/express/ExpressController.ts index efdee52..6d1e162 100644 --- a/server/src/contexts/common/infrastructure/express/ExpressController.ts +++ b/server/src/contexts/common/infrastructure/express/ExpressController.ts @@ -26,7 +26,7 @@ export abstract class ExpressController implements IController { new URL(`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`).origin }/api/v1`; - this.file = this.req && this.req["file"]; // <-- ???? + //this.file = this.req && this.req["file"]; // <-- ???? this.executeImpl(); } diff --git a/server/src/contexts/profile/application/UploadProfileLogo.useCase.ts b/server/src/contexts/profile/application/UploadProfileLogo.useCase.ts index 821122f..db2c3ce 100644 --- a/server/src/contexts/profile/application/UploadProfileLogo.useCase.ts +++ b/server/src/contexts/profile/application/UploadProfileLogo.useCase.ts @@ -8,7 +8,10 @@ import { IRepositoryManager } from "@/contexts/common/domain"; import { IInfrastructureError } from "@/contexts/common/infrastructure"; import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize"; import { Result, UniqueID } from "@shared/contexts"; +import fs from "fs/promises"; import { IProfileRepository, Profile } from "../domain"; +import { ProfileLogotype } from "../domain/entities/ProfileLogotype"; +import { IProfileContext } from "../infrastructure"; export interface IUpdateProfileLogoUseCaseRequest extends IUseCaseRequest { userId: UniqueID; @@ -24,10 +27,12 @@ export class UploadProfileLogoUseCase { private _adapter: ISequelizeAdapter; private _repositoryManager: IRepositoryManager; + private _defaults: Record; - constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) { + constructor(props: IProfileContext) { this._adapter = props.adapter; this._repositoryManager = props.repositoryManager; + this._defaults = props.defaults; } async execute( @@ -48,13 +53,21 @@ export class UploadProfileLogoUseCase const profile = exitsOrError.object; + // Borro del logo anterior + this._deleteOldLogo(profile.logo); + // Actualizar el perfil con datos actualizados - profile.logo = file.filename; + profile.logo = ProfileLogotype.create(file.filename).object; // Guardar los cambios return this._saveProfile(profile); } + private async _deleteOldLogo(filename) { + const logo_path = `${this._defaults.dealer_logos_upload_path}/${filename}`; + await fs.rm(logo_path); + } + private async _saveProfile(updatedProfile: Profile) { // Guardar el contacto const transaction = this._adapter.startTransaction(); diff --git a/server/src/contexts/profile/domain/entities/Profile.ts b/server/src/contexts/profile/domain/entities/Profile.ts index 1a765d3..b6f8197 100644 --- a/server/src/contexts/profile/domain/entities/Profile.ts +++ b/server/src/contexts/profile/domain/entities/Profile.ts @@ -10,6 +10,7 @@ import { Result, UniqueID, } from "@shared/contexts"; +import { ProfileLogotype } from "./ProfileLogotype"; export interface IProfileProps { name: Name; @@ -24,7 +25,7 @@ export interface IProfileProps { defaultQuoteValidity: Note; defaultTax: Percentage; - logo: string; + logo: ProfileLogotype; } export interface IProfile { @@ -42,7 +43,7 @@ export interface IProfile { defaultQuoteValidity: Note; defaultTax: Percentage; - logo: string; + logo: ProfileLogotype; } export class Profile extends AggregateRoot implements IProfile { @@ -115,11 +116,11 @@ export class Profile extends AggregateRoot implements IProfile { this.props.defaultTax = newDefaultTax; } - get logo(): string { + get logo(): ProfileLogotype { return this.props.logo; } - set logo(newLogo: string) { + set logo(newLogo: ProfileLogotype) { this.props.logo = newLogo; } } diff --git a/server/src/contexts/profile/domain/entities/ProfileLogotype.ts b/server/src/contexts/profile/domain/entities/ProfileLogotype.ts new file mode 100644 index 0000000..a48af62 --- /dev/null +++ b/server/src/contexts/profile/domain/entities/ProfileLogotype.ts @@ -0,0 +1,53 @@ +import { config } from "@/config"; +import { IValueObjectOptions, Result, ValueObject } from "@shared/contexts"; +import fs from "fs/promises"; +import mime from "mime-types"; +import path from "path"; + +export interface IProfileLogotypeOptions extends IValueObjectOptions {} + +export class ProfileLogotype extends ValueObject { + public static create(value: string, options: IProfileLogotypeOptions = {}) { + const _options = { + label: "logotype", + ...options, + }; + //const validationResult = ProfileStatus.validate(status, _options); + + /*if (validationResult.isFailure) { + return Result.fail( + handleDomainError(DomainError.INVALID_INPUT_DATA, validationResult.error.message, _options) + ); + }*/ + return Result.ok(new ProfileLogotype(value)); + } + + get filename(): string { + return this.props; + } + + public async toBase64() { + let data: any; + try { + data = await fs.readFile(this.relativePath()); + } catch (err) { + data = await fs.readFile(config.defaults.dealer_logo_placeholder); + } + const mimeType = mime.lookup(data); + return `data:${mimeType};base64,${data.toString("base64")}`; + } + + public relativePath() { + // `${context.defaults.dealer_logos_upload_path}/${profile.logo}` + return this.filename + ? path.format({ + base: config.defaults.dealer_logos_upload_path, + name: this.filename, + }) + : config.defaults.dealer_logo_placeholder; + } + + public toPrimitive(): string { + return this.filename; + } +} diff --git a/server/src/contexts/profile/infrastructure/express/controllers/getProfile/presenter/GetProfile.presenter.ts b/server/src/contexts/profile/infrastructure/express/controllers/getProfile/presenter/GetProfile.presenter.ts index 63b1915..b533a68 100644 --- a/server/src/contexts/profile/infrastructure/express/controllers/getProfile/presenter/GetProfile.presenter.ts +++ b/server/src/contexts/profile/infrastructure/express/controllers/getProfile/presenter/GetProfile.presenter.ts @@ -2,8 +2,6 @@ import { AuthUser } from "@/contexts/auth/domain"; import { Profile } from "@/contexts/profile/domain"; import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context"; import { IGetProfileResponse_DTO } from "@shared/contexts"; -import fs from "fs/promises"; -import mime from "mime-types"; export interface IGetProfilePresenter { map: ( @@ -13,22 +11,12 @@ export interface IGetProfilePresenter { ) => Promise; } -const logoToBase64Image = async (imagePath: string) => { - const data = await fs.readFile(imagePath); - const mimeType = mime.lookup(imagePath); - return `data:${mimeType};base64,${data.toString("base64")}`; -}; - export const GetProfilePresenter: IGetProfilePresenter = { map: async ( profile: Profile, user: AuthUser, context: IProfileContext ): Promise => { - const profile_logo = profile.logo - ? `${context.defaults.dealer_logos_upload_path}/${profile.logo}` - : context.defaults.dealer_logo_placeholder; - return { id: user.id.toString(), name: user.name.toString(), @@ -49,7 +37,7 @@ export const GetProfilePresenter: IGetProfilePresenter = { default_quote_validity: profile.defaultQuoteValidity.toString(), default_tax: profile.defaultTax.convertScale(2).toObject(), - logo: await logoToBase64Image(profile_logo), + logo: await profile.logo.toBase64(), }, }; }, diff --git a/server/src/contexts/profile/infrastructure/express/controllers/getProfileLogo/presenter/GetProfileLogo.presenter.ts b/server/src/contexts/profile/infrastructure/express/controllers/getProfileLogo/presenter/GetProfileLogo.presenter.ts index 6597e9f..d17e6b3 100644 --- a/server/src/contexts/profile/infrastructure/express/controllers/getProfileLogo/presenter/GetProfileLogo.presenter.ts +++ b/server/src/contexts/profile/infrastructure/express/controllers/getProfileLogo/presenter/GetProfileLogo.presenter.ts @@ -6,8 +6,5 @@ export interface IGetProfileLogoPresenter { } export const GetProfileLogoPresenter: IGetProfileLogoPresenter = { - map: (profile: Profile, context: IProfileContext): string => - profile.logo - ? `${context.defaults.dealer_logos_upload_path}/${profile.logo}` - : context.defaults.dealer_logo_placeholder, + map: (profile: Profile, context: IProfileContext): string => profile.logo.relativePath(), }; diff --git a/server/src/contexts/profile/infrastructure/express/controllers/uploadProfileLogo/UploadProfileLogo.controller.ts b/server/src/contexts/profile/infrastructure/express/controllers/uploadProfileLogo/UploadProfileLogo.controller.ts index d8b2bff..9ea29a7 100644 --- a/server/src/contexts/profile/infrastructure/express/controllers/uploadProfileLogo/UploadProfileLogo.controller.ts +++ b/server/src/contexts/profile/infrastructure/express/controllers/uploadProfileLogo/UploadProfileLogo.controller.ts @@ -32,7 +32,7 @@ export class UploadProfileLogoController extends ExpressController { async executeImpl() { const req = this.req as AuthenticatedRequest; const user = req.user; - const file: Express.Multer.File = this.file; + const file = this.req["file"]; if (!user || !file || !file.filename) { const errorMessage = "Unexpected missing input data"; diff --git a/server/src/contexts/profile/infrastructure/mappers/profile.mapper.ts b/server/src/contexts/profile/infrastructure/mappers/profile.mapper.ts index daeb83d..e0e456c 100644 --- a/server/src/contexts/profile/infrastructure/mappers/profile.mapper.ts +++ b/server/src/contexts/profile/infrastructure/mappers/profile.mapper.ts @@ -14,6 +14,7 @@ import { UniqueID, } from "@shared/contexts"; import { IProfileProps, Profile } from "../../domain"; +import { ProfileLogotype } from "../../domain/entities/ProfileLogotype"; import { IProfileContext } from "../Profile.context"; export interface IProfileMapper @@ -32,7 +33,7 @@ class ProfileMapper const status = this.mapsValue(source, "status", DealerStatus.create); const language = this.mapsValue(source, "lang_code", Language.createFromCode); const currency = this.mapsValue(source, "currency_code", CurrencyData.createFromCode); - const logo = source.logo; + const logo = this.mapsValue(source, "logo", ProfileLogotype.create); const contactInformation = this.mapsValue( source, @@ -95,7 +96,7 @@ class ProfileMapper default_legal_terms: source.defaultLegalTerms.toPrimitive(), default_quote_validity: source.defaultQuoteValidity.toPrimitive(), default_tax: source.defaultTax.convertScale(2).toPrimitive(), - logo: source.logo, + logo: source.logo.toPrimitive(), }; } } diff --git a/server/yarn.lock b/server/yarn.lock index 9024b2d..3b0ce6d 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -978,6 +978,11 @@ resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== +"@types/mime-types@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.4.tgz#93a1933e24fed4fb9e4adc5963a63efcbb3317a2" + integrity sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w== + "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"