175 lines
7.2 KiB
JavaScript
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
|