175 lines
7.2 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.representativesService = void 0;
const client_1 = require("@prisma/client");
const database_1 = require("../../../config/database");
const logger_1 = require("../../../utils/logger");
const error_handler_1 = require("../../../middleware/error-handler");
const represent_api_client_1 = require("./represent-api.client");
const postal_codes_service_1 = require("../postal-codes/postal-codes.service");
function deduplicateReps(reps) {
const seen = new Set();
return reps.filter((rep) => {
const key = `${rep.name}|${rep.elected_office}`;
if (seen.has(key))
return false;
seen.add(key);
return true;
});
}
exports.representativesService = {
async lookupByPostalCode(code, forceRefresh = false) {
// 1. Check cache unless forcing refresh
if (!forceRefresh) {
const cached = await database_1.prisma.representative.findMany({
where: { postalCode: code },
});
if (cached.length > 0) {
const postalInfo = await postal_codes_service_1.postalCodesService.findByPostalCode(code);
return {
source: 'cache',
postalCode: code,
location: {
city: postalInfo?.city ?? null,
province: postalInfo?.province ?? null,
},
representatives: cached,
};
}
}
// 2. Call Represent API
const apiResponse = await represent_api_client_1.representApiClient.getByPostalCode(code);
// Merge centroid + concordance reps and deduplicate
const allReps = [
...(apiResponse.representatives_centroid || []),
...(apiResponse.representatives_concordance || []),
];
const uniqueReps = deduplicateReps(allReps);
// 3. Fire-and-forget cache write
const cacheWrite = async () => {
try {
// Delete old cached reps for this postal code
await database_1.prisma.representative.deleteMany({ where: { postalCode: code } });
// Cache new reps
if (uniqueReps.length > 0) {
await database_1.prisma.representative.createMany({
data: uniqueReps.map((rep) => ({
postalCode: code,
name: rep.name || null,
email: rep.email || null,
districtName: rep.district_name || null,
electedOffice: rep.elected_office || null,
partyName: rep.party_name || null,
representativeSetName: rep.representative_set_name || null,
url: rep.url || null,
photoUrl: rep.photo_url || null,
offices: rep.offices ? rep.offices : client_1.Prisma.JsonNull,
})),
});
}
// Upsert postal code cache
const coords = apiResponse.centroid?.coordinates;
await postal_codes_service_1.postalCodesService.upsert({
postalCode: code,
city: apiResponse.city,
province: apiResponse.province,
centroidLat: coords ? coords[1] : null,
centroidLng: coords ? coords[0] : null,
});
}
catch (err) {
logger_1.logger.error('Failed to cache representatives', { postalCode: code, error: err });
}
};
// Don't await — fire and forget
cacheWrite();
// 4. Build response from API data
return {
source: 'api',
postalCode: code,
location: {
city: apiResponse.city ?? null,
province: apiResponse.province ?? null,
},
representatives: uniqueReps.map((rep) => ({
id: null,
postalCode: code,
name: rep.name || null,
email: rep.email || null,
districtName: rep.district_name || null,
electedOffice: rep.elected_office || null,
partyName: rep.party_name || null,
representativeSetName: rep.representative_set_name || null,
url: rep.url || null,
photoUrl: rep.photo_url || null,
offices: rep.offices ?? null,
cachedAt: new Date().toISOString(),
})),
};
},
async findAll(filters) {
const { page, limit, search, postalCode } = filters;
const skip = (page - 1) * limit;
const where = {};
if (postalCode) {
where.postalCode = postalCode;
}
if (search) {
where.OR = [
{ name: { contains: search, mode: 'insensitive' } },
{ email: { contains: search, mode: 'insensitive' } },
{ districtName: { contains: search, mode: 'insensitive' } },
{ electedOffice: { contains: search, mode: 'insensitive' } },
];
}
const [representatives, total] = await Promise.all([
database_1.prisma.representative.findMany({
where,
skip,
take: limit,
orderBy: { cachedAt: 'desc' },
}),
database_1.prisma.representative.count({ where }),
]);
return {
representatives,
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
};
},
async findById(id) {
const rep = await database_1.prisma.representative.findUnique({ where: { id } });
if (!rep) {
throw new error_handler_1.AppError(404, 'Representative not found', 'REPRESENTATIVE_NOT_FOUND');
}
return rep;
},
async clearByPostalCode(code) {
const result = await database_1.prisma.representative.deleteMany({
where: { postalCode: code },
});
return { deleted: result.count, postalCode: code };
},
async deleteById(id) {
const existing = await database_1.prisma.representative.findUnique({ where: { id } });
if (!existing) {
throw new error_handler_1.AppError(404, 'Representative not found', 'REPRESENTATIVE_NOT_FOUND');
}
await database_1.prisma.representative.delete({ where: { id } });
},
async testApiConnection() {
return represent_api_client_1.representApiClient.testConnection();
},
async getCacheStats() {
const [totalReps, postalCodesWithReps, totalPostalCodes] = await Promise.all([
database_1.prisma.representative.count(),
database_1.prisma.representative.groupBy({ by: ['postalCode'] }).then((g) => g.length),
database_1.prisma.postalCodeCache.count(),
]);
return {
totalRepresentatives: totalReps,
postalCodesWithRepresentatives: postalCodesWithReps,
totalPostalCodes,
};
},
};
//# sourceMappingURL=representatives.service.js.map