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/.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/public/copy.png b/public/copy.png new file mode 100644 index 0000000..1190876 Binary files /dev/null and b/public/copy.png differ 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/api/joinTeam/route.ts b/src/app/api/joinTeam/route.ts new file mode 100644 index 0000000..7ae551d --- /dev/null +++ b/src/app/api/joinTeam/route.ts @@ -0,0 +1,42 @@ +// app/api/join-team/route.ts +import { NextRequest, NextResponse } from 'next/server'; +import dbConnect from '@/lib/dbConnect'; +import { ProjectModel } from '@/mongodb/models'; +import { getServerSession } from 'next-auth'; +import { authOptions } from '@/utils/auth/auth'; +export async function POST(req: NextRequest,res:NextResponse) { + const session: any = await getServerSession(authOptions); + console.log(session); + const userId = session.user._id; + const { teamCode } = await req.json(); + try { + + + 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 }); + } + + + console.log(userId); + + // Add the user's ID to the contributors array + if(userId){ + project.team.push(userId); + await project.save(); + return NextResponse.json({ message: 'Joined team successfully' }); + } + else{ + return NextResponse.json({ error: 'User not found' }, { status: 404 }); + } + + + } 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/api/project/route.ts b/src/app/api/project/route.ts index 80ba519..3703f5e 100644 --- a/src/app/api/project/route.ts +++ b/src/app/api/project/route.ts @@ -1,6 +1,6 @@ import { getDataQuery } from "@/utils/getDataQuery"; -async function handler(req: Request) { +async function handler(req: Request):Promise{ return getDataQuery(req.url); } diff --git a/src/app/api/uploadthing/core.ts b/src/app/api/uploadthing/core.ts index 0cfd73c..c43104a 100644 --- a/src/app/api/uploadthing/core.ts +++ b/src/app/api/uploadthing/core.ts @@ -15,6 +15,19 @@ export const ourFileRouter = { console.log("file url", file.url); return { uploadedBy: metadata.userId }; }), + + // Add the resumeUploader endpoint + resumeUploader: f({ pdf: { maxFileSize: "2MB", maxFileCount: 1 } }) + .middleware(async ({ req }) => { + const user = await auth(req); + if (!user) throw new Error("Unauthorized"); + return { userId: user.id }; + }) + .onUploadComplete(async ({ metadata, file }) => { + console.log("Resume upload complete for userId:", metadata.userId); + console.log("Resume file url", file.url); + return { uploadedBy: metadata.userId }; + }), } satisfies FileRouter; -export type OurFileRouter = typeof ourFileRouter; +export type OurFileRouter = typeof ourFileRouter; \ No newline at end of file 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/app/join-team/page.tsx b/src/app/join-team/page.tsx new file mode 100644 index 0000000..96ae627 --- /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/joinTeam', { + 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/app/project/[id]/page.tsx b/src/app/project/[id]/page.tsx index 680da51..4df9104 100644 --- a/src/app/project/[id]/page.tsx +++ b/src/app/project/[id]/page.tsx @@ -36,6 +36,7 @@ export async function generateMetadata({ console.error("Project not found in open graph image"); return {}; } + console.log("This us Data:",data) const { title, desc, imgs, skills, repoDetails, domain } = data; const img = Array.isArray(imgs) && imgs.length > 0 diff --git a/src/components/AccountMenu.tsx b/src/components/AccountMenu.tsx index 51076ea..9c480db 100644 --- a/src/components/AccountMenu.tsx +++ b/src/components/AccountMenu.tsx @@ -83,6 +83,10 @@ export default function AccountMenu({ title: "Edit Profile", link: editProfile(userState?._id as string), }, + { + title:"Join Team", + link:"/join-team" + } ]; const handleClose = () => { setAnchorEl(null); diff --git a/src/components/FormNewProject.tsx b/src/components/FormNewProject.tsx index a45bc0c..e5cffff 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,14 @@ const FormNewProject = ({ resolver: zodResolver(zodProjectFormSchema), }); console.log("defaultValues", watch("repoName")); + + const handleGenerateTeamCode = () => { + const code = generateTeamCode(); + console.log("This is the Team code",code); + setTeamCode(code); + setValue('teamCode', code); + }; + const onSubmit: SubmitHandler = async (data) => { try { // const a = zodProjectFormSchema.parse(data); @@ -51,6 +61,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 +73,7 @@ const FormNewProject = ({ : "/db/createProject"; const res = await createProjectUser( url, - data, + formData, session, setError, projectId @@ -126,13 +140,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); } } @@ -189,6 +206,21 @@ const FormNewProject = ({ href={projectId && `/project/${projectId}`} /> )} +
+ + {teamCode && ( +
+

Team Code: {teamCode}

+
+ )} +
} buttonMessage={projectId ? "Update Project" : "Create Project"} @@ -199,4 +231,4 @@ const FormNewProject = ({ ); }; -export default FormNewProject; +export default FormNewProject; \ No newline at end of file diff --git a/src/components/FormNewUser.tsx b/src/components/FormNewUser.tsx index ef1db91..9d06e25 100644 --- a/src/components/FormNewUser.tsx +++ b/src/components/FormNewUser.tsx @@ -18,6 +18,9 @@ import LogedOutScreen from "./LogedOutScreen"; import { toast } from "react-toastify"; import { memberLevels } from "@/lib/memberLevel"; import { handleGithubConnect } from "@/utils/handleConnectGithub"; +import { UploadDropzone } from "@/utils/uploadthing"; +import ResumeUpload from "./ResumeUpload"; +import { pageTheme } from "@/lib/pageTheme"; const FormNewUser = ({ defaultValues: dv, @@ -33,6 +36,7 @@ const FormNewUser = ({ const [defaultValues, setDefaultValues] = useState( (dv as unknown as UserProps) || {} ); + const [resumeUrl, setResumeUrl] = useState(""); // console.log("defaultValues", defaultValues); // const defaultValues: UserProps | UserFormProps = // (dv as unknown as UserProps) || {}; @@ -50,15 +54,15 @@ const FormNewUser = ({ const onSubmit: SubmitHandler = async (data) => { try { data.contactMethodId = selectUserContactId(data) || session?.discordId; - // console.log("data", JSON.stringify(data), JSON.stringify(defaultValues)); + data.resume = resumeUrl; // Add the resume URL to the data object + if (JSON.stringify(data) === JSON.stringify(defaultValues)) { toast.success("Project Updated Successfully!", { autoClose: false, }); - // console.log("No change in data"); return; } - // console.log("setting data", data); + setDefaultValues(data); const res = await createProjectUser( `/user/${userid}/update`, @@ -67,10 +71,6 @@ const FormNewUser = ({ setError, "Profile Updated Successfully" ); - // console.log("res", res, session); - // if (!res && session) { - // throw new Error("Error in our server. Please try again later."); - // } return data; } catch (error: any) { console.error("error in onSubmit of FormNewUser", error); @@ -103,6 +103,12 @@ const FormNewUser = ({ limit: 10, // min: 3, }, + { + label: "Page Theme:", + name: "pageTheme", + desc: "Select the Theme that you want your Developer Page to have.", + options: pageTheme as any, + }, { label: "Skill Level:", name: "skillLevel", @@ -183,6 +189,13 @@ const FormNewUser = ({ limit: 3, // min: 1, }, + // { + // label: "Upload Resume:", + // name: "resume", + // type: "file", + // accept: "application/pdf", + // desc: "Upload your resume in PDF format.", + // }, ]; // Type assertion @@ -243,6 +256,13 @@ const FormNewUser = ({ heading="Lets Create Your Profile" setValue={setValue} buttons={ +
+ {/* */} + setResumeUrl(resumeUrl)} + /> +
} {...form} zodFormShape={userFormShape} diff --git a/src/components/ResumeUpload.tsx b/src/components/ResumeUpload.tsx new file mode 100644 index 0000000..2c8e81d --- /dev/null +++ b/src/components/ResumeUpload.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { UploadDropzone } from "@/utils/uploadthing"; +import { useState } from "react"; + +const ResumeUpload = ({ + setValue, + defaultResumeUrl, + onResumeUpload, // Add this prop + }: { + setValue: any; + defaultResumeUrl?: string; + onResumeUpload: (resumeUrl: string) => void; // Add this type + }) => { + const [resumeUrl, setResumeUrl] = useState(defaultResumeUrl || ""); + + const handleUploadComplete = (res: any) => { + const uploadedResumeUrl = res[0].url; + setResumeUrl(uploadedResumeUrl); + setValue("resume", uploadedResumeUrl); + onResumeUpload(uploadedResumeUrl); // Call the callback function with the uploaded resume URL + }; + + return ( +
+
+

Upload Resume

+

Upload your resume in PDF format.

+
+ { + if (files.length > 1) { + alert("You can only upload one resume file"); + return files.slice(0, 1); + } + return files; + }} + endpoint="resumeUploader" + onClientUploadComplete={handleUploadComplete} + onUploadError={(error: Error) => { + alert(`ERROR! ${error.message}`); + }} + /> + {resumeUrl && ( +
+

Uploaded Resume: {resumeUrl}

+
+ )} +
+ ); +}; + +export default ResumeUpload; \ No newline at end of file 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 2c3a32e..cf4c807 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,15 +35,41 @@ const ProjectHero = ({ repoDetails, video, repoName, + teamCode, + owner, }: Omit & PageProps & { repoDetails: Partial; + }) => { const data = { title, desc, _id, + teamCode, + owner, + }; + 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 (
@@ -47,16 +85,28 @@ const ProjectHero = ({ />
-
-

{data.title}

-

{data.desc}

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

{data.title}

+

{data.desc}

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

Team Code:

+

{data.teamCode}

+ Copy{handleCopy(data.teamCode || ""); SetCopied(true)}} /> +
+
+ + } + {/* chips */} + +
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..ac878e6 100644 --- a/src/github/config.github.ts +++ b/src/github/config.github.ts @@ -1,5 +1,26 @@ 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 + } +} export const getOctokit = async ({ installationId, @@ -11,16 +32,17 @@ 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 = convertPemToBase64(filePath); 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"); } @@ -28,11 +50,13 @@ export const getOctokit = async ({ appId, privateKey: stringKey, }); + const api = await app.getInstallationOctokit(installationId); 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,10 +64,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 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/lib/pageTheme.tsx b/src/lib/pageTheme.tsx new file mode 100644 index 0000000..1f853ef --- /dev/null +++ b/src/lib/pageTheme.tsx @@ -0,0 +1,11 @@ +export const pageThemeMap = { + dark: true, + light: true, + vibrant: true, + odysessy: true, + }; + + +export const pageTheme = [...Object.keys(pageThemeMap), ""] as const; + +export type PageThemeType = keyof typeof pageThemeMap | null; \ No newline at end of file diff --git a/src/mongodb/models.ts b/src/mongodb/models.ts index f48c5d4..f69e180 100644 --- a/src/mongodb/models.ts +++ b/src/mongodb/models.ts @@ -11,11 +11,13 @@ import { discordDetailsSchema } from "./discordModel"; import { userGithubDetailsSchema } from "./githubModal"; import projectRepoSchema from "./projectRepoDetails"; import { projectDomains } from "@/lib/domains"; +import { pageTheme } from "@/lib/pageTheme"; const userSchema = new mongoose.Schema( { discordId: { type: String, required: true }, skillLevel: { type: String, enum: memberLevels, default: "beginner" }, + pageTheme: { type: String, enum: pageTheme, default: "light" }, githubId: { type: String }, githubDetails: { type: userGithubDetailsSchema, @@ -101,6 +103,7 @@ const userSchema = new mongoose.Schema( type: String, }, ], + resume: { type: String, default: "" }, }, { timestamps: true } ); @@ -136,6 +139,7 @@ const projectSchema = new mongoose.Schema( topics: [{ type: String, default: [] }], // ml, android skills: [{ type: String, default: [] }], // tech: html, css repoName: { type: String, default: "", unique: true }, + teamCode: { type: String, required: true, unique: true }, likesCount: { type: Number, default: 0 }, bookmarkCount: { type: Number, default: 0 }, projectLinks: [{ type: String, default: [] }], diff --git a/src/mongodb/projectRepoDetails.ts b/src/mongodb/projectRepoDetails.ts index 9146c90..705bfa9 100644 --- a/src/mongodb/projectRepoDetails.ts +++ b/src/mongodb/projectRepoDetails.ts @@ -12,8 +12,7 @@ const projectRepoSchema = new Schema({ default: [], validate: [arrayMaxLengthValidator, "Topics array exceeds maximum length"], }, - commits: { type: Number, required: true, min: 0 }, - 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/types/form.types.tsx b/src/types/form.types.tsx index 8209e42..8249813 100644 --- a/src/types/form.types.tsx +++ b/src/types/form.types.tsx @@ -11,6 +11,8 @@ export type InputFieldProps = { required?: boolean; min?: number; preText?: string; + readOnly?: boolean; + accept?: string; }; export type FormClientProps = { zodSchema: any; diff --git a/src/types/mongo/project.types.ts b/src/types/mongo/project.types.ts index 6b285cc..c7502e0 100644 --- a/src/types/mongo/project.types.ts +++ b/src/types/mongo/project.types.ts @@ -29,6 +29,7 @@ export type ProjectProps = ProjectTeamProps & { desc: string; domain: ProjectDomainType[]; owner: mongoose.Types.ObjectId | Partial; + teamCode: string | null; contributors: mongoose.Types.ObjectId[] | Partial[]; topics: string[]; skills: string[]; diff --git a/src/types/mongo/user.types.ts b/src/types/mongo/user.types.ts index b8586f7..9129dbc 100644 --- a/src/types/mongo/user.types.ts +++ b/src/types/mongo/user.types.ts @@ -10,11 +10,13 @@ import { import mongoose, { Types } from "mongoose"; import { z } from "zod"; import { ProjectProps } from "./project.types"; +import { PageThemeType } from "@/lib/pageTheme"; // Define the User interface extending mongoose.Document export type UserProps = UserTeamItemProps & UserSearchInfoProps & { isMember?: boolean; + pageTheme?: PageThemeType; skillLevel?: MemberLevelType; domain?: ProjectDomainType; // domain you are currenty studying bio?: string; @@ -33,6 +35,7 @@ export type UserProps = UserTeamItemProps & createdAt: Date; updatedAt: Date; repos: string[]; + resume?: string; }; export type UserQuestionsProps = { currentCompany?: string; 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 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/getDataQuery.tsx b/src/utils/getDataQuery.tsx index 26e7ed1..7d16064 100644 --- a/src/utils/getDataQuery.tsx +++ b/src/utils/getDataQuery.tsx @@ -17,6 +17,7 @@ export async function getDataQuery( return NextResponse.json({ error: "no request" }); } const isProject = type === "projects"; + console.log("type",isProject); const Enum = isProject ? ProjectRedisKeys : UserRedisKeys; type Props = typeof isProject extends true ? ProjectProps : UserProps; try { diff --git a/src/utils/getInstalledReposFunc.ts b/src/utils/getInstalledReposFunc.ts index 35507b9..36c48a5 100644 --- a/src/utils/getInstalledReposFunc.ts +++ b/src/utils/getInstalledReposFunc.ts @@ -8,7 +8,7 @@ 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"); @@ -16,7 +16,7 @@ export const getInstallationId = async (userId?: string) => { installId = parseInt(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"); @@ -27,10 +27,10 @@ export const getInstallationId = async (userId?: string) => { decrypt(id?.githubDetails?.installId?.toString() || "") ); 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); @@ -61,11 +61,12 @@ export const getInstalledReposFunc = async ( ); 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", @@ -99,6 +100,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..1c4026d 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/repogiver/installations/new`; window.open(url, "_blank"); }; diff --git a/src/zod/zod.common.ts b/src/zod/zod.common.ts index 72b41db..c1c4b68 100644 --- a/src/zod/zod.common.ts +++ b/src/zod/zod.common.ts @@ -5,6 +5,7 @@ import { memberLevels } from "@/lib/memberLevel"; import { devStages } from "@/lib/devStages"; import { contactMethods } from "@/lib/contactMethods"; import { projectDomains } from "@/lib/domains"; +import { pageTheme } from "@/lib/pageTheme"; export const zodGithubInstallationId = z.union([ z @@ -204,6 +205,10 @@ export const zodUserDiscordDetailsSchema = z.object({ // Add other properties and validations as needed }); export const zodUserSearchInfoSchema = z.object({ + pageTheme: z + .enum(pageTheme as any) + .nullable() + .optional(), skillLevel: z .enum(memberLevels as any) .nullable() @@ -304,12 +309,17 @@ export const zodUserDataSchema = z.object({ ...zodUserDataCommonSchema.shape, }); export const zodUserFormSchemaObj = z.object({ + pageTheme: z + .enum(pageTheme as any) + .nullable() + .optional(), skillLevel: z .enum(memberLevels as any) .nullable() .optional(), skills: skillsSchema.min(3), bio: stringSchema.min(10).max(100), + resume: z.string().optional(), ...zodUserDataCommonSchema.shape, }); export const zodUserFormSuperRefine = (value: any, context: any) => { @@ -359,6 +369,7 @@ export const zodProjectSearchInfoSchema = z.object({ desc: z.string().min(10).max(200), skills: skillsSchema, team: zodTeamContactSchema.partial().array().optional(), + teamCode:z.string(), skillLevel: z .enum(memberLevels as any) .nullable() @@ -374,7 +385,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), @@ -475,11 +486,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()