Skip to content
This repository has been archived by the owner on Jan 16, 2024. It is now read-only.

88 add user page #89

Merged
merged 44 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e95c81a
chore(types): add type for User Submission
Clumsy-Coder Jan 13, 2024
8214e29
chore(schema:user:submissions): add schema for endpoint `/api/users/[…
Clumsy-Coder Jan 13, 2024
b88c3a2
feat(api:user:submissions): add endpoint `/api/users/[username]/submi…
Clumsy-Coder Jan 13, 2024
953998c
chore(hooks): add react-query hook to fetch user submissions
Clumsy-Coder Jan 13, 2024
d56bc21
chore(shadcn:data-table): add columns structure for user submissions …
Clumsy-Coder Jan 13, 2024
7e68d5b
feat(page:user): display user submissions on `/users/[username]` page
Clumsy-Coder Jan 13, 2024
e83aea2
chore(types): add property `verdictShort` in `ProblemVerdictMap` object
Clumsy-Coder Jan 13, 2024
2c0b18d
chore(types): add type for User submission verdict
Clumsy-Coder Jan 14, 2024
d3b217d
feat(api:user:submissions:verdict): add endpoint `/api/users/[usernam…
Clumsy-Coder Jan 14, 2024
d2854e0
chore(hooks): add react-query hook to fetch user submissions by verdict
Clumsy-Coder Jan 14, 2024
21f3ffe
feat(page:user): display user submissions by verdict with bar chart
Clumsy-Coder Jan 14, 2024
30b9241
style(page:user): format code
Clumsy-Coder Jan 14, 2024
debe276
chore(types:raw): add type for raw User submissions
Clumsy-Coder Jan 14, 2024
76bbdf9
refactor(api:user:submissions): use `RawUserSubmission` type in endpo…
Clumsy-Coder Jan 14, 2024
5d5572a
refactor(types): move type `SubmissionLangType` to `src/types/index.ts`
Clumsy-Coder Jan 14, 2024
9ebdce4
refactor(types): move type `VerdictBarChartType` to `src/types/index.ts`
Clumsy-Coder Jan 14, 2024
ff57181
refactor(api:problemNum:submission:language): process the data for Re…
Clumsy-Coder Jan 14, 2024
ff89cec
chore(hooks): add type returned for hook `useFetchSubmissionLang`
Clumsy-Coder Jan 14, 2024
853f163
refactor(components:charts): change prop type to `SubmissionLangType`
Clumsy-Coder Jan 14, 2024
a789098
refactor(page:problemNum): update datatype passed to component `Submi…
Clumsy-Coder Jan 14, 2024
7a80d9e
feat(api:users:submissions): add endpoint `/api/users/[username]/subm…
Clumsy-Coder Jan 14, 2024
b57dd3c
chore(hooks): add react-query hook to fetch user submissions by language
Clumsy-Coder Jan 14, 2024
0f3a540
feat(page:user): display user submissions by language with radar chart
Clumsy-Coder Jan 14, 2024
9cd4de2
chore(types): add type for raw Problem
Clumsy-Coder Jan 14, 2024
ff0e169
refactor(api:users:submissions): fetch all problems from uhunt api in…
Clumsy-Coder Jan 14, 2024
1a679a5
chore(types): add type for submissions overtime
Clumsy-Coder Jan 14, 2024
95ea3df
refactor(components:charts): use `SubmissionsOvertimeLineChartType` a…
Clumsy-Coder Jan 14, 2024
61f04a9
refactor(api:submission): use `SubmissionsOvertimeLineChartType` as a…
Clumsy-Coder Jan 14, 2024
c820678
feat(api:users:submissions): add endpoint `/api/users/[username]/subm…
Clumsy-Coder Jan 14, 2024
f29453e
chore(hooks): add react-query hook to fetch user submissions overtime
Clumsy-Coder Jan 14, 2024
07cf02c
style(hooks): format code
Clumsy-Coder Jan 14, 2024
aa7e6bd
feat(page:user): display user submissions overtime with area chart
Clumsy-Coder Jan 14, 2024
5e10649
chore(types): add proper color hexcode for `ProblemVerdictMap` object
Clumsy-Coder Jan 14, 2024
5c93f6f
feat(api:users:attempted): add endpoint `/api/users/[username]/submis…
Clumsy-Coder Jan 14, 2024
e192798
chore(hooks): add react-query hook to fetch user submission attempted
Clumsy-Coder Jan 15, 2024
ef829fe
feat(components:charts): add `SolvedVsAttemptedDonutChart` component
Clumsy-Coder Jan 15, 2024
aeefe5e
feat(page:user): display problem solved VS user submissions with donu…
Clumsy-Coder Jan 15, 2024
250458a
chore(components:charts): add style to display tooltip color
Clumsy-Coder Jan 15, 2024
9295cea
chore(page:user): add `Loading` component for `/users/[username]` page
Clumsy-Coder Jan 15, 2024
99fb1ec
chore(page:user): display `Loading` component when fetching data on `…
Clumsy-Coder Jan 15, 2024
5b2bea8
refactor(page:user): get `isFetching` from react-query hooks
Clumsy-Coder Jan 15, 2024
e81600f
style(page:user): format code
Clumsy-Coder Jan 15, 2024
78d1579
docs(docs:images): add screenshot of `/users/[username]` page
Clumsy-Coder Jan 15, 2024
87e1a49
docs(readme): display screenshots for `/users/[username]` page
Clumsy-Coder Jan 15, 2024
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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,10 @@ Check out our [Next.js deployment documentation](https://nextjs.org/docs/deploym

![Problems num page 2](docs/images/page-problems-num.png)

### Username page (`/users/[username]`)
![Username page 1](docs/images/page-users-username-loading.png)

![Username page 2](docs/images/page-users-username.png)


</details>
Binary file added docs/images/page-users-username-loading.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/page-users-username.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 13 additions & 4 deletions src/app/api/submissions/language/[problemNum]/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
uhuntProblemNumUrl,
uhuntProblemSubmissionListUrl,
} from "@/utils/constants";
import { Language, Problem, Submission } from "@/types";
import { Language, Problem, Submission, SubmissionLangType } from "@/types";

type getParamsType = {
params: z.infer<typeof schema>;
Expand Down Expand Up @@ -64,13 +64,22 @@ export const GET = async (_request: Request, { params }: getParamsType) => {
);

// increment count of key-value for their respective language ID
const responseData: getResponseType = submissionData.reduce((acc, cur) => {
const reducedData: Record<string, number> = submissionData.reduce((acc, cur) => {
const languageId = cur.lan;
acc[languageId] = acc[languageId] + 1;

return acc;
}, languageObj);
delete responseData["undefined"];
delete reducedData["undefined"];

const processedData: SubmissionLangType[] = Object.entries(reducedData).map(
([key, value]) => {
return {
language: Language[key],
count: value,
};
},
);

return Response.json(responseData);
return Response.json(processedData);
};
11 changes: 2 additions & 9 deletions src/app/api/submissions/overtime/[problemNum]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,13 @@ import { z } from "zod";
import { submissionOvertimeSchema as schema } from "@/schema";
import { NextResponse } from "next/server";
import { uhuntProblemNumUrl, uhuntSubmissionCountUrl } from "@/utils/constants";
import { Problem } from "@/types";
import { Problem, SubmissionsOvertimeLineChartType } from "@/types";
import moment, { Moment } from "moment";

type getParamsType = {
params: z.infer<typeof schema>;
};

export type getResponseType = {
name: string;
time: string; // time formatted to year
submissions: number;
fill: string;
}

/**
* Get the submission count of a problem using `problem number`
* The submission count will be a cumulative submission count
Expand Down Expand Up @@ -75,7 +68,7 @@ export const GET = async (_request: Request, { params }: getParamsType) => {
// 12 : Number of months each array element will represent
const submissionUrlSplit = submssionCountUrl.split("/");
const thirtyDaysInSeconds = 60 * 60 * 24 * 30;
const responseData:getResponseType[] = data.map((cur, i) => {
const responseData:SubmissionsOvertimeLineChartType[] = data.map((cur, i) => {
const submissionTime = +submissionUrlSplit[7];
const back = +submissionUrlSplit[8];
const jump = +submissionUrlSplit[9];
Expand Down
94 changes: 94 additions & 0 deletions src/app/api/users/[username]/submissions/attempted/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

import { z } from "zod";

import { NextResponse } from "next/server";

import { userSchema as schema } from "@/schema";
import {
Language,
ProblemVerdictMap,
SubmissionLangType,
SubmissionsOvertimeLineChartType,
SubmissionSovledVsAttempted
} from "@/types";
import { RawUserSubmission } from "@/types/raw";
import {
uhuntUserSubmissionsUrl,
uhuntUsername2UidUrl,
} from "@/utils/constants";
import moment from "moment";

type getParamsType = {
params: z.infer<typeof schema>;
};

export const GET = async (_request: Request, {params}: getParamsType) => {
// validate params
const schemaResponse = await schema.safeParseAsync(params);
if (!schemaResponse.success) {
const message = {
message: schemaResponse.error.issues[0].message,
};

return NextResponse.json(message, {
status: 400,
});
}

//----------------------------------------------------------------------------------------------//

// fetch username ID
const { username } = params;

const usernameUrl = uhuntUsername2UidUrl(username);
const usernameResponse = await fetch(usernameUrl);
const usernameData: number = await usernameResponse.json();

// return 404 if problem doesn't exist
if (usernameData === 0) {
const message = {
message: `Username ${username} not found`,
};
return NextResponse.json(message, {
status: 404,
});
}

//----------------------------------------------------------------------------------------------//
// fetch submissions of the user
const userSubmissionsUrl = uhuntUserSubmissionsUrl(usernameData);
const userSubmissionResponse = await fetch(userSubmissionsUrl, {
cache: "no-cache",
});
const userSubmissionData =
(await userSubmissionResponse.json()) as RawUserSubmission;

// count solved submissions
// use a set to keep track of problems solved
const set = new Set();

const verdictSolvedId = 90
userSubmissionData.subs.forEach(cur => {
// cur[2] : verdict ID
if(cur[2] === verdictSolvedId) {
set.add(cur[1]) // add problem ID
}
})

const processedData: SubmissionSovledVsAttempted[] = [
{
name: "Solved",
count: set.size,
tooltipTitle: "Solved unique problems",
fill: ProblemVerdictMap.ac.bgHex
},
{
name: "Attempts",
count: userSubmissionData.subs.length,
tooltipTitle: "Submission attempts",
fill: ProblemVerdictMap.inq.bgHex
}
]

return Response.json(processedData)
}
90 changes: 90 additions & 0 deletions src/app/api/users/[username]/submissions/language/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { z } from "zod";

import { NextResponse } from "next/server";

import { userSchema as schema } from "@/schema";
import { Language, SubmissionLangType } from "@/types";
import { RawUserSubmission } from "@/types/raw";
import {
uhuntUserSubmissionsUrl,
uhuntUsername2UidUrl,
} from "@/utils/constants";

type getParamsType = {
params: z.infer<typeof schema>;
};

export const GET = async (_request: Request, { params }: getParamsType) => {
// validate params
const schemaResponse = await schema.safeParseAsync(params);
if (!schemaResponse.success) {
const message = {
message: schemaResponse.error.issues[0].message,
};

return NextResponse.json(message, {
status: 400,
});
}

//----------------------------------------------------------------------------------------------//

// fetch username ID
const { username } = params;

const usernameUrl = uhuntUsername2UidUrl(username);
const usernameResponse = await fetch(usernameUrl);
const usernameData: number = await usernameResponse.json();

// return 404 if problem doesn't exist
if (usernameData === 0) {
const message = {
message: `Username ${username} not found`,
};
return NextResponse.json(message, {
status: 404,
});
}

//----------------------------------------------------------------------------------------------//

// fetch submissions of the user
const userSubmissionsUrl = uhuntUserSubmissionsUrl(usernameData);
const userSubmissionResponse = await fetch(userSubmissionsUrl, {
cache: "no-cache",
});
const userSubmissionData =
(await userSubmissionResponse.json()) as RawUserSubmission;

// map language id as key and 0 as value. (the value will be count of a submission language)
let languageObj = Object.keys(Language).reduce(
(acc: Record<string, number>, cur: string) => {
acc[cur] = 0;

return acc;
},
{},
);

// count the submissions by language id
// object key: language ID
// object value: count
const reducedData = userSubmissionData.subs.reduce((acc, cur) => {
const languageId = cur[5];
acc[languageId] = acc[languageId] + 1;

return acc;
}, languageObj);
delete reducedData["undefined"];

const processedData: SubmissionLangType[] = Object.entries(reducedData).map(
([key, value]) => {
return {
language: Language[key],
count: value,
};
},
);

return Response.json(processedData);
};
117 changes: 117 additions & 0 deletions src/app/api/users/[username]/submissions/overtime/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { z } from "zod";

import { NextResponse } from "next/server";

import { userSchema as schema } from "@/schema";
import {
Language,
SubmissionLangType,
SubmissionsOvertimeLineChartType,
} from "@/types";
import { RawUserSubmission } from "@/types/raw";
import {
uhuntUserSubmissionsUrl,
uhuntUsername2UidUrl,
} from "@/utils/constants";
import moment from "moment";

type getParamsType = {
params: z.infer<typeof schema>;
};

export const GET = async (_request: Request, { params }: getParamsType) => {
// validate params
const schemaResponse = await schema.safeParseAsync(params);
if (!schemaResponse.success) {
const message = {
message: schemaResponse.error.issues[0].message,
};

return NextResponse.json(message, {
status: 400,
});
}

//----------------------------------------------------------------------------------------------//

// fetch username ID
const { username } = params;

const usernameUrl = uhuntUsername2UidUrl(username);
const usernameResponse = await fetch(usernameUrl);
const usernameData: number = await usernameResponse.json();

// return 404 if problem doesn't exist
if (usernameData === 0) {
const message = {
message: `Username ${username} not found`,
};
return NextResponse.json(message, {
status: 404,
});
}

//----------------------------------------------------------------------------------------------//
// fetch submissions of the user
const userSubmissionsUrl = uhuntUserSubmissionsUrl(usernameData);
const userSubmissionResponse = await fetch(userSubmissionsUrl, {
cache: "no-cache",
});
const userSubmissionData =
(await userSubmissionResponse.json()) as RawUserSubmission;

// count submissions by year
const initData: Record<string, number> = {};
let reducedData = userSubmissionData.subs.reduce((obj, cur, index) => {
const year = moment.unix(cur[4]).format("YYYY");
obj[year] = obj[year] + 1 || 1;

return obj;
}, initData);

// add missing years
// - loop through the array starting from index 1 going upto last array element
// - convert processedData into an array using Object.entries
// - sort the array
// - get the current element and previous element
// - loop through the difference between curYear and prevYear
// - add entry to `processedData`
for (let i = 1; i < Object.entries(reducedData).length; i++) {
const sorted = Object.entries(reducedData).sort((a, b) => +a[0] - +b[0]);
const curYear = +sorted[i][0];
const prevYear = +sorted[i - 1][0];

// if (curYear - prevYear > 1) {
for (let k = prevYear + 1; k < curYear; k++) {
reducedData[k] = 0;
}
// }
}

// calculate cumulative sum
for (let i = 1; i < Object.entries(reducedData).length; i++) {
const sorted = Object.entries(reducedData).sort((a, b) => +a[0] - +b[0]);
const [curYear, curYearCount] = sorted[i];
const [prevYear, prevYearCount] = sorted[i - 1];

reducedData[curYear] = curYearCount + prevYearCount;
}

// construct data structure for Rechart area/line chart
const initProcessedData: SubmissionsOvertimeLineChartType[] = [];
const processedData: SubmissionsOvertimeLineChartType[] = Object.entries(
reducedData,
).reduce((obj, cur, index) => {
const [year, count] = cur;
obj.push({
name: "submissions",
time: year,
submissions: count,
fill: "#8884d8",
});

return obj;
}, initProcessedData);

return Response.json(processedData);
};
Loading