.
This commit is contained in:
parent
247d13ffcf
commit
133bcec868
5
.gitignore
vendored
5
.gitignore
vendored
@ -19,7 +19,10 @@ debug*.log*
|
|||||||
error*.log*
|
error*.log*
|
||||||
.*-audit.json
|
.*-audit.json
|
||||||
|
|
||||||
|
# uploads
|
||||||
|
server/uploads/dealer-logos/
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
|
|
||||||
|
|||||||
@ -127,6 +127,8 @@ export const createAxiosDataProvider = (
|
|||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append(key || "file", file);
|
formData.append(key || "file", file);
|
||||||
|
|
||||||
|
console.log(file);
|
||||||
|
|
||||||
const response = await httpClient.post<R>(url, formData, {
|
const response = await httpClient.post<R>(url, formData, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
|
|||||||
1
server/.gitignore
vendored
1
server/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
./uploads/dealer-logos
|
|
||||||
@ -57,6 +57,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@joi/date": "^2.1.0",
|
"@joi/date": "^2.1.0",
|
||||||
"@reis/joi-luxon": "^3.0.0",
|
"@reis/joi-luxon": "^3.0.0",
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
"cls-rtracer": "^2.6.3",
|
"cls-rtracer": "^2.6.3",
|
||||||
"cors": "^2.8.5",
|
"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
|
new URL(`${this.req.protocol}://${this.req.get("host")}${this.req.originalUrl}`).origin
|
||||||
}/api/v1`;
|
}/api/v1`;
|
||||||
|
|
||||||
this.file = this.req && this.req["file"]; // <-- ????
|
//this.file = this.req && this.req["file"]; // <-- ????
|
||||||
|
|
||||||
this.executeImpl();
|
this.executeImpl();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,10 @@ import { IRepositoryManager } from "@/contexts/common/domain";
|
|||||||
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
import { IInfrastructureError } from "@/contexts/common/infrastructure";
|
||||||
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
import { ISequelizeAdapter } from "@/contexts/common/infrastructure/sequelize";
|
||||||
import { Result, UniqueID } from "@shared/contexts";
|
import { Result, UniqueID } from "@shared/contexts";
|
||||||
|
import fs from "fs/promises";
|
||||||
import { IProfileRepository, Profile } from "../domain";
|
import { IProfileRepository, Profile } from "../domain";
|
||||||
|
import { ProfileLogotype } from "../domain/entities/ProfileLogotype";
|
||||||
|
import { IProfileContext } from "../infrastructure";
|
||||||
|
|
||||||
export interface IUpdateProfileLogoUseCaseRequest extends IUseCaseRequest {
|
export interface IUpdateProfileLogoUseCaseRequest extends IUseCaseRequest {
|
||||||
userId: UniqueID;
|
userId: UniqueID;
|
||||||
@ -24,10 +27,12 @@ export class UploadProfileLogoUseCase
|
|||||||
{
|
{
|
||||||
private _adapter: ISequelizeAdapter;
|
private _adapter: ISequelizeAdapter;
|
||||||
private _repositoryManager: IRepositoryManager;
|
private _repositoryManager: IRepositoryManager;
|
||||||
|
private _defaults: Record<string, any>;
|
||||||
|
|
||||||
constructor(props: { adapter: ISequelizeAdapter; repositoryManager: IRepositoryManager }) {
|
constructor(props: IProfileContext) {
|
||||||
this._adapter = props.adapter;
|
this._adapter = props.adapter;
|
||||||
this._repositoryManager = props.repositoryManager;
|
this._repositoryManager = props.repositoryManager;
|
||||||
|
this._defaults = props.defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(
|
async execute(
|
||||||
@ -48,13 +53,21 @@ export class UploadProfileLogoUseCase
|
|||||||
|
|
||||||
const profile = exitsOrError.object;
|
const profile = exitsOrError.object;
|
||||||
|
|
||||||
|
// Borro del logo anterior
|
||||||
|
this._deleteOldLogo(profile.logo);
|
||||||
|
|
||||||
// Actualizar el perfil con datos actualizados
|
// Actualizar el perfil con datos actualizados
|
||||||
profile.logo = file.filename;
|
profile.logo = ProfileLogotype.create(file.filename).object;
|
||||||
|
|
||||||
// Guardar los cambios
|
// Guardar los cambios
|
||||||
return this._saveProfile(profile);
|
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) {
|
private async _saveProfile(updatedProfile: Profile) {
|
||||||
// Guardar el contacto
|
// Guardar el contacto
|
||||||
const transaction = this._adapter.startTransaction();
|
const transaction = this._adapter.startTransaction();
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
Result,
|
Result,
|
||||||
UniqueID,
|
UniqueID,
|
||||||
} from "@shared/contexts";
|
} from "@shared/contexts";
|
||||||
|
import { ProfileLogotype } from "./ProfileLogotype";
|
||||||
|
|
||||||
export interface IProfileProps {
|
export interface IProfileProps {
|
||||||
name: Name;
|
name: Name;
|
||||||
@ -24,7 +25,7 @@ export interface IProfileProps {
|
|||||||
defaultQuoteValidity: Note;
|
defaultQuoteValidity: Note;
|
||||||
defaultTax: Percentage;
|
defaultTax: Percentage;
|
||||||
|
|
||||||
logo: string;
|
logo: ProfileLogotype;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IProfile {
|
export interface IProfile {
|
||||||
@ -42,7 +43,7 @@ export interface IProfile {
|
|||||||
defaultQuoteValidity: Note;
|
defaultQuoteValidity: Note;
|
||||||
defaultTax: Percentage;
|
defaultTax: Percentage;
|
||||||
|
|
||||||
logo: string;
|
logo: ProfileLogotype;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
||||||
@ -115,11 +116,11 @@ export class Profile extends AggregateRoot<IProfileProps> implements IProfile {
|
|||||||
this.props.defaultTax = newDefaultTax;
|
this.props.defaultTax = newDefaultTax;
|
||||||
}
|
}
|
||||||
|
|
||||||
get logo(): string {
|
get logo(): ProfileLogotype {
|
||||||
return this.props.logo;
|
return this.props.logo;
|
||||||
}
|
}
|
||||||
|
|
||||||
set logo(newLogo: string) {
|
set logo(newLogo: ProfileLogotype) {
|
||||||
this.props.logo = newLogo;
|
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 { Profile } from "@/contexts/profile/domain";
|
||||||
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
import { IProfileContext } from "@/contexts/profile/infrastructure/Profile.context";
|
||||||
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
import { IGetProfileResponse_DTO } from "@shared/contexts";
|
||||||
import fs from "fs/promises";
|
|
||||||
import mime from "mime-types";
|
|
||||||
|
|
||||||
export interface IGetProfilePresenter {
|
export interface IGetProfilePresenter {
|
||||||
map: (
|
map: (
|
||||||
@ -13,22 +11,12 @@ export interface IGetProfilePresenter {
|
|||||||
) => Promise<IGetProfileResponse_DTO>;
|
) => 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 = {
|
export const GetProfilePresenter: IGetProfilePresenter = {
|
||||||
map: async (
|
map: async (
|
||||||
profile: Profile,
|
profile: Profile,
|
||||||
user: AuthUser,
|
user: AuthUser,
|
||||||
context: IProfileContext
|
context: IProfileContext
|
||||||
): Promise<IGetProfileResponse_DTO> => {
|
): Promise<IGetProfileResponse_DTO> => {
|
||||||
const profile_logo = profile.logo
|
|
||||||
? `${context.defaults.dealer_logos_upload_path}/${profile.logo}`
|
|
||||||
: context.defaults.dealer_logo_placeholder;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: user.id.toString(),
|
id: user.id.toString(),
|
||||||
name: user.name.toString(),
|
name: user.name.toString(),
|
||||||
@ -49,7 +37,7 @@ export const GetProfilePresenter: IGetProfilePresenter = {
|
|||||||
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
default_quote_validity: profile.defaultQuoteValidity.toString(),
|
||||||
default_tax: profile.defaultTax.convertScale(2).toObject(),
|
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 = {
|
export const GetProfileLogoPresenter: IGetProfileLogoPresenter = {
|
||||||
map: (profile: Profile, context: IProfileContext): string =>
|
map: (profile: Profile, context: IProfileContext): string => profile.logo.relativePath(),
|
||||||
profile.logo
|
|
||||||
? `${context.defaults.dealer_logos_upload_path}/${profile.logo}`
|
|
||||||
: context.defaults.dealer_logo_placeholder,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -32,7 +32,7 @@ export class UploadProfileLogoController extends ExpressController {
|
|||||||
async executeImpl() {
|
async executeImpl() {
|
||||||
const req = this.req as AuthenticatedRequest;
|
const req = this.req as AuthenticatedRequest;
|
||||||
const user = <User>req.user;
|
const user = <User>req.user;
|
||||||
const file: Express.Multer.File = this.file;
|
const file = this.req["file"];
|
||||||
|
|
||||||
if (!user || !file || !file.filename) {
|
if (!user || !file || !file.filename) {
|
||||||
const errorMessage = "Unexpected missing input data";
|
const errorMessage = "Unexpected missing input data";
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
UniqueID,
|
UniqueID,
|
||||||
} from "@shared/contexts";
|
} from "@shared/contexts";
|
||||||
import { IProfileProps, Profile } from "../../domain";
|
import { IProfileProps, Profile } from "../../domain";
|
||||||
|
import { ProfileLogotype } from "../../domain/entities/ProfileLogotype";
|
||||||
import { IProfileContext } from "../Profile.context";
|
import { IProfileContext } from "../Profile.context";
|
||||||
|
|
||||||
export interface IProfileMapper
|
export interface IProfileMapper
|
||||||
@ -32,7 +33,7 @@ class ProfileMapper
|
|||||||
const status = this.mapsValue(source, "status", DealerStatus.create);
|
const status = this.mapsValue(source, "status", DealerStatus.create);
|
||||||
const language = this.mapsValue(source, "lang_code", Language.createFromCode);
|
const language = this.mapsValue(source, "lang_code", Language.createFromCode);
|
||||||
const currency = this.mapsValue(source, "currency_code", CurrencyData.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(
|
const contactInformation = this.mapsValue(
|
||||||
source,
|
source,
|
||||||
@ -95,7 +96,7 @@ class ProfileMapper
|
|||||||
default_legal_terms: source.defaultLegalTerms.toPrimitive(),
|
default_legal_terms: source.defaultLegalTerms.toPrimitive(),
|
||||||
default_quote_validity: source.defaultQuoteValidity.toPrimitive(),
|
default_quote_validity: source.defaultQuoteValidity.toPrimitive(),
|
||||||
default_tax: source.defaultTax.convertScale(2).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"
|
resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547"
|
||||||
integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==
|
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":
|
"@types/mime@^1":
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
|
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user