Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | 36x 11x 11x 1x 10x 10x 3x 3x 7x 1x 6x 1x 5x 4x 4x 1x 4x 4x 4x 9x 9x 2x 7x 1x 6x 1x 5x 2x 3x 3x 1x 2x 4x 4x 2x 2x | // Certifications Service - Certifications and awards management
import type { Dal } from "../../dal";
import type { ProCertification } from "../../db/schema";
import type { AddCertificationInput, UpdateCertificationInput } from "./types";
import { NotFoundError, ValidationError } from "../../lib/errors";
export class CertificationsService {
constructor(private dal: Dal) {}
async addCertification(
proId: string,
data: AddCertificationInput,
): Promise<ProCertification> {
// Verify pro exists
const pro = await this.dal.pros.findById(proId);
if (!pro) {
throw new NotFoundError("Pro not found");
}
// Per-type limit (10 each for certifications / awards / memberships)
const count = await this.dal.companyProfile.countCertificationsByType(
proId,
data.type,
);
if (count >= 10) {
const label =
data.type === "award"
? "awards"
: data.type === "membership"
? "memberships"
: "certifications";
throw new ValidationError(`You can add up to 10 ${label}`);
}
// Validate fields
if (!data.title || data.title.length > 100) {
throw new ValidationError("Title is required (max 100 characters)");
}
if (data.issuer && data.issuer.length > 100) {
throw new ValidationError("Issuer must be 100 characters or less");
}
if (data.year) {
const currentYear = new Date().getFullYear();
if (data.year < 1990 || data.year > currentYear) {
throw new ValidationError("Please enter a valid year");
}
}
const maxOrder =
await this.dal.companyProfile.getMaxCertificationOrder(proId);
const cert = await this.dal.companyProfile.createCertification({
proId,
title: data.title,
issuer: data.issuer,
year: data.year,
type: data.type,
imageUrl: data.imageUrl,
displayOrder: maxOrder + 1,
});
return cert;
}
async updateCertification(
proId: string,
certId: number,
data: UpdateCertificationInput,
): Promise<ProCertification> {
const cert = await this.dal.companyProfile.getCertificationById(certId);
if (!cert || cert.proId !== proId) {
throw new NotFoundError("Certification not found");
}
// Validate fields
if (data.title !== undefined && (!data.title || data.title.length > 100)) {
throw new ValidationError("Title is required (max 100 characters)");
}
if (data.issuer && data.issuer.length > 100) {
throw new ValidationError("Issuer must be 100 characters or less");
}
if (data.year) {
const currentYear = new Date().getFullYear();
/* v8 ignore start -- defensive guard: valid year always used in tests */
if (data.year < 1990 || data.year > currentYear) {
throw new ValidationError("Please enter a valid year");
}
/* v8 ignore stop */
}
const updated = await this.dal.companyProfile.updateCertification(
certId,
data,
);
if (!updated) {
throw new NotFoundError("Certification not found");
}
return updated;
}
async deleteCertification(proId: string, certId: number): Promise<void> {
const cert = await this.dal.companyProfile.getCertificationById(certId);
if (!cert || cert.proId !== proId) {
throw new NotFoundError("Certification not found");
}
await this.dal.companyProfile.deleteCertification(certId);
}
}
|