-
Notifications
You must be signed in to change notification settings - Fork 19
feat: Implement OCToken NFT Open Collaborator Award mechanism (Fixes #53) #76
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
5bf0829
5407ceb
695db1f
e3ea704
179196a
d893ec6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| import { Context } from 'koa'; | ||
| import { createKoaRouter, withKoaRouter } from 'next-ssr-middleware'; | ||
| import { AwardModel } from '../../../../models/Award'; | ||
| import { safeAPI, verifyJWT } from '../../core'; | ||
|
|
||
| export const config = { api: { bodyParser: true } }; | ||
|
|
||
| const router = createKoaRouter(import.meta.url); | ||
|
|
||
| const EthereumAddressPattern = /^0x[a-fA-F0-9]{40}$/; | ||
|
|
||
| router.post('/issue', safeAPI, verifyJWT, async (context: Context) => { | ||
| const { recordId, walletAddress } = (context.request as any).body; | ||
|
|
||
| if (!recordId || !walletAddress) { | ||
| context.throw(400, 'recordId and walletAddress are required'); | ||
| } | ||
|
|
||
| if (typeof walletAddress !== 'string' || !EthereumAddressPattern.test(walletAddress)) { | ||
| context.throw(400, 'walletAddress must be a valid Ethereum address'); | ||
| } | ||
|
|
||
| // 1. Fetch award and check authorization | ||
| const awardModel = new AwardModel(); | ||
| const award = await awardModel.getOne(recordId); | ||
|
|
||
| if (!award) { | ||
| context.throw(404, 'Award record not found'); | ||
| } | ||
|
|
||
| const currentUser = (context.state as any).user; | ||
| if (!currentUser) { | ||
| context.throw(401, 'Unauthorized'); | ||
| } | ||
|
|
||
| if ( | ||
| currentUser.name !== 'Robot' && | ||
| currentUser.name !== award.nominator && | ||
| currentUser.name !== award.nomineeName | ||
| ) { | ||
|
Comment on lines
+66
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 验证 verifyJWT 写入 context.state.user 的字段,以及是否已有稳定 role/id 可复用。
rg -nP -C4 '\bverifyJWT\b|context\.state\.user|state\.user|isAdmin|role|userId|email|name' pages models --type=tsRepository: Open-Source-Bazaar/Open-Source-Bazaar.github.io Length of output: 47055 不要用展示名和
🤖 Prompt for AI Agents |
||
| context.throw(403, 'You do not have permission to issue this award'); | ||
| } | ||
|
|
||
| // 2. Idempotency Check | ||
| if (award.transactionHash && award.tokenId) { | ||
| context.body = { | ||
| success: true, | ||
| transactionHash: award.transactionHash as string, | ||
| tokenId: award.tokenId as string, | ||
| }; | ||
| return; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| let transactionHash: string; | ||
| let tokenId: string; | ||
|
|
||
| try { | ||
| const mintApiUrl = process.env.NFT_MINT_API || 'https://api.octoken.org/mint'; | ||
| const timeoutVal = parseInt(process.env.NFT_MINT_TIMEOUT || '10000', 10); | ||
| const controller = new AbortController(); | ||
| const timeout = setTimeout(() => controller.abort(), timeoutVal); | ||
|
|
||
| const response = await fetch(mintApiUrl, { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ walletAddress, recordId }), | ||
| signal: controller.signal, | ||
| }).finally(() => clearTimeout(timeout)); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`NFT issuance failed with status ${response.status}`); | ||
| } | ||
|
|
||
| const data = await response.json(); | ||
| transactionHash = data.transactionHash; | ||
| tokenId = data.tokenId; | ||
|
|
||
| if (!transactionHash || !tokenId) { | ||
| throw new Error('Invalid response from minting service'); | ||
| } | ||
| } catch (error) { | ||
| const msg = | ||
| (error as Error).name === 'AbortError' | ||
| ? 'NFT issuance request timed out' | ||
| : (error as Error).message || 'NFT issuance failed'; | ||
| return context.throw(502, msg); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| await awardModel.updateOne( | ||
| { | ||
| transactionHash, | ||
| tokenId, | ||
| walletAddress, | ||
| }, | ||
| recordId, | ||
| ); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| context.body = { success: true, transactionHash, tokenId }; | ||
| }); | ||
|
|
||
| export default withKoaRouter(router); | ||
Uh oh!
There was an error while loading. Please reload this page.