Skip to content

Commit

Permalink
create routing and ranking business logic
Browse files Browse the repository at this point in the history
  • Loading branch information
estevamngcash committed Jun 15, 2024
1 parent 5230e49 commit 0946ba7
Show file tree
Hide file tree
Showing 15 changed files with 535 additions and 495 deletions.
1 change: 1 addition & 0 deletions pages/api/ranking/contants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const RANKING_INITIAL_DATA = new Date("2024-06-10");
82 changes: 82 additions & 0 deletions pages/api/ranking/degrees/[degree]/[class]/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { NextApiRequest, NextApiResponse } from "next";
import runRequestWithDIContainer from "../../../../../../middlewares/diContainerMiddleware";
import { PrismaClient } from "@prisma/client";
import { DIContainerNextApiRequest } from "../../../../../../dependency_injection/DIContainerNextApiRequest";
import { RANKING_INITIAL_DATA } from "../../../contants";
import { GetClassData } from "../../../types";

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "GET") {
await runRequestWithDIContainer(req, res, run);
} else {
res.statusCode = 405;
res.send("");
}
};

async function run(req: DIContainerNextApiRequest, res: NextApiResponse) {
const prismaClient: PrismaClient = req.scope.resolve("dbClient");
try {
const rankingData = await getData(
prismaClient,
req.query.degree as string,
req.query.class as string
);
res.statusCode = 200;
res.json(rankingData);
} catch (error) {
console.log(error);
res.statusCode = 500;
return;
}
}

async function getData(
prisma: PrismaClient,
degree: string,
year: string
): Promise<GetClassData> {
const minYear = Math.floor(Number(year) / 5) * 5;
const maxYear = minYear + 5;

const result: {
period: number;
degree: string;
amount: number;
donors: number;
first_name: string;

Check failure on line 47 in pages/api/ranking/degrees/[degree]/[class]/index.ts

View workflow job for this annotation

GitHub Actions / run-all-checks

Identifier 'first_name' is not in camel case
last_name: string;

Check failure on line 48 in pages/api/ranking/degrees/[degree]/[class]/index.ts

View workflow job for this annotation

GitHub Actions / run-all-checks

Identifier 'last_name' is not in camel case
url: string;
}[] = await prisma.$queryRaw`
SELECT
u."id",
u."first_name",
u."last_name",
u."url",
SUM(c."amount_in_cents")/100 AS "amount"
FROM "users" u
LEFT JOIN "contributions" c ON u."id" = c."userId"
WHERE
u."degree" = ${degree}
AND u."admission_year" >= ${minYear}
AND u."admission_year" < ${maxYear}
AND c."state" = 'completed'
AND c."createdAt" > ${RANKING_INITIAL_DATA}
GROUP BY u."id"
`;

const amount = result.reduce((acc, row) => acc + row.amount, 0);
const numberOfDonors = result.length;
const donors: GetClassData["donors"] = result.map((row, index) => {

Check failure on line 70 in pages/api/ranking/degrees/[degree]/[class]/index.ts

View workflow job for this annotation

GitHub Actions / run-all-checks

'index' is defined but never used
return {
name: `${row.first_name} ${row.last_name}`,
url: row.url,
};
});

return {
amount,
numberOfDonors,
donors,
};
}
77 changes: 77 additions & 0 deletions pages/api/ranking/degrees/[degree]/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { NextApiRequest, NextApiResponse } from "next";
import runRequestWithDIContainer from "../../../../../middlewares/diContainerMiddleware";
import { PrismaClient } from "@prisma/client";
import { DIContainerNextApiRequest } from "../../../../../dependency_injection/DIContainerNextApiRequest";
import { RANKING_INITIAL_DATA } from "./../../contants";
import { GetRankingData } from "./../../types";

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "GET") {
await runRequestWithDIContainer(req, res, run);
} else {
res.statusCode = 405;
res.send("");
}
};

async function run(req: DIContainerNextApiRequest, res: NextApiResponse) {
const prismaClient: PrismaClient = req.scope.resolve("dbClient");
try {
console.log(req.query);
const rankingData = await getRankingData(
prismaClient,
req.query.degree as string | undefined
);
res.statusCode = 200;
res.json(rankingData);
} catch (error) {
console.log(error);
res.statusCode = 500;
return;
}
}

async function getRankingData(
prisma: PrismaClient,
degree: string | undefined
): Promise<GetRankingData> {
const result: {
period: number;
degree: string;
amount: number;
donors: number;
}[] = await prisma.$queryRaw`
SELECT
FLOOR((u."admission_year") / 5) * 5 AS "period",
u."degree",
SUM(c."amount_in_cents")/100 AS "amount",
COUNT(DISTINCT c."userId") AS "donors"
FROM "users" u
JOIN "contributions" c ON u."id" = c."userId"
WHERE
c."state" = 'completed'
AND c."createdAt" > ${RANKING_INITIAL_DATA}
AND u."degree" = ${degree}
GROUP BY "period", u."degree"
ORDER BY "amount" DESC;
`;

const amount = result.reduce((acc, row) => acc + row.amount, 0);
const numberOfDonors = result.reduce((acc, row) => acc + row.donors, 0);
const ranking: GetRankingData["ranking"] = result.map((row, index) => {
return {
position: index + 1,
degree: row.degree,
initialYear: row.period,
finalYear: row.period + 4,
amount: row.amount,
numberOfDonors: row.donors,
};
});

return {
amount,
numberOfDonors,
ranking,
};
}
76 changes: 76 additions & 0 deletions pages/api/ranking/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { NextApiRequest, NextApiResponse } from "next";
import runRequestWithDIContainer from "../../../middlewares/diContainerMiddleware";
import { PrismaClient } from "@prisma/client";
import { DIContainerNextApiRequest } from "../../../dependency_injection/DIContainerNextApiRequest";
import { RANKING_INITIAL_DATA } from "./contants";
import { GetRankingData } from "./types";

export default async (req: NextApiRequest, res: NextApiResponse) => {
if (req.method === "GET") {
await runRequestWithDIContainer(req, res, run);
} else {
res.statusCode = 405;
res.send("");
}
};

async function run(req: DIContainerNextApiRequest, res: NextApiResponse) {
const prismaClient: PrismaClient = req.scope.resolve("dbClient");
try {
console.log(req.query);
const rankingData = await getRankingData(prismaClient);
res.statusCode = 200;
res.json(rankingData);
} catch (error) {
console.log(error);
res.statusCode = 500;
return;
}
}

const MOCK: GetRankingData = {

Check failure on line 31 in pages/api/ranking/index.ts

View workflow job for this annotation

GitHub Actions / run-all-checks

'MOCK' is assigned a value but never used
amount: 100000,
numberOfDonors: 100,
ranking: [],
};

async function getRankingData(prisma: PrismaClient): Promise<GetRankingData> {
const result: {
period: number;
degree: string;
amount: number;
donors: number;
}[] = await prisma.$queryRaw`
SELECT
FLOOR((u."admission_year") / 5) * 5 AS "period",
u."degree",
SUM(c."amount_in_cents")/100 AS "amount",
COUNT(DISTINCT c."userId") AS "donors"
FROM "users" u
JOIN "contributions" c ON u."id" = c."userId"
WHERE
c."state" = 'completed'
AND c."createdAt" > ${RANKING_INITIAL_DATA}
GROUP BY "period", u."degree"
ORDER BY "amount" DESC;
`;

const amount = result.reduce((acc, row) => acc + row.amount, 0);
const numberOfDonors = result.reduce((acc, row) => acc + row.donors, 0);
const ranking: GetRankingData["ranking"] = result.map((row, index) => {
return {
position: index + 1,
degree: row.degree,
initialYear: row.period,
finalYear: row.period + 4,
amount: row.amount,
numberOfDonors: row.donors,
};
});

return {
amount,
numberOfDonors,
ranking,
};
}
21 changes: 21 additions & 0 deletions pages/api/ranking/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export type GetRankingData = {
amount: number;
numberOfDonors: number;
ranking: {
position: number;
degree: string;
initialYear: number;
finalYear: number;
amount: number;
numberOfDonors: number;
}[];
};

export type GetClassData = {
amount: number;
numberOfDonors: number;
donors: {
name: string;
url?: string;
}[];
};
2 changes: 2 additions & 0 deletions pages/api/registration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const CreateUserSchema = schema({
degree: string,
admissionYear: string,
dob: string,
url: string.optional(),
tutorshipInterest: Boolean,
mentorshipInterest: Boolean,
volunteeringInterest: Boolean,
Expand Down Expand Up @@ -69,6 +70,7 @@ async function runCreateUser(
tutorshipInterest: args.tutorshipInterest,
mentorshipInterest: args.mentorshipInterest,
volunteeringInterest: args.volunteeringInterest,
url: args.url,
});
if (!user) {
throw new Error(messages.USER_REGISTRATION_FAILED);
Expand Down
86 changes: 86 additions & 0 deletions pages/ranking/degrees/[degree]/[year]/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import styles from "./index.module.css";
import Header from "../../../../../components/ranking/Header/Header";
import DonationCallout from "../../../../../components/ranking/Donate/DonateBanner";
import ResultsDisplay from "../../../../../components/ranking/ResultsDisplay/ResultsDisplay";
import ShareCallout from "../../../../../components/ranking/Share/ShareCallout";
import BackButton from "../../../../../components/ranking/BackButton/BackButton";
import { useRouter } from "next/router";
import { GetClassData } from "../../../../api/ranking/types";
import { useState, useEffect } from "react";

const getClassData = async (degree: string, year: string) => {
const response = await fetch(`/api/ranking/degrees/${degree}/${year}`);
return (await response.json()) as GetClassData;
};

export default function ClassPage() {
const [data, setData] = useState<GetClassData>({
amount: 0,
numberOfDonors: 0,
donors: [],
});

const donated = true;

const router = useRouter();
const degree = router.query.degree as string;
const year = router.query.year as string;

useEffect(() => {
getClassData(degree, year).then(setData);
});

if (Number(year) < 1900) {
return <p>Invalid year</p>;
}

return (
<div className={styles.html}>
<main className={styles.rankingPage}>
<DonationCallout />
<div className={styles.content}>
<BackButton />
<Header
tag={degree}
tagOnClick={() => {
window.open("/ranking/degrees/" + degree, "_self");
}}
title={year + "-" + String(Number(year) + 4)}
description={"Any"}
/>
<ResultsDisplay amount={data.amount} count={data.numberOfDonors} />
<Donors userDonated={donated} donors={data.donors} />

{!donated && (
<p>Você precisa doar para poder visualizar aos doadores</p>
)}

<ShareCallout donate whatsApp linkedIn copy />
</div>
</main>
</div>
);
}

const Donors = ({
userDonated,
donors,
}: {
userDonated: boolean;
donors: { name: string; url?: string }[];
}) => {
return (
<div className={userDonated ? styles.donors : styles.hideDonors}>
{donors.map((donor) => (
<button

Check failure on line 75 in pages/ranking/degrees/[degree]/[year]/index.tsx

View workflow job for this annotation

GitHub Actions / run-all-checks

Missing "key" prop for element in iterator
className={styles.donor}
onClick={() => {
if (userDonated && donor.url) window.open(donor.url, "_blank");
}}
>
<p>{donor.name}</p>
</button>
))}
</div>
);
};
Loading

0 comments on commit 0946ba7

Please sign in to comment.