.
This commit is contained in:
parent
247d13ffcf
commit
133bcec868
5
.gitignore
vendored
5
.gitignore
vendored
@ -19,7 +19,10 @@ debug*.log*
|
||||
error*.log*
|
||||
.*-audit.json
|
||||
|
||||
# uploads
|
||||
server/uploads/dealer-logos/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
*.pem
|
||||
|
||||
|
||||
@ -127,6 +127,8 @@ export const createAxiosDataProvider = (
|
||||
const formData = new FormData();
|
||||
formData.append(key || "file", file);
|
||||
|
||||
console.log(file);
|
||||
|
||||
const response = await httpClient.post<R>(url, formData, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
|
||||
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@ -1 +0,0 @@
|
||||
./uploads/dealer-logos
|
||||
@ -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",
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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<string, any>;
|
||||
|
||||
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();
|
||||
|
||||
@ -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<IProfileProps> implements IProfile {
|
||||
@ -115,11 +116,11 @@ export class Profile extends AggregateRoot<IProfileProps> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<string> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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<IGetProfileResponse_DTO>;
|
||||
}
|
||||
|
||||
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<IGetProfileResponse_DTO> => {
|
||||
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(),
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
|
||||
@ -32,7 +32,7 @@ export class UploadProfileLogoController extends ExpressController {
|
||||
async executeImpl() {
|
||||
const req = this.req as AuthenticatedRequest;
|
||||
const user = <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";
|
||||
|
||||
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user