metis-desk/modules/companies/src/api/application/company-logo.use-cases.ts
David 5515191eff feat: add company logo management functionality
- 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.
2026-10-08 18:31:46 +02:00

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"));
}
}