diff --git a/models/Award.ts b/models/Award.ts index 406ddc2..ddf7dbd 100644 --- a/models/Award.ts +++ b/models/Award.ts @@ -1,18 +1,25 @@ -import { BiDataQueryOptions, BiDataTable, BiSearch, TableCellValue } from 'mobx-lark'; +import { BiDataQueryOptions, BiDataTable, BiSearch, TableCellValue, TableRecord, normalizeTextArray } from 'mobx-lark'; -import { larkClient } from './Base'; +import { LarkBase, larkClient } from './Base'; import { AwardTableId, LarkBitableId } from './configuration'; -export type Award = Record< - | 'awardName' - | `nominee${'Name' | 'Desc'}` - | 'videoUrl' - | 'reason' - | 'nominator' - | 'createdAt' - | 'votes', - TableCellValue ->; +export type AwardStatus = 'nominated' | 'reviewing' | 'voting' | 'awarded' | 'declined'; + +export type Award = LarkBase & + Record< + | 'awardName' + | `nominee${'Name' | 'Desc' | 'Email' | 'GitHub'}` + | 'videoUrl' + | 'reason' + | 'nominator' + | 'nominatorEmail' + | 'status' + | 'votes' + | 'tokenId' + | 'tokenTxHash' + | 'awardedAt', + TableCellValue + >; export class AwardModel extends BiDataTable() { client = larkClient; @@ -25,5 +32,12 @@ export class AwardModel extends BiDataTable() { } export class SearchAwardModel extends BiSearch(AwardModel) { - searchKeys = ['awardName', 'nomineeName', 'nomineeDesc', 'reason', 'nominator']; + searchKeys = [ + 'awardName', + 'nomineeName', + 'nomineeDesc', + 'nomineeGitHub', + 'reason', + 'nominator', + ]; } diff --git a/pages/api/award/[id]/workflow.ts b/pages/api/award/[id]/workflow.ts new file mode 100644 index 0000000..26f0d5e --- /dev/null +++ b/pages/api/award/[id]/workflow.ts @@ -0,0 +1,48 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { AwardModel } from '../../../../models/Award'; + +const VALID_TRANSITIONS: Record = { + nominated: ['reviewing', 'declined'], + reviewing: ['voting', 'declined'], + voting: ['awarded'], + awarded: [], + declined: [], +}; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + const { id } = req.query; + const { status } = req.body; + + if (!id || !status) { + return res.status(400).json({ error: 'Missing id or status' }); + } + + try { + const awardModel = new AwardModel(); + const awards = await awardModel.getAll(); + const award = awards.find((a) => a.id === id); + + if (!award) { + return res.status(404).json({ error: 'Award not found' }); + } + + const currentStatus = (award.status ?? 'nominated') as string; + const allowed = VALID_TRANSITIONS[currentStatus]; + + if (!allowed || !allowed.includes(status)) { + return res.status(400).json({ + error: `Invalid status transition: ${currentStatus} -> ${status}`, + allowedTransitions: allowed, + }); + } + + await awardModel.updateOne(id as string, { status }); + return res.status(200).json({ success: true, from: currentStatus, to: status }); + } catch (error) { + return res.status(500).json({ error: String(error) }); + } +} \ No newline at end of file diff --git a/pages/api/award/index.ts b/pages/api/award/index.ts new file mode 100644 index 0000000..7bbb4af --- /dev/null +++ b/pages/api/award/index.ts @@ -0,0 +1,33 @@ +import type { NextApiRequest, NextApiResponse } from 'next'; +import { AwardModel } from '../../../models/Award'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const awardModel = new AwardModel(); + + switch (req.method) { + case 'GET': { + const awards = await awardModel.getAll(); + return res.status(200).json(awards); + } + case 'POST': { + const body = req.body; + try { + const result = await awardModel.createOne(body); + return res.status(201).json(result); + } catch (error) { + return res.status(400).json({ error: String(error) }); + } + } + case 'PUT': { + const { id, ...data } = req.body; + try { + await awardModel.updateOne(id, data); + return res.status(200).json({ success: true }); + } catch (error) { + return res.status(400).json({ error: String(error) }); + } + } + default: + return res.status(405).json({ error: 'Method not allowed' }); + } +}