Skip to content

Commit

Permalink
feat: able to export entire citizen record log (#1860)
Browse files Browse the repository at this point in the history
  • Loading branch information
casperiv0 authored Oct 25, 2023
1 parent 3e002ef commit 3a8dc41
Show file tree
Hide file tree
Showing 7 changed files with 453 additions and 21 deletions.
2 changes: 1 addition & 1 deletion apps/api/src/controllers/citizen/CitizenController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { recordsInclude } from "controllers/leo/search/SearchController";
import { leoProperties } from "utils/leo/includes";
import { sendDiscordWebhook } from "~/lib/discord/webhooks";

export const citizenInclude = Prisma.validator<Prisma.CitizenSelect>()({
export const citizenInclude = Prisma.validator<Prisma.CitizenInclude>()({
user: { select: userProperties },
flags: true,
suspendedLicenses: true,
Expand Down
113 changes: 111 additions & 2 deletions apps/api/src/controllers/record/records-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
WhitelistStatus,
DiscordWebhookType,
type CombinedLeoUnit,
type Officer,
type User,
type Business,
PaymentStatus,
Expand All @@ -37,7 +36,7 @@ import type * as APITypes from "@snailycad/types/api";
import { officerOrDeputyToUnit } from "lib/leo/officerOrDeputyToUnit";
import { Socket } from "services/socket-service";
import { assignUnitsToWarrant } from "~/lib/leo/records/assign-units-to-warrant";
import type { MiscCadSettings, cad } from "@snailycad/types";
import type { MiscCadSettings, Officer, cad } from "@snailycad/types";
import { userProperties } from "lib/auth/getSessionUser";
import { upsertRecord } from "~/lib/leo/records/upsert-record";
import { IsFeatureEnabled } from "middlewares/is-enabled";
Expand Down Expand Up @@ -274,6 +273,101 @@ export class RecordsController {
}
}

@Post("/pdf/citizen/:id")
@UseBefore(ActiveOfficer)
@Description("Export an entire citizen record to a PDF file.")
@UsePermissions({
permissions: [Permissions.Leo],
})
@Header("Content-Type", "application/pdf")
async exportCitizenCriminalRecordToPDF(
@PathParams("id") citizenId: string,
@Context("cad")
cad: cad & { miscCadSettings: MiscCadSettings; features: Record<Feature, boolean> },
@Context("user") user: User,
@Context("activeOfficer") activeOfficer: (CombinedLeoUnit & { officers: Officer[] }) | Officer,
) {
const isEnabled = isFeatureEnabled({
feature: Feature.CITIZEN_RECORD_APPROVAL,
features: cad.features,
defaultReturn: false,
});

const officer = getUserOfficerFromActiveOfficer({
userId: user.id,
activeOfficer,
allowDispatch: true,
});

const citizen = await prisma.citizen.findUnique({
where: { id: citizenId },
include: citizenInclude,
});

if (!citizen) {
throw new NotFound("citizenNotFound");
}

const records = await prisma.record.findMany({
where: { citizenId: citizen.id },
include: recordsInclude(isEnabled).include,
});

const root = __dirname;
const templatePath = resolve(root, "../../templates/citizen-criminal-record.ejs");

const translator = await getTranslator({
type: "webhooks",
locale: user.locale,
namespace: "Records",
});

function formatOfficer(officer: Officer) {
const unitName = officer ? `${officer.citizen.name} ${officer.citizen.surname}` : "";
const officerCallsign = officer
? generateCallsign(officer, cad.miscCadSettings.callsignTemplate)
: "";

return `${officerCallsign} ${unitName}`;
}

const template = await ejs.renderFile(templatePath, {
formatDate,
formatOfficer,
slateDataToString,
sumOf,
age: calculateAge(citizen.dateOfBirth),
citizen,
officer: officer ? formatOfficer(officer as Officer) : "Dispatch",
dateOfExport: Date.now(),
records,
translator,
});

try {
const args = process.env.IS_USING_ROOT_USER === "true" ? ["--no-sandbox"] : [];
const browser = await puppeteer.launch({ args, headless: "new" });
const page = await browser.newPage();

page.setContent(template, { waitUntil: "domcontentloaded" });

await page.emulateMediaType("screen");

const pdf = await page.pdf({
format: "letter",
printBackground: true,
scale: 0.8,
preferCSSPageSize: true,
});

return pdf;
} catch (err) {
console.log(err);
captureException(err);
return null;
}
}

@UseBefore(ActiveOfficer)
@Put("/warrant/:id")
@Description("Update a warrant by its id")
Expand Down Expand Up @@ -598,3 +692,18 @@ function formatDate(date: string | Date | number, options?: { onlyDate: boolean
const hmsString = options?.onlyDate ? "" : " HH:mm:ss";
return format(dateObj, `yyyy-MM-dd${hmsString}`);
}

function sumOf(violations: Violation[], type: "fine" | "jailTime" | "bail") {
let sum = 0;

for (const violation of violations) {
const counts = violation.counts || 1;
const fine = violation[type];

if (fine) {
sum += fine * counts;
}
}

return Intl.NumberFormat("en-BE").format(sum);
}
Loading

0 comments on commit 3a8dc41

Please sign in to comment.