Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added Bounty dashboard + Page to Link github account #686

Closed
wants to merge 11 commits into from
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,8 @@ CACHE_EXPIRE_S = 10

ADMINS = "Random,[email protected]"
NEXT_PUBLIC_DISABLE_FEATURES = "featurea,featureb,featurec"
EXCHANGE_RATE_SECRET="" # https://www.exchangerate-api.com/
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
GITHUB_PAT=""
HASHING_SECRET_KEY=
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
},
"dependencies": {
"@auth/prisma-adapter": "^1.0.6",
"nextjs-toploader": "^1.6.11",
"@discordjs/core": "^1.1.1",
"@discordjs/next": "^0.1.1-dev.1673526225-a580768.0",
"@icons-pack/react-simple-icons": "^9.4.0",
Expand All @@ -36,16 +35,20 @@
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@types/bcrypt": "^5.0.2",
"@types/crypto-js": "^4.2.2",
"@types/jsonwebtoken": "^9.0.5",
"@uiw/react-md-editor": "^4.0.4",
"axios": "^1.6.2",
"bcrypt": "^5.1.1",
"canvas": "^2.11.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"discord-oauth2": "^2.11.0",
"discord.js": "^14.14.1",
Expand All @@ -57,6 +60,7 @@
"next": "14.0.2",
"next-auth": "^4.24.5",
"next-themes": "^0.2.1",
"nextjs-toploader": "^1.6.11",
"node-fetch": "^3.3.2",
"notion-client": "^6.16.0",
"pdf-lib": "^1.17.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
-- CreateTable
CREATE TABLE "GithubUser" (
"id" SERIAL NOT NULL,
"userId" TEXT NOT NULL,
"username" TEXT NOT NULL,
"email" TEXT,
"image" TEXT,
"publicName" TEXT,
"isLinked" BOOLEAN NOT NULL DEFAULT false,

CONSTRAINT "GithubUser_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "BountyInfo" (
"id" SERIAL NOT NULL,
"username" TEXT NOT NULL,
"PR_Link" TEXT NOT NULL,
"PR_Title" TEXT NOT NULL,
"repoName" TEXT NOT NULL,
"USD_amount" INTEGER NOT NULL,
"INR_amount" INTEGER NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"githubUserId" TEXT,

CONSTRAINT "BountyInfo_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "GithubUser_userId_key" ON "GithubUser"("userId");

-- CreateIndex
CREATE UNIQUE INDEX "GithubUser_username_key" ON "GithubUser"("username");

-- CreateIndex
CREATE UNIQUE INDEX "GithubUser_email_key" ON "GithubUser"("email");

-- CreateIndex
CREATE UNIQUE INDEX "BountyInfo_PR_Link_key" ON "BountyInfo"("PR_Link");

-- AddForeignKey
ALTER TABLE "GithubUser" ADD CONSTRAINT "GithubUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "BountyInfo" ADD CONSTRAINT "BountyInfo_githubUserId_fkey" FOREIGN KEY ("githubUserId") REFERENCES "GithubUser"("userId") ON DELETE SET NULL ON UPDATE CASCADE;
128 changes: 78 additions & 50 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ model Course {
content CourseContent[]
purchasedBy UserPurchases[]
certificate Certificate[]
certIssued Boolean @default(false)
certIssued Boolean @default(false)
}

model UserPurchases {
Expand Down Expand Up @@ -64,12 +64,12 @@ model CourseContent {
}

model Certificate {
id String @id @default(cuid())
slug String @default("certId")
user User @relation(fields: [userId], references: [id])
userId String
course Course @relation(fields: [courseId], references: [id])
courseId Int
id String @id @default(cuid())
slug String @default("certId")
user User @relation(fields: [userId], references: [id])
userId String
course Course @relation(fields: [courseId], references: [id])
courseId Int

@@unique([userId, courseId])
}
Expand Down Expand Up @@ -147,6 +147,7 @@ model User {
questions Question[]
answers Answer[]
certificate Certificate[]
githubUser GithubUser?
}

model DiscordConnect {
Expand Down Expand Up @@ -206,75 +207,102 @@ model Comment {
votes Vote[]
isPinned Boolean @default(false)
}

model Question {
id Int @id @default(autoincrement())
title String
content String
slug String @unique
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
authorId String
upvotes Int @default(0)
downvotes Int @default(0)
id Int @id @default(autoincrement())
title String
content String
slug String @unique
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
authorId String
upvotes Int @default(0)
downvotes Int @default(0)
totalanswers Int @default(0)
answers Answer[]
votes Vote[]
tags String[]
updatedAt DateTime @updatedAt
answers Answer[]
votes Vote[]
tags String[]
updatedAt DateTime @updatedAt

@@index([authorId])
}

model Answer {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
question Question @relation(fields: [questionId], references: [id])
questionId Int
author User @relation(fields: [authorId], references: [id])
authorId String
votes Vote[]
upvotes Int @default(0)
downvotes Int @default(0)
totalanswers Int @default(0)
parentId Int?
responses Answer[] @relation("AnswerToAnswer")
parent Answer? @relation("AnswerToAnswer", fields: [parentId], references: [id])
updatedAt DateTime @updatedAt
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
question Question @relation(fields: [questionId], references: [id])
questionId Int
author User @relation(fields: [authorId], references: [id])
authorId String
votes Vote[]
upvotes Int @default(0)
downvotes Int @default(0)
totalanswers Int @default(0)
parentId Int?
responses Answer[] @relation("AnswerToAnswer")
parent Answer? @relation("AnswerToAnswer", fields: [parentId], references: [id])
updatedAt DateTime @updatedAt

@@index([questionId])
@@index([authorId])
@@index([parentId])
}


model Vote {
id Int @id @default(autoincrement())
questionId Int?
question Question? @relation(fields: [questionId], references: [id])
answerId Int?
answer Answer? @relation(fields: [answerId], references: [id])
commentId Int?
comment Comment? @relation(fields: [commentId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
voteType VoteType
createdAt DateTime @default(now())
id Int @id @default(autoincrement())
questionId Int?
question Question? @relation(fields: [questionId], references: [id])
answerId Int?
answer Answer? @relation(fields: [answerId], references: [id])
commentId Int?
comment Comment? @relation(fields: [commentId], references: [id])
userId String
user User @relation(fields: [userId], references: [id])
voteType VoteType
createdAt DateTime @default(now())

@@unique([questionId, userId])
@@unique([answerId, userId])
@@unique([commentId, userId])
@@unique([questionId, userId])
@@unique([answerId, userId])
@@unique([commentId, userId])
}

enum VoteType {
UPVOTE
DOWNVOTE
}

enum PostType {
QUESTION
ANSWER
}

enum CommentType {
INTRO
DEFAULT
}

model GithubUser {
id Int @id @default(autoincrement())
userId String @unique
username String @unique
email String? @unique
image String?
publicName String?
isLinked Boolean @default(false)
user User? @relation(fields: [userId], references: [id])
bountyInfo BountyInfo[]
}

model BountyInfo {
mvp5464 marked this conversation as resolved.
Show resolved Hide resolved
id Int @id @default(autoincrement())
username String
PR_Link String @unique
PR_Title String
repoName String
USD_amount Int
INR_amount Int
createdAt DateTime @default(now())
githubUser GithubUser? @relation(fields: [githubUserId], references: [userId])
githubUserId String?
}
95 changes: 95 additions & 0 deletions src/app/api/github-webhook/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* To get bounty comment info from bounty-webhook after verification
* Add the info to DB and then send verification comment to Github PR
*/

import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto-js';
import db from '@/db';
import {
formatINR,
formatUSD,
getCurrencyRate,
sendBountyComment,
} from '@/utiles/bounty';

export async function POST(req: NextRequest) {
mvp5464 marked this conversation as resolved.
Show resolved Hide resolved
const body = await req.json();
const receivedHash = req.headers.get('X-Hash');

const USD_amount = +body.bountyAmount.split('$')[1];
const PR_By = body.author;
const PR_Link = body.pr_link;
const repo_owner = body.repo_owner;
const repoName = PR_Link.split('/')[4];
const PR_No: string = PR_Link.split('/')[6];
const username = body.username;
const PR_Title = body.PR_Title;
const SECRET_KEY = process.env.HASHING_SECRET_KEY || '';
const SALT = 'mySalt';
const payload = JSON.stringify(body);
const expectedHash = crypto.HmacSHA256(SALT + payload, SECRET_KEY).toString();

if (receivedHash !== expectedHash) {
return NextResponse.json({ message: 'Invalid hash' }, { status: 401 });
}

try {
const INR_amount = await getCurrencyRate(USD_amount);
const commentBody = `💰Congratulation @${PR_By} for winning ${formatUSD.format(USD_amount).split('.')[0]} (${formatINR.format(INR_amount).split('.')[0]}) bounty.\n👉 To claim visit https://app.100xdevs.com/bounty.\n🐥Keep contributing.`;

const findBountyInfo = await db.bountyInfo.findUnique({
where: { PR_Link },
});

if (findBountyInfo) {
const commentBody = `[Duplicate]\n💰Bounty worth ${formatUSD.format(findBountyInfo.USD_amount).split('.')[0]} (${formatINR.format(findBountyInfo.INR_amount).split('.')[0]}) is already created to @${PR_By} for this PR.\n👉 To claim visit https://app.100xdevs.com/bounty.\n🐥Keep contributing.`;
sendBountyComment({ repo_owner, repoName, PR_No, commentBody });
return NextResponse.json(
{ message: 'duplicate bounty message' },
{ status: 401 },
);
}

const findUser = await db.githubUser.findUnique({
where: { username },
});

if (findUser) {
const addBountyInfo = await db.bountyInfo.create({
data: {
username,
PR_Title,
PR_Link,
repoName,
USD_amount,
INR_amount,
githubUserId: findUser.userId,
},
});
if (addBountyInfo) {
sendBountyComment({ repo_owner, repoName, PR_No, commentBody });
}
} else {
const addBountyInfo = await db.bountyInfo.create({
data: {
username,
PR_Title,
PR_Link,
repoName,
USD_amount,
INR_amount,
},
});
if (addBountyInfo) {
sendBountyComment({ repo_owner, repoName, PR_No, commentBody });
}
}
return NextResponse.json({ message: 'success' }, { status: 200 });
} catch (e) {
return NextResponse.json(
{ message: 'Error while updating database' },
{ status: 401 },
);
}
}
Loading