Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Crear endpoint de generacion de identidades #91

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions src/pages/api/admin/participant/identities/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { findMostRecentOfmi, findParticipants } from "@/lib/ofmi";
import { VolunteerParticipationInputSchema } from "@/types/participation.schema";
import { Value } from "@sinclair/typebox/build/cjs/value";
import { NextApiRequest, NextApiResponse } from "next";
import countries from "@/lib/address/iso-3166-countries.json";
import { capitalizeInitials, filterNull, getMexStateCode } from "@/utils";

async function generateIdentitiesHandler(
req: NextApiRequest,
res: NextApiResponse,
): Promise<void> {
const ofmi = await findMostRecentOfmi();
const allParticipants = await findParticipants(ofmi);
const onlyContestants = filterNull(
allParticipants.map((participant) => {
if (
Value.Check(
VolunteerParticipationInputSchema,
participant.userParticipation,
)
) {
return null;
}
return {
ofmiEdition: participant.ofmiEdition,
user: participant.user,
userParticipation: participant.userParticipation,
};
}),
);

const states = new Map<string, number>();

const getMaxContestantsCount = (
contestants: typeof onlyContestants,
): number => {
let maxi = 0;
const states = new Map<string, number>();
// Mexican contestants are divided by their state
// International contestants are divided by their country
// Among all these different groups, find the one with the greatest amount of participants.
// This is done to make sure participant usernames are as short as possible.
for (const contestant of contestants) {
let state = contestant.userParticipation.schoolCountry;
if (state === "Mexico") {
state = contestant.userParticipation.schoolState;
}
const count = (states.get(state) || 0) + 1;
maxi = Math.max(maxi, count);
states.set(state, count);
}
return maxi;
};

const minDigits = Math.log10(getMaxContestantsCount(onlyContestants));

const generateUsername = (state: string): string => {
const rawNumber = (states.get(state) || 0) + 1;
const strNumber = rawNumber.toString();
const number = "0".repeat(minDigits - strNumber.length) + strNumber;
states.set(state, rawNumber);
return `${state}-${number}`;
};

onlyContestants.map((contestant) => {
const { user, userParticipation } = contestant;
const participation = userParticipation;
const country = countries.find((country) => {
return country.name === participation.schoolCountry;
}) || {
name: "International",
"alpha-3": "INT",
"country-code": "0",
};
const baseUser = {
name: capitalizeInitials(`${user.firstName} ${user.lastName}`),
school_name: participation.schoolName,
country_id: country["alpha-3"],
gender: "decline",
};
const { country_id } = baseUser;
if (country_id === "MEX") {
const state = getMexStateCode(participation.schoolState);
return {
...baseUser,
state_id: state,
username: generateUsername(state),
};
}
return {
...baseUser,
state_id: country_id,
username: generateUsername(country_id),
};
});
return res
.status(201)
.json({ message: "Las identidades han sido generadas exitosamente" });
}

export default async function handle(
req: NextApiRequest,
res: NextApiResponse,
): Promise<void> {
if (req.method == "GET") {
await generateIdentitiesHandler(req, res);
} else {
return res.status(405).json({ message: "Method Not Allowed" });
}
}
4 changes: 2 additions & 2 deletions src/types/participation.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const UserInputSchema = Type.Object({
export type ContestantParticipationInput = Static<
typeof ContestantParticipationInputSchema
>;
const ContestantParticipationInputSchema = Type.Object({
export const ContestantParticipationInputSchema = Type.Object({
role: Type.Literal(ParticipationRole.CONTESTANT),
schoolName: Type.String({ minLength: 1 }),
schoolStage: SchoolStageSchema,
Expand All @@ -87,7 +87,7 @@ const ContestantParticipationInputSchema = Type.Object({
schoolState: Type.String({ minLength: 1 }),
});

const VolunteerParticipationInputSchema = Type.Object({
export const VolunteerParticipationInputSchema = Type.Object({
role: Type.Literal(ParticipationRole.VOLUNTEER),
educationalLinkageOptIn: Type.Boolean(),
fundraisingOptIn: Type.Boolean(),
Expand Down
33 changes: 33 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,36 @@ export function jsonToCsv(items: Array<Record<string, string>>): string {
const csv = [headerString, ...csvContent].join("\r\n");
return csv;
}

export const getMexStateCode = (name: string): string => {
const exceptions = new Map([
["Baja California", "BCN"],
["Ciudad de México", "CMX"],
["Chiapas", "CHP"],
["Chihuahua", "CHH"],
["Guerrero", "GRO"],
["México", "MEX"],
["Nuevo León", "NLE"],
["Quintana Roo", "ROO"],
["San Luis Potosi", "SLP"],
]);
let state = exceptions.get(name);
if (!state) {
state = name.substring(0, 3).toUpperCase();
}
return state;
};

export const capitalizeInitials = (words: string): string => {
// Capitalizes the first letter of every word in a sentence, removing extra spaces
const separated = words.split(" ").filter((word) => word.length);
return separated
.map((word) => {
let initial = word[0];
if ("a" <= initial && initial <= "z") {
initial += -32;
}
return initial + word.substring(1).toLowerCase();
})
.join(" ");
};
Loading