added prisma db and actions and github workflow#5
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughAdds Prisma schema, migration, and client singleton; new server actions for user/org/repo management, GitHub webhook registration, and a reward flow; API endpoint for GitHub webhooks; Octokit dependency; .gitignore entry for generated Prisma client. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor GitHub as GitHub
participant API as /api/webhooks/github
participant Reward as rewardContributorForIssue
participant Prisma as PrismaClient
note right of API #D8F1E8: webhook POST (pull_request)
GitHub->>API: POST webhook (pull_request merged)
API->>API: parse event & extract PR author + closing issue#
alt author & issue found
API->>Reward: rewardContributorForIssue(issueId, username)
Reward->>Prisma: fetch issue & user, validate state
Reward->>Reward: sendSplTokenReward(recipient, amount, issueNumber)
Reward->>Prisma: transaction — create Contribution, update Issue.status = REWARDED
Reward-->>API: { success } or { error }
API-->>GitHub: 200 OK (or 400 on known error)
else missing data or not merged
API-->>GitHub: 200 OK (no action)
end
sequenceDiagram
autonumber
actor Admin as Org Admin
participant Action as registerRepo (actions/userProfile)
participant Octokit as createGithubWebhook
participant GitHub as GitHub
note right of Action #FFF4D9: create repo record, then webhook
Admin->>Action: registerRepo(data, repoOwner, userToken)
Action->>Prisma: insert repository record
Action->>Octokit: createGithubWebhook(repoOwner, repoName, userToken)
Octokit->>GitHub: create webhook (pull_request events)
GitHub-->>Octokit: webhook created
Octokit-->>Action: { success } or { error }
Action-->>Admin: { repo } or { error }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (5)
actions/createGithubWebhook.js (2)
19-29: Consider checking for existing webhooks to prevent duplicates.If
registerRepois called multiple times for the same repository, this will create duplicate webhooks, which could lead to duplicate reward processing and race conditions.Add a check before creating the webhook:
try { + // Check if webhook already exists + const { data: hooks } = await octokit.rest.repos.listWebhooks({ + owner: repoOwner, + repo: repoName, + }); + + const existingHook = hooks.find(hook => + hook.config.url === webhookUrl && + hook.events.includes('pull_request') + ); + + if (existingHook) { + console.log(`Webhook already exists for ${repoOwner}/${repoName}`); + return { success: true }; + } + await octokit.rest.repos.createWebhook({
32-34: Return error details for better debugging.The catch block logs the error but returns a generic message, making it difficult to diagnose webhook creation failures in production.
Include error details in the response:
} catch (error) { console.error("Failed to create webhook:", error); - return { error: "Could not create the GitHub webhook." }; + return { error: `Could not create the GitHub webhook: ${error.message}` }; }lib/prisma.js (1)
4-4: Minor formatting inconsistencies.Missing spaces around operators reduce readability.
Apply consistent spacing:
-export const prisma= globalThis.prisma || new PrismaClient() +export const prisma = globalThis.prisma || new PrismaClient() -if (process.env.NODE_ENV!=="production"){ - globalThis.prisma=prisma +if (process.env.NODE_ENV !== "production") { + globalThis.prisma = prisma }Also applies to: 6-6
api/webhooks/github.js (1)
26-43: Add input validation for webhook payload.The handler assumes the payload structure is valid but doesn't validate required fields, which could cause runtime errors.
Add validation before processing:
if (event === 'pull_request' && payload.action === 'closed' && payload.pull_request?.merged) { const pr = payload.pull_request; + + if (!pr.user?.login) { + console.error('PR payload missing user.login'); + return res.status(400).json({ message: 'Invalid payload structure' }); + } + + if (!pr.body) { + console.log('PR has no body, skipping issue resolution check'); + return res.status(200).json({ message: 'PR merged but no issue reference found' }); + } const githubUsername = pr.user.login;prisma/migrations/20251004172052_init/migration.sql (1)
10-10: Consider making email non-nullable.If users are created via Clerk (as suggested by the
upsertUserfunction), email should always be available. Making it nullable could complicate user lookups and validations.If email is always provided by Clerk, update the schema:
- "email" TEXT, + "email" TEXT NOT NULL,Then generate a new migration. If email truly can be null in some scenarios, document why in a comment.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (10)
.gitignore(1 hunks)actions/createGithubWebhook.js(1 hunks)actions/rewardContributorForIssue.js(1 hunks)actions/userProfile.js(1 hunks)api/webhooks/github.js(1 hunks)lib/prisma.js(1 hunks)package.json(1 hunks)prisma/migrations/20251004172052_init/migration.sql(1 hunks)prisma/migrations/migration_lock.toml(1 hunks)prisma/schema.prisma(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
actions/rewardContributorForIssue.js (3)
api/webhooks/github.js (2)
githubIssueId(30-30)githubUsername(29-29)actions/userProfile.js (3)
issue(204-214)user(15-28)user(60-60)lib/prisma.js (2)
prisma(4-4)prisma(4-4)
actions/userProfile.js (3)
actions/rewardContributorForIssue.js (2)
user(24-26)issue(20-22)lib/prisma.js (2)
prisma(4-4)prisma(4-4)actions/createGithubWebhook.js (1)
createGithubWebhook(8-36)
api/webhooks/github.js (1)
actions/rewardContributorForIssue.js (1)
rewardContributorForIssue(17-73)
🔇 Additional comments (9)
package.json (1)
13-13: LGTM!The dependency versions are current and appropriate for this PR's Prisma ORM integration and GitHub webhook functionality. Based on learnings, these are stable releases:
- Prisma 6.16.3: Latest patch with bug fixes
- Octokit 5.0.3: Current "all-batteries" SDK package
Also applies to: 18-19
actions/userProfile.js (3)
114-131: LGTM with proper BigInt handling.The raw SQL query correctly computes leaderboard rankings and handles BigInt serialization for JSON. The query is safe from SQL injection since it contains no user input interpolation.
147-159: LGTM! Proper use of transactions.The transactional creation of organization and admin membership ensures data consistency, preventing orphaned organizations without members.
204-214: LGTM! Proper upsert pattern.The issue bounty logic correctly uses
githubIssueIdas the unique identifier, allowing both creation and updates of bounties.prisma/migrations/migration_lock.toml (1)
1-3: LGTM!Standard Prisma migration lock file correctly specifying PostgreSQL as the database provider.
lib/prisma.js (1)
1-8: LGTM! Proper Prisma singleton pattern.The implementation correctly uses
globalThisto prevent multiple PrismaClient instances in development with hot module reloading. Based on learnings, this is the recommended pattern for Next.js with Prisma 6.16.3.Based on learnings
prisma/migrations/20251004172052_init/migration.sql (2)
1-5: LGTM! Well-defined enums.The enum types provide type safety and clear semantics for role-based access control and issue lifecycle tracking.
101-101: LGTM! One reward per issue constraint.The unique constraint on
issueIdenforces that each issue can only have one rewarded contribution. This prevents double-rewarding but assumes only one PR closes each issue. Document this business rule in your schema or code comments if this is intentional behavior..gitignore (1)
42-43: Remove redundant .gitignore entry (lines 42–43):prisma/schema.prismauses the default client output (node_modules/.prisma/client), so/lib/generated/prismais never populated—delete that entry.
| if (!userGithubToken) return { error: "User does not have a GitHub token." }; | ||
|
|
||
| const octokit = new Octokit({ auth: userGithubToken }); | ||
| const webhookUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/github`; |
There was a problem hiding this comment.
Security risk: Use server-side environment variable.
NEXT_PUBLIC_APP_URL is exposed to the client and should not be used for server-side webhook configuration. This creates a security vulnerability where the webhook URL could be manipulated.
Use a server-only environment variable:
- const webhookUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/github`;
+ const webhookUrl = `${process.env.APP_URL}/api/webhooks/github`;Add APP_URL to your .env file (not .env.local with NEXT_PUBLIC_ prefix).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const webhookUrl = `${process.env.NEXT_PUBLIC_APP_URL}/api/webhooks/github`; | |
| const webhookUrl = `${process.env.APP_URL}/api/webhooks/github`; |
🤖 Prompt for AI Agents
In actions/createGithubWebhook.js around line 12, the code uses
NEXT_PUBLIC_APP_URL (a client-exposed env var) to build the webhook URL; replace
it with a server-only environment variable (e.g., process.env.APP_URL) set in
your server .env (not NEXT_PUBLIC_ prefixed) and remove any client exposure.
Update the code to read process.env.APP_URL, validate it exists (throw or log
and abort if missing), and ensure APP_URL is documented/added to your deployment
env vars so the webhook URL is configured server-side only.
| export async function rewardContributorForIssue(githubIssueId, githubUsername) { | ||
| try { | ||
| // 1. Find the issue and the user in your database | ||
| const issue = await prisma.issue.findUnique({ | ||
| where: { githubIssueId }, | ||
| }); | ||
|
|
||
| const user = await prisma.user.findFirst({ | ||
| where: { githubUsername }, | ||
| }); | ||
|
|
||
| // 2. Validate everything before proceeding | ||
| if (!issue) return { error: `Issue #${githubIssueId} not found in our database.` }; | ||
| if (!user) return { error: `User '${githubUsername}' is not registered in our app.` }; | ||
| if (issue.status !== 'OPEN') return { error: 'Issue is not open for rewards.' }; | ||
| if (!user.walletAddress) return { error: `User ${githubUsername} has no wallet address.` }; | ||
| if (issue.tokenReward <= 0) return { error: 'Issue has no token reward set.' }; | ||
|
|
||
| // 3. Perform the on-chain Solana transaction | ||
| const { signature, error } = await sendSplTokenReward( | ||
| user.walletAddress, | ||
| issue.tokenReward, | ||
| issue.number | ||
| ); | ||
|
|
||
| if (error) throw new Error(error); | ||
|
|
||
| // 4. Update your database in a single transaction | ||
| const contribution = await prisma.$transaction(async (tx) => { | ||
| // Create the contribution record as a log | ||
| const newContribution = await tx.contribution.create({ | ||
| data: { | ||
| issueId: issue.id, | ||
| contributorId: user.id, | ||
| transactionSignature: signature, | ||
| }, | ||
| }); | ||
|
|
||
| // Update the issue status to REWARDED | ||
| await tx.issue.update({ | ||
| where: { id: issue.id }, | ||
| data: { status: 'REWARDED' }, | ||
| }); | ||
|
|
||
| return newContribution; | ||
| }); | ||
|
|
||
| // 5. Revalidate paths to update the UI for users | ||
| revalidatePath(`/profile/${user.githubUsername}`); | ||
| revalidatePath("/leaderboard"); | ||
|
|
||
| return { success: true, contribution }; | ||
| } catch (error) { | ||
| console.error("Failed to reward contributor:", error); | ||
| return { error: error.message }; | ||
| } |
There was a problem hiding this comment.
Import the dependencies before using them.
prisma and revalidatePath are referenced without any imports, so this module will throw a ReferenceError the first time rewardContributorForIssue runs. Add the missing imports at the top of the file.
+import { prisma } from "@/lib/prisma";
+import { revalidatePath } from "next/cache";
+
// --- REAL SOLANA LOGIC GOES HERE ---
// 1. Initialize connection to Solana cluster.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function rewardContributorForIssue(githubIssueId, githubUsername) { | |
| try { | |
| // 1. Find the issue and the user in your database | |
| const issue = await prisma.issue.findUnique({ | |
| where: { githubIssueId }, | |
| }); | |
| const user = await prisma.user.findFirst({ | |
| where: { githubUsername }, | |
| }); | |
| // 2. Validate everything before proceeding | |
| if (!issue) return { error: `Issue #${githubIssueId} not found in our database.` }; | |
| if (!user) return { error: `User '${githubUsername}' is not registered in our app.` }; | |
| if (issue.status !== 'OPEN') return { error: 'Issue is not open for rewards.' }; | |
| if (!user.walletAddress) return { error: `User ${githubUsername} has no wallet address.` }; | |
| if (issue.tokenReward <= 0) return { error: 'Issue has no token reward set.' }; | |
| // 3. Perform the on-chain Solana transaction | |
| const { signature, error } = await sendSplTokenReward( | |
| user.walletAddress, | |
| issue.tokenReward, | |
| issue.number | |
| ); | |
| if (error) throw new Error(error); | |
| // 4. Update your database in a single transaction | |
| const contribution = await prisma.$transaction(async (tx) => { | |
| // Create the contribution record as a log | |
| const newContribution = await tx.contribution.create({ | |
| data: { | |
| issueId: issue.id, | |
| contributorId: user.id, | |
| transactionSignature: signature, | |
| }, | |
| }); | |
| // Update the issue status to REWARDED | |
| await tx.issue.update({ | |
| where: { id: issue.id }, | |
| data: { status: 'REWARDED' }, | |
| }); | |
| return newContribution; | |
| }); | |
| // 5. Revalidate paths to update the UI for users | |
| revalidatePath(`/profile/${user.githubUsername}`); | |
| revalidatePath("/leaderboard"); | |
| return { success: true, contribution }; | |
| } catch (error) { | |
| console.error("Failed to reward contributor:", error); | |
| return { error: error.message }; | |
| } | |
| import { prisma } from "@/lib/prisma"; | |
| import { revalidatePath } from "next/cache"; | |
| // --- REAL SOLANA LOGIC GOES HERE --- | |
| // 1. Initialize connection to Solana cluster. | |
| export async function rewardContributorForIssue(githubIssueId, githubUsername) { | |
| try { | |
| // 1. Find the issue and the user in your database | |
| const issue = await prisma.issue.findUnique({ | |
| where: { githubIssueId }, | |
| }); | |
| const user = await prisma.user.findFirst({ | |
| where: { githubUsername }, | |
| }); | |
| // 2. Validate everything before proceeding | |
| if (!issue) return { error: `Issue #${githubIssueId} not found in our database.` }; | |
| if (!user) return { error: `User '${githubUsername}' is not registered in our app.` }; | |
| if (issue.status !== 'OPEN') return { error: 'Issue is not open for rewards.' }; | |
| if (!user.walletAddress) return { error: `User ${githubUsername} has no wallet address.` }; | |
| if (issue.tokenReward <= 0) return { error: 'Issue has no token reward set.' }; | |
| // 3. Perform the on-chain Solana transaction | |
| const { signature, error } = await sendSplTokenReward( | |
| user.walletAddress, | |
| issue.tokenReward, | |
| issue.number | |
| ); | |
| if (error) throw new Error(error); | |
| // 4. Update your database in a single transaction | |
| const contribution = await prisma.$transaction(async (tx) => { | |
| // Create the contribution record as a log | |
| const newContribution = await tx.contribution.create({ | |
| data: { | |
| issueId: issue.id, | |
| contributorId: user.id, | |
| transactionSignature: signature, | |
| }, | |
| }); | |
| // Update the issue status to REWARDED | |
| await tx.issue.update({ | |
| where: { id: issue.id }, | |
| data: { status: 'REWARDED' }, | |
| }); | |
| return newContribution; | |
| }); | |
| // 5. Revalidate paths to update the UI for users | |
| revalidatePath(`/profile/${user.githubUsername}`); | |
| revalidatePath("/leaderboard"); | |
| return { success: true, contribution }; | |
| } catch (error) { | |
| console.error("Failed to reward contributor:", error); | |
| return { error: error.message }; | |
| } | |
| } |
🤖 Prompt for AI Agents
In actions/rewardContributorForIssue.js around lines 17-72, the function uses
prisma and revalidatePath without importing them; add the missing imports at the
top of the file (e.g., import your Prisma client instance as prisma from your
project path such as '../lib/prisma' or wherever it lives, and import {
revalidatePath } from 'next/cache' or the appropriate framework module) so the
function can access the database client and revalidation helper; ensure the
import paths match your project structure and remove any unused imports.
|
|
||
| // Updates the wallet address for the current user. | ||
| export async function updateUserWallet(walletAddress) { | ||
| const { userId } = auth(); |
There was a problem hiding this comment.
Use async auth() for Next.js 15 compatibility.
The Clerk SDK version 6.33.2 used in this project requires await auth() when called in server actions with Next.js 15.
Based on learnings
Apply this diff:
- const { userId } = auth();
+ const { userId } = await auth();This pattern must be applied to all auth() calls in this file (lines 38, 56, 143, 170, 200).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { userId } = auth(); | |
| const { userId } = await auth(); |
🤖 Prompt for AI Agents
In actions/userProfile.js around lines 38, 56, 143, 170 and 200, calls to auth()
must be awaited for Next.js 15 compatibility with Clerk SDK v6.33.2; replace
each occurrence like const { userId } = auth(); with const { userId } = await
auth(); (or await auth() destructured appropriately) and ensure the surrounding
function is declared async so awaiting is valid; apply this change at all listed
line locations.
| where: { contributorId: userId }, | ||
| include: { issue: { select: { tokenReward: true } } }, | ||
| }); | ||
| const totalEarned = contributions.reduce((sum, c) => sum + Number(c.issue.tokenReward), 0); |
There was a problem hiding this comment.
Potential precision loss converting Decimal to Number.
The tokenReward field is defined as DECIMAL(18,9) in the database, which can represent values larger than JavaScript's Number.MAX_SAFE_INTEGER (2^53 - 1). Converting to Number may cause precision loss for large token amounts.
Use string arithmetic or a decimal library:
- const totalEarned = contributions.reduce((sum, c) => sum + Number(c.issue.tokenReward), 0);
+ const totalEarned = contributions.reduce((sum, c) => sum + parseFloat(c.issue.tokenReward.toString()), 0);Consider using a decimal library like decimal.js for financial calculations if precision is critical.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const totalEarned = contributions.reduce((sum, c) => sum + Number(c.issue.tokenReward), 0); | |
| const totalEarned = contributions.reduce((sum, c) => sum + parseFloat(c.issue.tokenReward.toString()), 0); |
🤖 Prompt for AI Agents
In actions/userProfile.js around line 67, the reduction converts
issue.tokenReward to Number causing potential precision loss for DECIMAL(18,9)
values; change the summation to use a decimal library (e.g., decimal.js):
import/require Decimal, convert each c.issue.tokenReward to a string then to new
Decimal(...) and accumulate using Decimal.add, and finally convert the
totalEarned to the required output type (string or Decimal.toFixed) instead of a
Number; ensure any downstream uses expect the string/Decimal representation.
| if (webhookResult.error) { | ||
| console.error("Webhook setup failed:", webhookResult.error); | ||
| } |
There was a problem hiding this comment.
Webhook failures are silently ignored.
If webhook creation fails, the repository is still registered but won't receive PR events, breaking the reward flow. Users won't know the webhook setup failed.
Consider these approaches:
Option 1: Fail the entire operation (stricter)
if (webhookResult.error) {
- console.error("Webhook setup failed:", webhookResult.error);
+ // Rollback repo creation
+ await prisma.repository.delete({ where: { id: repo.id } });
+ return { error: `Repository registered but webhook failed: ${webhookResult.error}` };
}Option 2: Track webhook status (more resilient)
Add a webhookConfigured boolean field to the Repository model and allow manual retry:
if (webhookResult.error) {
console.error("Webhook setup failed:", webhookResult.error);
+ await prisma.repository.update({
+ where: { id: repo.id },
+ data: { webhookConfigured: false }
+ });
+ return { repo, warning: "Repository registered but webhook setup failed. Please retry." };
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (webhookResult.error) { | |
| console.error("Webhook setup failed:", webhookResult.error); | |
| } | |
| if (webhookResult.error) { | |
| // Rollback repo creation | |
| await prisma.repository.delete({ where: { id: repo.id } }); | |
| return { error: `Repository registered but webhook failed: ${webhookResult.error}` }; | |
| } |
| if (webhookResult.error) { | |
| console.error("Webhook setup failed:", webhookResult.error); | |
| } | |
| if (webhookResult.error) { | |
| console.error("Webhook setup failed:", webhookResult.error); | |
| await prisma.repository.update({ | |
| where: { id: repo.id }, | |
| data: { webhookConfigured: false } | |
| }); | |
| return { repo, warning: "Repository registered but webhook setup failed. Please retry." }; | |
| } |
| function getClosingIssueId(body) { | ||
| if (!body) return null; | ||
| const match = body.match(/(?:closes|fixes|resolves)\s#(\d+)/i); | ||
| return match ? BigInt(match[1]) : null; | ||
| } |
There was a problem hiding this comment.
Avoid BigInt for compatibility.
Line 9 returns a BigInt which can cause JSON serialization errors and compatibility issues with the reward function.
Convert to Number or String:
function getClosingIssueId(body) {
if (!body) return null;
const match = body.match(/(?:closes|fixes|resolves)\s#(\d+)/i);
- return match ? BigInt(match[1]) : null;
+ return match ? parseInt(match[1], 10) : null;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function getClosingIssueId(body) { | |
| if (!body) return null; | |
| const match = body.match(/(?:closes|fixes|resolves)\s#(\d+)/i); | |
| return match ? BigInt(match[1]) : null; | |
| } | |
| function getClosingIssueId(body) { | |
| if (!body) return null; | |
| const match = body.match(/(?:closes|fixes|resolves)\s#(\d+)/i); | |
| return match ? parseInt(match[1], 10) : null; | |
| } |
🤖 Prompt for AI Agents
In api/webhooks/github.js around lines 6 to 10, the function currently returns a
BigInt which can break JSON serialization and downstream compatibility; replace
the BigInt return with a JSON-serializable Number (e.g., parseInt/Number on
match[1]) or a String per caller needs — convert match[1] to Number, guard
against NaN and return null if conversion fails, so the function returns either
a Number or null instead of BigInt.
| // IMPORTANT: You must verify the webhook signature in a real app! | ||
| // This ensures the request is actually from GitHub. | ||
| // For the hackathon, we might skip this, but it's critical for production. |
There was a problem hiding this comment.
Critical: Implement webhook signature verification.
The webhook endpoint accepts any POST request without verifying it came from GitHub. This is a critical security vulnerability that allows attackers to trigger fake reward payouts.
Implement HMAC signature verification:
+import crypto from 'crypto';
+
+function verifyGitHubSignature(payload, signature, secret) {
+ const hmac = crypto.createHmac('sha256', secret);
+ const digest = 'sha256=' + hmac.update(payload).digest('hex');
+ return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
+}
+
export default async function handler(req, res) {
- // IMPORTANT: You must verify the webhook signature in a real app!
- // This ensures the request is actually from GitHub.
- // For the hackathon, we might skip this, but it's critical for production.
+ const signature = req.headers['x-hub-signature-256'];
+ const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
+
+ if (!signature || !webhookSecret) {
+ return res.status(401).json({ message: 'Unauthorized' });
+ }
+
+ const payload = JSON.stringify(req.body);
+ if (!verifyGitHubSignature(payload, signature, webhookSecret)) {
+ return res.status(401).json({ message: 'Invalid signature' });
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // IMPORTANT: You must verify the webhook signature in a real app! | |
| // This ensures the request is actually from GitHub. | |
| // For the hackathon, we might skip this, but it's critical for production. | |
| import crypto from 'crypto'; | |
| function verifyGitHubSignature(payload, signature, secret) { | |
| const hmac = crypto.createHmac('sha256', secret); | |
| const digest = 'sha256=' + hmac.update(payload).digest('hex'); | |
| return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest)); | |
| } | |
| export default async function handler(req, res) { | |
| const signature = req.headers['x-hub-signature-256']; | |
| const webhookSecret = process.env.GITHUB_WEBHOOK_SECRET; | |
| if (!signature || !webhookSecret) { | |
| return res.status(401).json({ message: 'Unauthorized' }); | |
| } | |
| const payload = JSON.stringify(req.body); | |
| if (!verifyGitHubSignature(payload, signature, webhookSecret)) { | |
| return res.status(401).json({ message: 'Invalid signature' }); | |
| } | |
| // ... existing handler logic continues here ... | |
| } |
| ALTER TABLE "org_members" ADD CONSTRAINT "org_members_userId_fkey" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "org_members" ADD CONSTRAINT "org_members_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "repositories" ADD CONSTRAINT "repositories_orgId_fkey" FOREIGN KEY ("orgId") REFERENCES "organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "issues" ADD CONSTRAINT "issues_repoId_fkey" FOREIGN KEY ("repoId") REFERENCES "repositories"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "contributions" ADD CONSTRAINT "contributions_issueId_fkey" FOREIGN KEY ("issueId") REFERENCES "issues"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
|
||
| -- AddForeignKey | ||
| ALTER TABLE "contributions" ADD CONSTRAINT "contributions_contributorId_fkey" FOREIGN KEY ("contributorId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Review CASCADE delete implications for data integrity.
All foreign keys use ON DELETE CASCADE, which will propagate deletions through the entire relationship hierarchy. Consider the impact:
- Deleting a user removes all their contributions and org memberships
- Deleting an organization removes all repositories, issues, and contributions
- Deleting a repository removes all issues and contributions
For a reward/bounty system, you may want to preserve historical contribution records even after user or org deletion. Consider using ON DELETE RESTRICT or ON DELETE SET NULL for certain relationships, or implement soft deletes.
Example for preserving contribution history:
ALTER TABLE "contributions" ADD CONSTRAINT "contributions_contributorId_fkey"
- FOREIGN KEY ("contributorId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+ FOREIGN KEY ("contributorId") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;This would prevent user deletion if they have contributions, or you could change the field to nullable and use SET NULL to mark them as deleted.
🤖 Prompt for AI Agents
In prisma/migrations/20251004172052_init/migration.sql around lines 107 to 122,
all foreign keys currently use ON DELETE CASCADE which will cascade removals
across orgs, repos, issues and contributions and can erase historical
contribution data; update the migration to preserve history by replacing cascade
on the contribution and membership relationships: change the
contributions.contributorId FK to allow NULL and use ON DELETE SET NULL (or
change to ON DELETE RESTRICT if you want to block deletion while contributions
exist), change org_members.userId to ON DELETE RESTRICT (or implement
soft-delete on users and orgs instead), and change repositories/orgs or issues
FKs as appropriate (use RESTRICT or SET NULL) to avoid unintentional mass
deletions; ensure schema changes include ALTER COLUMN ... DROP NOT NULL when
making contributorId nullable and add corresponding constraint updates or
soft-delete flags before running the migration.
| model User { | ||
| id String @id @unique // Clerk User ID | ||
| email String? @unique | ||
| githubUsername String? | ||
| githubAvatarUrl String? | ||
| walletAddress String? @unique | ||
| createdAt DateTime @default(now()) |
There was a problem hiding this comment.
Drop @unique from the primary key field.
Prisma rejects schemas that annotate the same field with both @id and @unique. This causes prisma generate/migrate to fail, blocking the entire rollout. Remove the redundant @unique on the id field.
-model User {
- id String @id @unique // Clerk User ID
+model User {
+ id String @id // Clerk User ID🤖 Prompt for AI Agents
In prisma/schema.prisma around lines 27 to 33, the User model's id field is
annotated with both @id and @unique which Prisma rejects; remove the redundant
@unique from the id field so it is annotated only with @id (leave other fields
unchanged), then run prisma generate/migrate to confirm the schema validates.
Summary by CodeRabbit
New Features
Chores