From ac15a97746108f88755521d0f823eca2a7eb97c6 Mon Sep 17 00:00:00 2001 From: sidxh Date: Sun, 17 Mar 2024 13:16:23 +0530 Subject: [PATCH 1/7] feat: added team code, joining functionality --- .gitignore | 1 + src/app/api/join-team.ts | 33 ++++++++++++++ src/app/join-team/page.tsx | 54 ++++++++++++++++++++++ src/components/FormNewProject.tsx | 30 ++++++++++++- src/dummy/dummy.project.form.tsx | 18 +++++--- src/github/config.github.ts | 74 ++++++++++++++++++++++--------- src/types/form.types.tsx | 1 + src/utils/generateTeamCode.ts | 12 +++++ src/utils/handleConnectGithub.ts | 2 +- src/zod/zod.common.ts | 3 +- 10 files changed, 198 insertions(+), 30 deletions(-) create mode 100644 src/app/api/join-team.ts create mode 100644 src/app/join-team/page.tsx create mode 100644 src/utils/generateTeamCode.ts diff --git a/.gitignore b/.gitignore index 42c2f0b..1b5152f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. /.env +/devclans-local.pem # dependencies /node_modules /.pnp diff --git a/src/app/api/join-team.ts b/src/app/api/join-team.ts new file mode 100644 index 0000000..9326645 --- /dev/null +++ b/src/app/api/join-team.ts @@ -0,0 +1,33 @@ +// app/api/join-team/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import dbConnect from '@/lib/dbConnect'; +import { ProjectModel } from '@/mongodb/models'; + +export async function POST(req: NextRequest) { + try { + const { teamCode } = await req.json(); + + await dbConnect(); + + // Find the project with the provided team code + const project = await ProjectModel.findOne({ teamCode }); + + if (!project) { + return NextResponse.json({ error: 'Project not found' }, { status: 404 }); + } + + // Get the current user's ID from the session or request + const userId = req.cookies.get('session')?.value; // Replace with your logic to get the user ID + + // Add the user's ID to the contributors array + project.contributors.push(userId); + + // Save the updated project document + await project.save(); + + return NextResponse.json({ message: 'Joined team successfully' }); + } catch (error) { + console.error('Error joining team:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/join-team/page.tsx b/src/app/join-team/page.tsx new file mode 100644 index 0000000..4f9ae85 --- /dev/null +++ b/src/app/join-team/page.tsx @@ -0,0 +1,54 @@ +// pages/join-team.tsx +"use client"; +import { ButtonBlue } from '@/components'; +import { useState } from 'react'; + +const JoinTeamPage = () => { + const [teamCode, setTeamCode] = useState(''); + const [message, setMessage] = useState(''); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + try { + const response = await fetch('/api/join-team', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ teamCode }), + }); + + const data = await response.json(); + + if (response.ok) { + setMessage(data.message); + } else { + setMessage(data.error); + } + } catch (error) { + console.error('Error joining team:', error); + setMessage('An error occurred while joining the team'); + } + }; + + return ( +
+

Join A Team

+
+ + + + {message &&

{message}

} +
+ ); +}; + +export default JoinTeamPage; \ No newline at end of file diff --git a/src/components/FormNewProject.tsx b/src/components/FormNewProject.tsx index a45bc0c..ed09497 100644 --- a/src/components/FormNewProject.tsx +++ b/src/components/FormNewProject.tsx @@ -17,6 +17,7 @@ import { usePathname } from "next/navigation"; import { getGHInstaddedRepos } from "@/utils/getInstalledRepos"; import { handleGithubChangeRepos } from "@/utils/handleConnectGithub"; import ButtonConnectGithub from "./buttons/ButtonConnectGithub"; +import { generateTeamCode } from "@/utils/generateTeamCode"; const FormNewProject = ({ defaultValues: dv, @@ -25,6 +26,7 @@ const FormNewProject = ({ defaultValues?: Partial; projectId?: string; }) => { + const [teamCode, setTeamCode] = useState(''); const { data }: any = useSession(); const pathname = usePathname(); const session = data?.user; @@ -40,6 +42,13 @@ const FormNewProject = ({ resolver: zodResolver(zodProjectFormSchema), }); console.log("defaultValues", watch("repoName")); + + const handleGenerateTeamCode = () => { + const code = generateTeamCode(); + setTeamCode(code); + setValue('teamCode', code); + }; + const onSubmit: SubmitHandler = async (data) => { try { // const a = zodProjectFormSchema.parse(data); @@ -51,6 +60,10 @@ const FormNewProject = ({ } setDefaultValues(data); console.log("clicked"); + const formData: ProjectFormProps = { + ...data, + teamCode: teamCode || null, + }; // const dt = zodProjectFormSchema.parse(data); // console.log(dt); // return; @@ -59,7 +72,7 @@ const FormNewProject = ({ : "/db/createProject"; const res = await createProjectUser( url, - data, + formData, session, setError, projectId @@ -189,6 +202,21 @@ const FormNewProject = ({ href={projectId && `/project/${projectId}`} /> )} +
+ + {teamCode && ( +
+

Team Code: {teamCode}

+
+ )} +
} buttonMessage={projectId ? "Update Project" : "Create Project"} diff --git a/src/dummy/dummy.project.form.tsx b/src/dummy/dummy.project.form.tsx index 81595e5..ebe2a3b 100644 --- a/src/dummy/dummy.project.form.tsx +++ b/src/dummy/dummy.project.form.tsx @@ -51,12 +51,20 @@ export const dummyProjectFormSchemaFields: InputFieldProps[] = [ name: "devStage", options: devStages, }, + + { + label: 'Team Code', + desc: 'Generate a team code to allow others to join your project team.', + name: 'teamCode', + type: 'text', + readOnly: true, // Make the field read-only + }, + // { - // label: "Team", - // desc: "Select the team members involved in the project.", - // name: "team", - // // Assuming zodUserTeamItemSchema is defined elsewhere - // // and represents the schema for team members + // label: 'Team Members', + // desc: 'Enter the Discord IDs of team members, separated by commas (e.g., @rahul, @sunil, @priya).', + // name: 'teamMembers', + // type: 'text', // }, // { diff --git a/src/github/config.github.ts b/src/github/config.github.ts index b26771f..e544411 100644 --- a/src/github/config.github.ts +++ b/src/github/config.github.ts @@ -1,5 +1,6 @@ import { zodGithubAccessToken } from "@/zod/zod.common"; import { App, Octokit } from "octokit"; +import fs from 'fs'; export const getOctokit = async ({ installationId, @@ -10,28 +11,56 @@ export const getOctokit = async ({ }) => { try { if (installationId) { - const appId = process.env.AUSPY_GITHUB_APP_ID; - - const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; - if (!base64Key) { - throw new Error("Private Key not found"); - } - // Decode the Base64-encoded key to binary data - const binaryKey = Buffer.from(base64Key, "base64"); - // Convert the binary key to a string - const stringKey = binaryKey.toString("utf8"); - - if (!appId || !stringKey) { - throw new Error("App ID or Private Key not found"); - } - const app = new App({ - appId, - privateKey: stringKey, - }); - return { - api: await app.getInstallationOctokit(installationId), - type: "app", - }; + // const appId = process.env.AUSPY_GITHUB_APP_ID; + + // const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; + // if (!base64Key) { + // throw new Error("Private Key not found"); + // } + // // Decode the Base64-encoded key to binary data + // const binaryKey = Buffer.from(base64Key, "base64"); + // // Convert the binary key to a string + // const stringKey = binaryKey.toString("utf8"); + + // if (!appId || !stringKey) { + // throw new Error("App ID or Private Key not found"); + // } + // const app = new App({ + // appId, + // privateKey: stringKey, + // }); + // return { + // api: await app.getInstallationOctokit(installationId), + // type: "app", + // }; + + + const appId = process.env.AUSPY_GITHUB_APP_ID; + const filepath = './devclans-local.pem'; + + const base64Key = fs + .readFileSync(filepath, "base64") + .replace("-----BEGIN RSA PRIVATE KEY-----", "") + .replace("-----END RSA PRIVATE KEY-----", "") + .trim(); + // Decode the Base64-encoded key to binary data + const binaryKey = Buffer.from(base64Key, "base64"); + + // Convert the binary key to a string (optional) + const stringKey = binaryKey.toString("utf8"); + // console.log("My key is: ", myKey); + if (!appId || !base64Key) { + throw new Error("App ID or Private Key not found"); + } + const app = new App({ + appId, + privateKey: stringKey, + }); + return { + api: await app.getInstallationOctokit(installationId), + type: "app", + }; + } else if (zodGithubAccessToken.safeParse(accessToken).success) { return { api: new Octokit({ @@ -47,3 +76,4 @@ export const getOctokit = async ({ return null; } }; + diff --git a/src/types/form.types.tsx b/src/types/form.types.tsx index 8209e42..9698152 100644 --- a/src/types/form.types.tsx +++ b/src/types/form.types.tsx @@ -11,6 +11,7 @@ export type InputFieldProps = { required?: boolean; min?: number; preText?: string; + readOnly?: boolean }; export type FormClientProps = { zodSchema: any; diff --git a/src/utils/generateTeamCode.ts b/src/utils/generateTeamCode.ts new file mode 100644 index 0000000..117e8e8 --- /dev/null +++ b/src/utils/generateTeamCode.ts @@ -0,0 +1,12 @@ +export const generateTeamCode = (): string => { + const length = 10; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+~`|}{[]:;?><,./-='; + let code = ''; + + for (let i = 0; i < length; i++) { + const randomIndex = Math.floor(Math.random() * characters.length); + code += characters[randomIndex]; + } + + return code; + }; \ No newline at end of file diff --git a/src/utils/handleConnectGithub.ts b/src/utils/handleConnectGithub.ts index 66e6685..fadee12 100644 --- a/src/utils/handleConnectGithub.ts +++ b/src/utils/handleConnectGithub.ts @@ -2,7 +2,7 @@ import { z } from "zod"; export const handleGithubChangeRepos = async () => { - const url = `https://github.com/apps/devclans/installations/new`; + const url = `https://github.com/apps/devclans-local/installations/new`; window.open(url, "_blank"); }; diff --git a/src/zod/zod.common.ts b/src/zod/zod.common.ts index 72b41db..912172a 100644 --- a/src/zod/zod.common.ts +++ b/src/zod/zod.common.ts @@ -475,11 +475,12 @@ export const zodGithubDataSchema = z.object({ owner: projectSchema.shape["owner"], repoName: zodRepoName, }); -export const zodProjectFormSchema = z.object({ +export const zodProjectFormSchema:any = z.object({ title: z.string().trim().min(3).max(50), desc: z.string().min(10).max(200), skills: skillsSchema, team: zodUserTeamItemSchema.optional(), + teamCode: z.string().optional().nullable(), skillLevel: z .enum(memberLevels as any) .nullable() From cd4dd8852754a2e9e4d0e43afe2829213f40b22e Mon Sep 17 00:00:00 2001 From: sidxh Date: Wed, 20 Mar 2024 02:38:04 +0530 Subject: [PATCH 2/7] fix: updated github configs --- src/github/config.github.ts | 113 +++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 52 deletions(-) diff --git a/src/github/config.github.ts b/src/github/config.github.ts index e544411..0bae7e4 100644 --- a/src/github/config.github.ts +++ b/src/github/config.github.ts @@ -1,7 +1,32 @@ import { zodGithubAccessToken } from "@/zod/zod.common"; import { App, Octokit } from "octokit"; -import fs from 'fs'; +import fs from "fs" + +function convertPemToBase64(filePath:string) { + try { + // Read the .pem file synchronously + const pemData = fs.readFileSync(filePath, 'base64'); + + // Extract the RSA key from the .pem file, removing header, footer, and newlines + const base64Key = pemData + .replace(/-----BEGIN RSA PRIVATE KEY-----/, "") + .replace(/-----END RSA PRIVATE KEY-----/, "") + .trim() + + + return base64Key; + } catch (error) { + console.error('Failed to convert PEM to Base64:', error); + return null; // or throw the error, depending on your error handling strategy + } +} + +function readFile(filePath:string){ + // read base64 file and return + const base64Key = fs.readFileSync(filePath, 'base64'); + return base64Key; +} export const getOctokit = async ({ installationId, accessToken, @@ -11,57 +36,41 @@ export const getOctokit = async ({ }) => { try { if (installationId) { - // const appId = process.env.AUSPY_GITHUB_APP_ID; - - // const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; - // if (!base64Key) { - // throw new Error("Private Key not found"); - // } - // // Decode the Base64-encoded key to binary data - // const binaryKey = Buffer.from(base64Key, "base64"); - // // Convert the binary key to a string - // const stringKey = binaryKey.toString("utf8"); - - // if (!appId || !stringKey) { - // throw new Error("App ID or Private Key not found"); - // } - // const app = new App({ - // appId, - // privateKey: stringKey, - // }); - // return { - // api: await app.getInstallationOctokit(installationId), - // type: "app", - // }; - + const appId = process.env.AUSPY_GITHUB_APP_ID; - const appId = process.env.AUSPY_GITHUB_APP_ID; - const filepath = './devclans-local.pem'; - - const base64Key = fs - .readFileSync(filepath, "base64") - .replace("-----BEGIN RSA PRIVATE KEY-----", "") - .replace("-----END RSA PRIVATE KEY-----", "") - .trim(); - // Decode the Base64-encoded key to binary data - const binaryKey = Buffer.from(base64Key, "base64"); - - // Convert the binary key to a string (optional) - const stringKey = binaryKey.toString("utf8"); - // console.log("My key is: ", myKey); - if (!appId || !base64Key) { - throw new Error("App ID or Private Key not found"); - } - const app = new App({ - appId, - privateKey: stringKey, - }); - return { - api: await app.getInstallationOctokit(installationId), - type: "app", - }; - +// Usage example +const filePath = 'src/github/privateKey_pkcs8.pem'; +//const base64Key = readFile(filePath); +const base64Key = convertPemToBase64(filePath); +//const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; + //const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; + if (!base64Key) { + throw new Error("Private Key not found"); + } + console.log("This is base64Key",base64Key); + // Decode the Base64-encoded key to binary data + const binaryKey = Buffer.from(base64Key, "base64"); + // console.log("This is binaryKey",binaryKey); + // Convert the binary key to a string + const stringKey = binaryKey.toString("utf8"); + // console.log("This is stringKey",stringKey); + + if (!appId || !stringKey) { + throw new Error("App ID or Private Key not found"); + } + const app = new App({ + appId, + privateKey: stringKey, + }); + console.log(app); + const api = await app.getInstallationOctokit(installationId); + console.log("returning as app",api); + return { + api: api, + type: "app", + }; } else if (zodGithubAccessToken.safeParse(accessToken).success) { + console.log("returning as auth") return { api: new Octokit({ auth: accessToken, @@ -69,11 +78,11 @@ export const getOctokit = async ({ type: "auth", }; } else { + console.log("returning as free") return { api: new Octokit({}), type: "free" }; } } catch (error) { console.error("Error getting octokit", error); return null; } -}; - +}; \ No newline at end of file From 9ff88798f78d6ef9a36f35b7fa6401180bd54236 Mon Sep 17 00:00:00 2001 From: Satvik1769 Date: Wed, 20 Mar 2024 08:16:12 +0530 Subject: [PATCH 3/7] updated config --- .vscode/settings.json | 79 ++++++++++++++------ package.json | 1 + pnpm-lock.yaml | 7 ++ public/config.github.ts | 79 ++++++++++++++++++++ src/app/api/auth/github/appcallback/route.ts | 6 +- src/app/explore/projects/page.tsx | 2 +- src/components/FormNewProject.tsx | 5 +- src/github/config.github.ts | 47 +++++++++++- src/github/private_key.base64 | 22 ++++++ src/utils/getInstalledReposFunc.ts | 18 ++++- src/utils/handleConnectGithub.ts | 4 +- 11 files changed, 237 insertions(+), 33 deletions(-) create mode 100644 public/config.github.ts create mode 100644 src/github/private_key.base64 diff --git a/.vscode/settings.json b/.vscode/settings.json index 7c3cc49..03d2f68 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,23 +1,60 @@ { - "accessibility.alert.format": "never", - "accessibility.alert.save": "never", - "audioCues.volume": 0, - "audioCues.debouncePositionChanges": false, - "audioCues.diffLineDeleted": "off", - "audioCues.diffLineInserted": "off", - "audioCues.terminalQuickFix": "off", - "audioCues.terminalCommandFailed": "off", - "audioCues.taskFailed": "off", - "audioCues.taskCompleted": "off", - "audioCues.onDebugBreak": "off", - "audioCues.notebookCellFailed": "off", - "audioCues.notebookCellCompleted": "off", - "audioCues.noInlayHints": "off", - "audioCues.lineHasInlineSuggestion": "off", - "audioCues.lineHasFoldedArea": "off", - "audioCues.lineHasError": "off", - "audioCues.lineHasBreakpoint": "off", - "audioCues.diffLineModified": "off", - "audioCues.chatResponsePending": "off", - "editor.accessibilitySupport": "off" + "editor.accessibilitySupport": "off", + "accessibility.signals.sounds.volume": 0, + "accessibility.signals.debouncePositionChanges": false, + "accessibility.signals.lineHasError": { + "sound": "off" + }, + "accessibility.signals.lineHasFoldedArea": { + "sound": "off" + }, + "accessibility.signals.lineHasBreakpoint": { + "sound": "off" + }, + "accessibility.signals.lineHasInlineSuggestion": { + "sound": "off" + }, + "accessibility.signals.terminalQuickFix": { + "sound": "off" + }, + "accessibility.signals.onDebugBreak": { + "sound": "off" + }, + "accessibility.signals.noInlayHints": { + "sound": "off" + }, + "accessibility.signals.taskCompleted": { + "sound": "off" + }, + "accessibility.signals.taskFailed": { + "sound": "off" + }, + "accessibility.signals.terminalCommandFailed": { + "sound": "off" + }, + "accessibility.signals.notebookCellCompleted": { + "sound": "off" + }, + "accessibility.signals.notebookCellFailed": { + "sound": "off" + }, + "accessibility.signals.diffLineInserted": { + "sound": "off" + }, + "accessibility.signals.diffLineDeleted": { + "sound": "off" + }, + "accessibility.signals.diffLineModified": { + "sound": "off" + }, + "accessibility.signals.chatResponsePending": { + "sound": "off" + }, + "accessibility.signals.save": { + "announcement": "never" + }, + "accessibility.signals.format": { + "announcement": "never" + }, + "CodeGPT.apiKey": "Mistral" } diff --git a/package.json b/package.json index f243281..2f2e261 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "axios": "^1.6.5", "bson": "^6.2.0", "fast-json-stable-stringify": "^2.1.0", + "fs": "0.0.1-security", "github-calendar": "^2.3.2", "github-calendar-graph": "^0.2.8", "ioredis": "^5.3.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 776a34d..4516a4e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ dependencies: fast-json-stable-stringify: specifier: ^2.1.0 version: 2.1.0 + fs: + specifier: 0.0.1-security + version: 0.0.1-security github-calendar: specifier: ^2.3.2 version: 2.3.2 @@ -2799,6 +2802,10 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true + /fs@0.0.1-security: + resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} + dev: false + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} diff --git a/public/config.github.ts b/public/config.github.ts new file mode 100644 index 0000000..ff46fb9 --- /dev/null +++ b/public/config.github.ts @@ -0,0 +1,79 @@ +import { zodGithubAccessToken } from "@/zod/zod.common"; +import { App, Octokit } from "octokit"; +import fs from 'fs'; + +export const getOctokit = async ({ + installationId, + accessToken, +}: { + installationId?: number; + accessToken?: string; +}) => { + try { + if (installationId) { + const appId = process.env.AUSPY_GITHUB_APP_ID; + + const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; + if (!base64Key) { + throw new Error("Private Key not found"); + } + // Decode the Base64-encoded key to binary data + const binaryKey = Buffer.from(base64Key, "base64"); + // Convert the binary key to a string + const stringKey = binaryKey.toString("utf8"); + + if (!appId || !stringKey) { + throw new Error("App ID or Private Key not found"); + } + const app = new App({ + appId, + privateKey: stringKey, + }); + return { + api: await app.getInstallationOctokit(installationId), + type: "app", + }; + + + // const appId = process.env.AUSPY_GITHUB_APP_ID; + // const filepath = './devclans-local.pem'; + + // const base64Key = fs + // .readFileSync(filepath, "base64") + // .replace("-----BEGIN RSA PRIVATE KEY-----", "") + // .replace("-----END RSA PRIVATE KEY-----", "") + // .trim(); + // // Decode the Base64-encoded key to binary data + // const binaryKey = Buffer.from(base64Key, "base64"); + + // // Convert the binary key to a string (optional) + // const stringKey = binaryKey.toString("utf8"); + // // console.log("My key is: ", myKey); + // if (!appId || !base64Key) { + // throw new Error("App ID or Private Key not found"); + // } + // const app = new App({ + // appId, + // privateKey: stringKey, + // }); + // return { + // api: await app.getInstallationOctokit(installationId), + // type: "app", + // }; + + } else if (zodGithubAccessToken.safeParse(accessToken).success) { + return { + api: new Octokit({ + auth: accessToken, + }), + type: "auth", + }; + } else { + return { api: new Octokit({}), type: "free" }; + } + } catch (error) { + console.error("Error getting octokit", error); + return null; + } +}; + diff --git a/src/app/api/auth/github/appcallback/route.ts b/src/app/api/auth/github/appcallback/route.ts index e81b0a9..e7b3d63 100644 --- a/src/app/api/auth/github/appcallback/route.ts +++ b/src/app/api/auth/github/appcallback/route.ts @@ -15,7 +15,10 @@ export const dynamic = "force-dynamic"; const handler = async (req: NextRequest) => { try { console.log(" start of github/appcallback"); - // const code = req.nextUrl.searchParams.get("code"); + const code = req.nextUrl.searchParams.get("code"); + const setupAction = req.nextUrl.searchParams.get("setup_action"); + console.log(setupAction, code); + console.log(req.nextUrl.searchParams.get("installation_id")) const installId = zodGithubInstallationId.parse( req.nextUrl.searchParams.get("installation_id") ); @@ -26,6 +29,7 @@ const handler = async (req: NextRequest) => { const userId = zodMongoId.parse(session?.user?._id); // get installed repos const reposData = await getInstalledReposFunc(installId, false); + console.log(reposData) // get user profile await updateData(userId, installId, reposData); console.log("success of github/appcallback, redirecting"); diff --git a/src/app/explore/projects/page.tsx b/src/app/explore/projects/page.tsx index 46719ea..4c553af 100644 --- a/src/app/explore/projects/page.tsx +++ b/src/app/explore/projects/page.tsx @@ -18,7 +18,7 @@ const Projects = async ({ params, searchParams }: Partial) => { const projects: ProjectSearchItemProps[] = (await Fetch({ endpoint: "/project" + (str ? `?${str}` : ""), - revalidate: 3600 * 3, // TODO - set revalidate time + revalidate: 1, // TODO - set revalidate time })) || []; console.log( Array.isArray(projects) && projects.length > 0, diff --git a/src/components/FormNewProject.tsx b/src/components/FormNewProject.tsx index a45bc0c..8d66eb7 100644 --- a/src/components/FormNewProject.tsx +++ b/src/components/FormNewProject.tsx @@ -126,13 +126,16 @@ const FormNewProject = ({ useEffect(() => { updateRepofield(["Loading..."], true); if (!isUserConnected) { + console.log("user not connected"); updateRepofield([], true); return; } if (Array.isArray(repos) && repos.length > 0) { + console.log("user is connected and has some repos"); updateRepofield([null, ...repos], true); } else { if (Array.isArray(repos)) { + console.log("user is connected but has no repos yet"); updateRepofield([], true); } } @@ -199,4 +202,4 @@ const FormNewProject = ({ ); }; -export default FormNewProject; +export default FormNewProject; \ No newline at end of file diff --git a/src/github/config.github.ts b/src/github/config.github.ts index b26771f..3566857 100644 --- a/src/github/config.github.ts +++ b/src/github/config.github.ts @@ -1,6 +1,32 @@ import { zodGithubAccessToken } from "@/zod/zod.common"; import { App, Octokit } from "octokit"; +import fs from "fs" + +function convertPemToBase64(filePath:string) { + try { + // Read the .pem file synchronously + const pemData = fs.readFileSync(filePath, 'base64'); + + // Extract the RSA key from the .pem file, removing header, footer, and newlines + const base64Key = pemData + .replace(/-----BEGIN RSA PRIVATE KEY-----/, "") + .replace(/-----END RSA PRIVATE KEY-----/, "") + .trim() + + + return base64Key; + } catch (error) { + console.error('Failed to convert PEM to Base64:', error); + return null; // or throw the error, depending on your error handling strategy + } +} + +function readFile(filePath:string){ + // read base64 file and return + const base64Key = fs.readFileSync(filePath, 'base64'); + return base64Key; +} export const getOctokit = async ({ installationId, accessToken, @@ -11,16 +37,24 @@ export const getOctokit = async ({ try { if (installationId) { const appId = process.env.AUSPY_GITHUB_APP_ID; - - const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; + +// Usage example +const filePath = 'src/github/privateKey_pkcs8.pem'; +//const base64Key = readFile(filePath); +const base64Key = convertPemToBase64(filePath); +//const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; + //const base64Key = process.env.AUSPY_GITHUB_PRIVATE_KEY; if (!base64Key) { throw new Error("Private Key not found"); } + console.log("This is base64Key",base64Key); // Decode the Base64-encoded key to binary data const binaryKey = Buffer.from(base64Key, "base64"); + // console.log("This is binaryKey",binaryKey); // Convert the binary key to a string const stringKey = binaryKey.toString("utf8"); - + // console.log("This is stringKey",stringKey); + if (!appId || !stringKey) { throw new Error("App ID or Private Key not found"); } @@ -28,11 +62,15 @@ export const getOctokit = async ({ appId, privateKey: stringKey, }); + console.log(app); + const api = await app.getInstallationOctokit(installationId); + console.log("returning as app",api); return { - api: await app.getInstallationOctokit(installationId), + api: api, type: "app", }; } else if (zodGithubAccessToken.safeParse(accessToken).success) { + console.log("returning as auth") return { api: new Octokit({ auth: accessToken, @@ -40,6 +78,7 @@ export const getOctokit = async ({ type: "auth", }; } else { + console.log("returning as free") return { api: new Octokit({}), type: "free" }; } } catch (error) { diff --git a/src/github/private_key.base64 b/src/github/private_key.base64 new file mode 100644 index 0000000..716e24c --- /dev/null +++ b/src/github/private_key.base64 @@ -0,0 +1,22 @@ +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCZYvgH9+wyY/Sd1r4Q1wPplj8k +iDvPbdV4VL+8YpgWgVEnZbi+jLTuGU7IWL1Bq8m6J0mzIXJQYv/s8KkIlzJ1e8G/bYVib6nDSe2w +X+4aWKNJ4ToHm591uAZH1dhJUkK0qFcBF3We5rjD5C4T1JWvGO1NE3qVaTxBCguDvdsX18/t2nz/ +DBxaUcRqetu6u/Yx9pV9JyH/xEIlWHcCz7iXuBgPNByQ+mVPc637joDR8MT52eOW1PY6oUL/UdR2 +lY/linb57srqrGfETeLhsYvVb31HtWWI/lj4NRH/IVN2MRblR/iJVYTO92Mspmh6g9fxUN6oxfNb +eO2kUJUb5PfPAgMBAAECggEAbxXM+cBksA3R76nBFuMRc08bxbJgVpZ93lrKTX2+FbZr9rNlu6Kd +uj5b3x7dWb03KHkhl/imGG/KV2fnk1Ak/A63v5Jq4mJqOQPGf1DKWQxOncfexfm/33p6GfZWYKuR +odK0yBacWYtQzpXnhPcVvKy513M++Qww1g2XTz7leHr1lkkLR0nqMYadYdCYIihd9WTXs8phHzJp +4KPfFIbJE1gUm6A9gVzaCsyjd85LBWULsunORDLoEU7db7HPqYxNY9pwJ0fjxRLqX9Oov0vB0IDh +6S9APRwceLFKn8I4kiB/gPqX4jmvq38besrnS5+7OciFfKGqqFmuVRnSbxLpkQKBgQDGWUMt/Nsb +ntsRGqn2rPlTBwmpIDXDBgmztedesTUjjBzwNxrIKMcmRQGZJxilDzDC4a/9LwybF+8urxvhGQBi +1Nx0E5FY12uSurf6NN1xCaYFe9v8jlQkUyBIeXioQ5v1CWgtVj4AuPqndlpLhVZwKFa/ZSfERGKs +sLJMB/tTdwKBgQDF+CpppWiLWOwZg6N4MGw5hIVZGJwlYPSUxcj7JcqvZewdbjduXRDSVqu3fA3P +W2Kh4zuZ4UIQFjcLrM7aoNNrXHu+A2um+2HuRTvq73ANKNA2lLxeRVEcGsyn5y7H25JH74c5lmcL +1/z3Hlr9B5t/HCS7D0K7jVuMR+eIuUUkaQKBgGJYug2gVUfRsU8+yE1OIOWBFNIt7VpAsSjRJeJ2 +vSFeBK86s2XRikU4a/1zPC4DZSsL2kYQQRDZgjfEjW9EBUP7xT8swKGrVmRPfo9qDO5SWWZYrpAA +RkooiVZGHIRZ2Yda3bO1ElMrZOUJsuXFPEGqTrDhyCDxw3dauV0ni/8rAoGBALv/e2dwKOOLwkpI +dEiC04ObkXaWpFHqzNkrKj02GlCOnbclH4ay4nKc090e9Y5g3j2WKNdszuuv7P4tURLVf3CJFwnO +/cBJISrAnLHyR5p3k6MBhBCSCwCBgrBAHnC01ylxEh1h8uGQby3NqeJ4Avamp0rspxasmq+Q6RMx +vbNhAoGBAJ7MRYS1Csv2R2NTDAtZQdTcqqfxY/ceTgWiOz44x3v8GaU/lYtI4GCfwFDTlnXoblBR +wrFadajB4eQotWHlqLdH4DOvL83EyP7mNw1P+zIvSKzTo5xqWmn4mpRPJn9UGE91u1wMEG8IPBt8 +zlPH3U4OfVGeqgy4pYq/x2YoUCvl diff --git a/src/utils/getInstalledReposFunc.ts b/src/utils/getInstalledReposFunc.ts index 35507b9..2f3b8b7 100644 --- a/src/utils/getInstalledReposFunc.ts +++ b/src/utils/getInstalledReposFunc.ts @@ -8,29 +8,34 @@ import getServerSessionForServer from "./auth/getServerSessionForApp"; import { decrypt } from "./EncryptFunctions"; export const getInstallationId = async (userId?: string) => { - let installId: number | undefined; + let installId: number ; try { const uid = zodMongoId.parse(userId); console.log("getting install id from cache"); + console.log("uid: ",uid) const cacheInstallId = await redisGet(UserRedisKeys.installId, uid); + console.log(cacheInstallId); installId = parseInt(cacheInstallId || ""); + console.log(cacheInstallId); // if not in cache, get installation id from github if (installId) { - console.log("install id from cache hit"); + console.log("install id from cache hit", installId); return installId; } console.log("install id from cache miss"); const id: UserProps | null = await UserModel.findById(uid) .select("githubDetails.installId") .lean(); + console.log("id after lean: ",id) installId = parseInt( decrypt(id?.githubDetails?.installId?.toString() || "") ); + console.log(installId); if (installId) { - console.log("install id from db hit"); + console.log("install id from db hit: ", installId); return installId; } - console.log("install id from db miss"); + console.log("install id from db miss: ", installId); return installId; } catch (error) { console.error("error in getInstallationId", error); @@ -59,18 +64,22 @@ export const getInstalledReposFunc = async ( const installId: number = zodGithubInstallationId.parse( installationId || (await getInstallationId(userId)) ); + console.log("This is installId:", installId); console.log("getting octokit"); const api = await getOctokit({ installationId: installId }); + if (!(api && api.type === "app")) { console.log("error getting octokit"); throw new Error("Error getting octokit"); } console.log("getting repos"); + const repos = await api.api.request("GET /installation/repositories", { headers: { "X-GitHub-Api-Version": "2022-11-28", }, }); + console.log(repos); if (repos.status !== 200) { console.log("error getting repos"); throw new Error("Error getting repos"); @@ -99,6 +108,7 @@ export const getInstalledReposFunc = async ( }, }); return reposData; + } catch (error) { console.error("error in getInstalledReposFunc", error); return; diff --git a/src/utils/handleConnectGithub.ts b/src/utils/handleConnectGithub.ts index 66e6685..ddc7a06 100644 --- a/src/utils/handleConnectGithub.ts +++ b/src/utils/handleConnectGithub.ts @@ -2,17 +2,19 @@ import { z } from "zod"; export const handleGithubChangeRepos = async () => { - const url = `https://github.com/apps/devclans/installations/new`; + const url = `https://github.com/apps/repogiver/installations/new`; window.open(url, "_blank"); }; export const handleGithubConnect = () => { const clientID = z.string().parse(process.env.GH_CLIENT_ID); + console.log(clientID); const redirect_uri = z .string() .startsWith("http") .max(150) .parse(process.env.GH_REDIRECT_URI); + console.log(redirect_uri); const authUrl = `https://github.com/login/oauth/authorize?client_id=${clientID}&redirect_uri=${redirect_uri}`; // Open the URL in a new tab From 300a688075987ddc0acb11947e8798108f7c101a Mon Sep 17 00:00:00 2001 From: Satvik1769 Date: Thu, 21 Mar 2024 23:33:55 +0530 Subject: [PATCH 4/7] feat: add team member --- src/mongodb/projectRepoDetails.ts | 2 +- src/zod/zod.common.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mongodb/projectRepoDetails.ts b/src/mongodb/projectRepoDetails.ts index 05e6632..705bfa9 100644 --- a/src/mongodb/projectRepoDetails.ts +++ b/src/mongodb/projectRepoDetails.ts @@ -12,7 +12,7 @@ const projectRepoSchema = new Schema({ default: [], validate: [arrayMaxLengthValidator, "Topics array exceeds maximum length"], }, - lastCommit: { type: Date, required: true }, + lastCommit: { type: Date,default: Date.now }, readme: { type: String, validate: [textLengthValidator, "Readme text exceeds maximum length"], diff --git a/src/zod/zod.common.ts b/src/zod/zod.common.ts index 63fc6bf..72e070d 100644 --- a/src/zod/zod.common.ts +++ b/src/zod/zod.common.ts @@ -375,7 +375,7 @@ export const zodRepoDetailsSchema = z.object({ watchers: z.number().max(1000000), topics: z.array(z.string()).max(20).default([]).optional(), commits: z.number().max(10000).optional(), - lastCommit: zodDateString, + lastCommit: zodDateString.optional(), created_at: zodDateString.optional(), updated_at: zodDateString.optional(), readme: z.string().max(10000), From 7c852820af292eb68a2396c0b07bb5e2dc157c17 Mon Sep 17 00:00:00 2001 From: Satvik1769 Date: Sat, 23 Mar 2024 14:23:13 +0530 Subject: [PATCH 5/7] feat: added copy functionality --- public/copy.png | Bin 0 -> 705 bytes src/components/project/ExpandDetailsBox.tsx | 1 + src/components/project/ProjectHero.tsx | 71 ++++++++++++++++---- src/utils/copyText.ts | 13 ++++ 4 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 public/copy.png create mode 100644 src/utils/copyText.ts diff --git a/public/copy.png b/public/copy.png new file mode 100644 index 0000000000000000000000000000000000000000..11908763faa062f6d9b544fa19cda5251673d2a4 GIT binary patch literal 705 zcmV;y0zUnTP)MHxRHB*yQt^4kU^47<4NGPM}&cskX`%tbBYLX200{7y5DhL@5T^d6*>G@ zxFf>$pkUmK010wf^VB!#*r7Mh0(=1Wd1_o3w$G1?0G0=?2w5=Ze+ABsvjCP2YX|3> z;yeCve%u8ZMyKflBr1S|P5^3wWG9OZL^A*l!G!vuyMWo*%9;UYx1bq-W&rg=Gr&|3 zf^UGsCKA9gU{mr0Tn*i{r0BMNz(>gfNJ0fLIsyHt1&E&kzp}rqV?6|A zAAtHTIRoyR$N=k-1-KWwVNWeDrCZ=(q{m~f0MDdbKoTl|(Fy2BEkOJX*aVK6NB~EG zw~{m9e(0t>wOo@v0kff-&ebs^eF7w*0vMfue$)c=onu-w4a|U6c8Y3j#9NCBpRAJm8Mz=2=_)X)aYCN!kyg&rfHN6`*Pl8Vq4Y6m;^%cYzQCCvmI njP|&CnP-E2%hqrzgy;VNYv#_2u(@n|00000NkvXXu0mjfM*l3o literal 0 HcmV?d00001 diff --git a/src/components/project/ExpandDetailsBox.tsx b/src/components/project/ExpandDetailsBox.tsx index 6b7b8ed..15bfcd6 100644 --- a/src/components/project/ExpandDetailsBox.tsx +++ b/src/components/project/ExpandDetailsBox.tsx @@ -24,6 +24,7 @@ const ExpandDetailsBox = ({ }, [data]); return (
+ { setActive(!isActive); diff --git a/src/components/project/ProjectHero.tsx b/src/components/project/ProjectHero.tsx index eaa80c4..9991b31 100644 --- a/src/components/project/ProjectHero.tsx +++ b/src/components/project/ProjectHero.tsx @@ -1,3 +1,4 @@ +"use client" import { ProjectStage, ProjectIconGroup, ChipGroup } from "@/components"; import ProductImg from "@/components/project/ProjectImg"; import { urlProject } from "@/constants"; @@ -9,6 +10,17 @@ import { import { PageProps } from "@/types/page.types"; import ProjectRepoDetails from "./ProjectRepoDetails"; import { convertVideoLinkToEmbed } from "@/utils/ConvertYoutubeLinkToEmbed"; +import { useSession } from "next-auth/react"; +import handleCopy from "@/utils/copyText"; +import Image from "next/image"; +import { useState, useEffect } from "react"; +import { toast } from "react-toastify"; +interface User { + _id?: string; + name?: string | null; + email?: string | null; + image?: string | null; +} const ProjectHero = ({ skills = ["react", "nextjs", "typescript", "tailwindcss"], @@ -23,19 +35,41 @@ const ProjectHero = ({ repoDetails, video, repoName, - teamCode + teamCode, + owner, }: Omit & PageProps & { repoDetails: Partial; + }) => { const data = { title, desc, _id, teamCode, + owner, }; - console.log("This is the teamCode: ",data.teamCode); + const { data: session } = useSession(); + +// _id not found +const userDetails = (session?.user as User)?._id; + +const [isCopied, SetCopied] = useState(false); + +useEffect(() => { + if (isCopied) { + toast.success("Copied to clipboard") + const timer = setTimeout(() => { + SetCopied(false); + }, 100 * 60); + + return () => clearTimeout(timer); // This will clear the timer when the component unmounts + } +}, [isCopied]); + console.log("this is session",session); + console.log("This is going to be the user id", userDetails); + console.log("This is the owner of project:",data.owner?._id); const videoLink = convertVideoLinkToEmbed(video); return (
@@ -51,17 +85,28 @@ const ProjectHero = ({ />
-
-

{data.title}

-

{data.desc}

-

Team Code: {data.teamCode}

- {/* chips */} - -
+
+

{data.title}

+

{data.desc}

+ { data.owner?._id === userDetails && + +
+
+ +

Team Code:

+

{data.teamCode}

+ Copy{handleCopy(data.teamCode); SetCopied(true)}} /> +
+
+ + } + {/* chips */} + +
diff --git a/src/utils/copyText.ts b/src/utils/copyText.ts new file mode 100644 index 0000000..55c47a3 --- /dev/null +++ b/src/utils/copyText.ts @@ -0,0 +1,13 @@ +const handleCopy = async (textToCopy:string) => { + try { + await navigator.clipboard.writeText(textToCopy); + console.log('Text copied to clipboard'); + } catch (err) { + console.error('Failed to copy text: ', err); + } + }; + + + + +export default handleCopy; \ No newline at end of file From 71e39e742f498c1a0f9adc34265883ede1120d8a Mon Sep 17 00:00:00 2001 From: Satvik1769 Date: Fri, 29 Mar 2024 18:36:50 +0530 Subject: [PATCH 6/7] feat: leetcode details api --- src/app/api/joinTeam/route.ts | 2 + .../getUserProfile/[username]/route.ts | 105 ++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/app/api/leetcode/getUserProfile/[username]/route.ts diff --git a/src/app/api/joinTeam/route.ts b/src/app/api/joinTeam/route.ts index 7ae551d..a4cf9a2 100644 --- a/src/app/api/joinTeam/route.ts +++ b/src/app/api/joinTeam/route.ts @@ -28,6 +28,8 @@ export async function POST(req: NextRequest,res:NextResponse) { if(userId){ project.team.push(userId); await project.save(); + + return NextResponse.json({ message: 'Joined team successfully' }); } else{ diff --git a/src/app/api/leetcode/getUserProfile/[username]/route.ts b/src/app/api/leetcode/getUserProfile/[username]/route.ts new file mode 100644 index 0000000..bf3cba1 --- /dev/null +++ b/src/app/api/leetcode/getUserProfile/[username]/route.ts @@ -0,0 +1,105 @@ +import { NextRequest, NextResponse } from "next/server"; + +async function handler(req:NextRequest, { params }: { params: { username: string } }){ + try{ + const username = params.username; + const query = ` + query getUserProfile($username: String!) { + allQuestionsCount { + difficulty + count + } + matchedUser(username: $username) { + contributions { + points + } + profile { + reputation + ranking + } + submissionCalendar + submitStats { + acSubmissionNum { + difficulty + count + submissions + } + totalSubmissionNum { + difficulty + count + submissions + } + } + } + recentSubmissionList(username: $username) { + title + titleSlug + timestamp + statusDisplay + lang + __typename + } + matchedUserStats: matchedUser(username: $username) { + submitStats: submitStatsGlobal { + acSubmissionNum { + difficulty + count + submissions + __typename + } + totalSubmissionNum { + difficulty + count + submissions + __typename + } + __typename + } + } + } + `; + + const formatData = (data:any) => { + let sendData = { + totalSolved: data.data.matchedUser.submitStats.acSubmissionNum[0].count, + totalSubmissions: data.data.matchedUser.submitStats.totalSubmissionNum, + totalQuestions: data.data.allQuestionsCount[0].count, + easySolved: data.data.matchedUser.submitStats.acSubmissionNum[1].count, + totalEasy: data.data.allQuestionsCount[1].count, + mediumSolved: data.data.matchedUser.submitStats.acSubmissionNum[2].count, + totalMedium: data.data.allQuestionsCount[2].count, + hardSolved: data.data.matchedUser.submitStats.acSubmissionNum[3].count, + totalHard: data.data.allQuestionsCount[3].count, + ranking: data.data.matchedUser.profile.ranking, + contributionPoint: data.data.matchedUser.contributions.points, + reputation: data.data.matchedUser.profile.reputation, + submissionCalendar: JSON.parse(data.data.matchedUser.submissionCalendar), + recentSubmissions: data.data.recentSubmissionList, + matchedUserStats: data.data.matchedUser.submitStats + } + return sendData; + } + const response = await fetch('https://leetcode.com/graphql', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Referer': 'https://leetcode.com', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET', + 'Access-Control-Allow-Headers':'Content-Type, application/json' + }, + body: JSON.stringify({query: query, variables: {username: username}}), + }); + const data = await response.json(); + + return NextResponse.json({ data: formatData(data)}, {status: 200}); + + +} +catch(error){ + console.error('Error getting user profile:', error); + return NextResponse.json({ error: 'Error getting user profile' }, { status: 500 }); +} +} + +export { handler as GET }; \ No newline at end of file From 9d1a3f21aeef109cd34da5d63a922bf6bad2d511 Mon Sep 17 00:00:00 2001 From: Satvik1769 Date: Sun, 31 Mar 2024 17:43:38 +0530 Subject: [PATCH 7/7] feat: added leetcode stats --- src/components/FormNewUser.tsx | 7 +++ src/components/GithubGraph.tsx | 4 +- src/components/LeetCodeStats.tsx | 70 ++++++++++++++++++++++++ src/components/project/ProjectHero.tsx | 11 ++++ src/components/userPage/UserOverview.tsx | 4 +- src/lib/findLeague.ts | 53 ++++++++++++++++++ src/mongodb/models.ts | 1 + src/types/mongo/user.types.ts | 1 + src/zod/zod.common.ts | 2 + tailwind.config.ts | 2 + 10 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 src/components/LeetCodeStats.tsx create mode 100644 src/lib/findLeague.ts diff --git a/src/components/FormNewUser.tsx b/src/components/FormNewUser.tsx index ef1db91..69062d2 100644 --- a/src/components/FormNewUser.tsx +++ b/src/components/FormNewUser.tsx @@ -49,6 +49,7 @@ const FormNewUser = ({ const userid = session?._id; const onSubmit: SubmitHandler = async (data) => { try { + console.log("Clicked update button") data.contactMethodId = selectUserContactId(data) || session?.discordId; // console.log("data", JSON.stringify(data), JSON.stringify(defaultValues)); if (JSON.stringify(data) === JSON.stringify(defaultValues)) { @@ -60,6 +61,7 @@ const FormNewUser = ({ } // console.log("setting data", data); setDefaultValues(data); + console.log("Data from form is ", data); const res = await createProjectUser( `/user/${userid}/update`, data, @@ -103,6 +105,11 @@ const FormNewUser = ({ limit: 10, // min: 3, }, + { + label: "Leetcode:", + name: "leetcode", + desc: "The LeetCode Username of user.", + }, { label: "Skill Level:", name: "skillLevel", diff --git a/src/components/GithubGraph.tsx b/src/components/GithubGraph.tsx index 70108cc..b280a8e 100644 --- a/src/components/GithubGraph.tsx +++ b/src/components/GithubGraph.tsx @@ -2,8 +2,9 @@ import colors from "@/lib/colors"; import { useState } from "react"; import GitHubCalendar from "react-github-calendar"; +import LeetCodeStats from "./LeetCodeStats"; -const GitHubGraph = ({ username }: { username?: string | null }) => { +const GitHubGraph = ({ username, leetcode}: { username?: string | null , leetcode?:string | null }) => { const currentYear = new Date().getFullYear(); const [year, setYear] = useState(currentYear); const removeHashTag = (str: string) => str?.replace("#", "") || "E2E8FF8C"; @@ -45,6 +46,7 @@ const GitHubGraph = ({ username }: { username?: string | null }) => { )}&point=${removeHashTag(colors.text)}&hide_border=true`} alt={`${username}'s github activity graph`} /> + ); }; diff --git a/src/components/LeetCodeStats.tsx b/src/components/LeetCodeStats.tsx new file mode 100644 index 0000000..f08176f --- /dev/null +++ b/src/components/LeetCodeStats.tsx @@ -0,0 +1,70 @@ +import { Fetch } from "@/utils/fetchApi"; +import React from "react"; +import { findLeague } from "@/lib/findLeague"; +function FractionCircle({ fraction, children }:{fraction:number,children:React.ReactNode}) { + // Calculate clip-path values based on fraction + const clipPathBlue = `polygon(0 0, 100% 0, 100% ${fraction * 100}%, 0% ${fraction * 100}%)`; + const clipPathRed = `polygon(0 ${fraction * 100}%, 100% ${fraction * 100}%, 100% 100%, 0% 100%)`; + + return ( +
+
+
+
+
+ {children} +
+
+
+ ); +} + +const LeetCodeStats = async ({username}:{username:string})=>{ + const data = await Fetch({endpoint:`/leetcode/getUserProfile/${username}`}); + console.log(data) + const league = findLeague({ranking:data.data.ranking}); + console.log(league); + + + return ( + +
+

{username} LeetCode Stats

+
+
+
+

Total Problems Solved:

+

{data.data.totalSolved}

+
+
+

Easy Problems Solved:

+

{data.data.easySolved}

+
+
+

Medium Problems Solved:

+

{data.data.mediumSolved}

+
+
+

Hard Problems Solved:

+

{data.data.hardSolved}

+
+
+

Current Ranking:

+

{data.data.ranking}

+
+
+
+ +

{league?.grade}

+
+ +
+ + +
+
+ + ) +} + +export default LeetCodeStats; \ No newline at end of file diff --git a/src/components/project/ProjectHero.tsx b/src/components/project/ProjectHero.tsx index 9991b31..491953c 100644 --- a/src/components/project/ProjectHero.tsx +++ b/src/components/project/ProjectHero.tsx @@ -15,6 +15,7 @@ import handleCopy from "@/utils/copyText"; import Image from "next/image"; import { useState, useEffect } from "react"; import { toast } from "react-toastify"; +import { redirect } from "next/dist/server/api-utils"; interface User { _id?: string; name?: string | null; @@ -89,6 +90,7 @@ useEffect(() => {

{data.title}

{data.desc}

{ data.owner?._id === userDetails && + data.teamCode ?(
@@ -98,6 +100,15 @@ useEffect(() => { Copy{handleCopy(data.teamCode); SetCopied(true)}} />
+ ): + + ( +
+

Generate Team Code

+

You can generate a team code to share with your team members.

+ +
+ ) } {/* chips */} diff --git a/src/components/userPage/UserOverview.tsx b/src/components/userPage/UserOverview.tsx index dcfd48f..0ab99ca 100644 --- a/src/components/userPage/UserOverview.tsx +++ b/src/components/userPage/UserOverview.tsx @@ -14,11 +14,13 @@ const UserOverview = async ({ username, githubDetails, _id, + leetcode, }: { data: ProjectDetailsItemProps[]; username: string; _id: string; githubDetails?: UserProps["githubDetails"]; + leetcode:string; }) => { const session: any = await getServerSessionForServer(); const { readme, login } = githubDetails || {}; @@ -44,7 +46,7 @@ const UserOverview = async ({ href={login as string} /> - + {typeof readme == "string" && typeof readmePurified == "string" && (
{ + if (ranking >= 2500000) { + return { + grade: "C", + fraction: 1 / 7 + } + } + else if(ranking<2500000 && ranking>=1000000){ + return { + grade: "B", + fraction: 2 / 7 + } + + } + else if(ranking<1000000 && ranking>=500000){ + return { + grade: "B+", + fraction: 3 / 7 + } + + } + else if(ranking<500000 && ranking>=100000){ + return { + grade: "A", + fraction: 4 / 7 + } + + } + else if(ranking<100000 && ranking>=10000){ + return { + grade: "A+", + fraction: 5 / 7 + } + + } + else if(ranking<10000 && ranking>=1000){ + return { + grade: "S", + fraction: 6 / 7 + } + + } + else if(ranking<1000){ + return { + grade: "S+", + fraction: 7 / 7 + } + + } + } + + + \ No newline at end of file diff --git a/src/mongodb/models.ts b/src/mongodb/models.ts index 9add8f7..21ba21b 100644 --- a/src/mongodb/models.ts +++ b/src/mongodb/models.ts @@ -20,6 +20,7 @@ const userSchema = new mongoose.Schema( githubDetails: { type: userGithubDetailsSchema, }, + leetcode:{ type:String , unique:true,required:false }, domain: { type: String, enum: projectDomains, diff --git a/src/types/mongo/user.types.ts b/src/types/mongo/user.types.ts index b8586f7..3f5a338 100644 --- a/src/types/mongo/user.types.ts +++ b/src/types/mongo/user.types.ts @@ -27,6 +27,7 @@ export type UserProps = UserTeamItemProps & linkedin: string; website: string; }; + leetcode:string; ownedProjects: mongoose.Types.ObjectId[]; contributedProjects: mongoose.Types.ObjectId[]; questions: UserQuestionsProps; diff --git a/src/zod/zod.common.ts b/src/zod/zod.common.ts index 72e070d..c5d61af 100644 --- a/src/zod/zod.common.ts +++ b/src/zod/zod.common.ts @@ -224,6 +224,7 @@ export const zodUserSearchInfoSchema = z.object({ }) .optional() .nullable(), + leetcode: z.string().optional(), githubId: z.string().max(50).optional(), bio: stringSchema.min(10).max(100), username: zodDiscordUsername, @@ -310,6 +311,7 @@ export const zodUserFormSchemaObj = z.object({ .optional(), skills: skillsSchema.min(3), bio: stringSchema.min(10).max(100), + leetcode: z.string().optional(), ...zodUserDataCommonSchema.shape, }); export const zodUserFormSuperRefine = (value: any, context: any) => { diff --git a/tailwind.config.ts b/tailwind.config.ts index 2418aa9..55cf0a0 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -15,6 +15,8 @@ export const tailwindColors = { cardBg1: "#020D23", green: "#2da519", red: "#D64343", + blueRing:"#d5e5fb", + card:"#081121", }; export default withUt({