- Implemented CompanyLogoUseCases for retrieving, saving, and removing company logos. - Created CompanyLogoError class for error handling related to logo operations. - Developed CompanyLogoProcessor to optimize and validate logo images. - Added HTTP routes for logo management, including GET and POST endpoints. - Created Sequelize-based repository for logo persistence. - Implemented migration for adding logoVersion to companies and creating company_logos table. - Added frontend components for displaying and managing company logos, including avatar and logo upload fields. - Wrote tests for logo processing and avatar components to ensure functionality and correctness.
59 lines
2.3 KiB
TypeScript
59 lines
2.3 KiB
TypeScript
import { Result } from "@repo/rdx-utils";
|
|
import { z } from "zod/v4";
|
|
|
|
import { COMPANY_LOGO_LIMITS } from "../../common/company-logo";
|
|
import { CompanyLogoError } from "../domain/company-logo-error";
|
|
|
|
export type CompanyLogo = Readonly<{ data: Uint8Array; version: string }>;
|
|
|
|
export interface ICompanyLogoRepository {
|
|
exists(companyId: string): Promise<boolean>;
|
|
get(companyId: string): Promise<CompanyLogo | null>;
|
|
replace(companyId: string, logo: CompanyLogo | null): Promise<boolean>;
|
|
}
|
|
export interface ICompanyLogoProcessor {
|
|
optimize(data: Uint8Array): Promise<Result<CompanyLogo, CompanyLogoError>>;
|
|
}
|
|
export class CompanyLogoUseCases {
|
|
constructor(
|
|
private readonly repository: ICompanyLogoRepository,
|
|
private readonly processor: ICompanyLogoProcessor
|
|
) {}
|
|
async get(companyId: string): Promise<Result<CompanyLogo, CompanyLogoError>> {
|
|
if (!z.uuid().safeParse(companyId).success) {
|
|
return Result.fail(new CompanyLogoError("INVALID_REQUEST"));
|
|
}
|
|
const logo = await this.repository.get(companyId);
|
|
return logo ? Result.ok(logo) : Result.fail(new CompanyLogoError("LOGO_NOT_FOUND"));
|
|
}
|
|
async save(
|
|
companyId: string,
|
|
data: Uint8Array
|
|
): Promise<Result<string | null, CompanyLogoError>> {
|
|
if (!z.uuid().safeParse(companyId).success) {
|
|
return Result.fail(new CompanyLogoError("INVALID_REQUEST"));
|
|
}
|
|
if (data.length > COMPANY_LOGO_LIMITS.inputBytes) {
|
|
return Result.fail(new CompanyLogoError("LOGO_TOO_LARGE"));
|
|
}
|
|
if (!(await this.repository.exists(companyId))) {
|
|
return Result.fail(new CompanyLogoError("COMPANY_NOT_FOUND"));
|
|
}
|
|
const processed = await this.processor.optimize(data);
|
|
if (!processed.isSuccess) {
|
|
return Result.fail(processed.error);
|
|
}
|
|
return (await this.repository.replace(companyId, processed.data))
|
|
? Result.ok(processed.data.version)
|
|
: Result.fail(new CompanyLogoError("COMPANY_NOT_FOUND"));
|
|
}
|
|
async remove(companyId: string): Promise<Result<string | null, CompanyLogoError>> {
|
|
if (!z.uuid().safeParse(companyId).success) {
|
|
return Result.fail(new CompanyLogoError("INVALID_REQUEST"));
|
|
}
|
|
return (await this.repository.replace(companyId, null))
|
|
? Result.ok(null)
|
|
: Result.fail(new CompanyLogoError("COMPANY_NOT_FOUND"));
|
|
}
|
|
}
|