diff --git a/.env b/.env index 6efe19b..aca6142 100644 --- a/.env +++ b/.env @@ -8,5 +8,7 @@ NEXT_PUBLIC_LARK_WIKI_URL = https://open-source-bazaar.feishu.cn/wiki/space/7052 NEXT_PUBLIC_LARK_BITABLE_ID = PNOGbGqhPacsHOsvJqHctS77nje NEXT_PUBLIC_ACTIVITY_TABLE_ID = tblREEMxDOECZZrK +NEXT_PUBLIC_PROJECT_TABLE_ID = tblGnY6Hm0nTSBR9 +NEXT_PUBLIC_AWARD_TABLE_ID = tblmYd5V5BMngAp2 NEXT_PUBLIC_STRAPI_API_HOST = https://china-ngo-db.onrender.com/api/ diff --git a/.github/scripts/share-reward.ts b/.github/scripts/share-reward.ts index 953ef1a..77aa148 100644 --- a/.github/scripts/share-reward.ts +++ b/.github/scripts/share-reward.ts @@ -19,28 +19,37 @@ interface PRMeta { assignees: components['schemas']['simple-user'][]; } -const PR_URL = await $`gh api graphql -f query='{ +const PR_DATA = await $`gh api graphql -f query='{ repository(owner: "${repositoryOwner}", name: "${repositoryName}") { issue(number: ${issueNumber}) { closedByPullRequestsReferences(first: 10) { nodes { url merged + mergeCommit { + oid + } } } } } -}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.merged == true) | .url' | head -n 1`; +}' --jq '.data.repository.issue.closedByPullRequestsReferences.nodes[] | select(.merged == true) | {url: .url, mergeCommitSha: .mergeCommit.oid}' | head -n 1`; -if (!PR_URL.text().trim()) - throw new ReferenceError('No merged PR is found for the given issue number.'); +const prData = PR_DATA.text().trim(); + +if (!prData) throw new ReferenceError('No merged PR is found for the given issue number.'); + +const { url: PR_URL, mergeCommitSha } = JSON.parse(prData); + +if (!PR_URL || !mergeCommitSha) throw new Error('Missing required fields in PR data'); + +console.table({ PR_URL, mergeCommitSha }); const { author, assignees }: PRMeta = await ( await $`gh pr view ${PR_URL} --json author,assignees` ).json(); -// Function to check if a user is a Copilot/bot user -function isCopilotUser(login: string): boolean { +function isBotUser(login: string) { const lowerLogin = login.toLowerCase(); return ( lowerLogin.includes('copilot') || @@ -50,19 +59,17 @@ function isCopilotUser(login: string): boolean { ); } -// Filter out Copilot and bot users from the list +// Filter out Bot users from the list const allUsers = [author.login, ...assignees.map(({ login }) => login)]; -const users = allUsers.filter(login => !isCopilotUser(login)); +const users = allUsers.filter(login => !isBotUser(login)); console.log(`All users: ${allUsers.join(', ')}`); -console.log(`Filtered users (excluding bots/copilot): ${users.join(', ')}`); +console.log(`Filtered users (excluding bots): ${users.join(', ')}`); -// Handle case where all users are bots/copilot -if (users.length === 0) { - console.log('No real users found (all users are bots/copilot). Skipping reward distribution.'); - console.log(`Filtered users: ${allUsers.join(', ')}`); - process.exit(0); -} +if (!users[0]) + throw new ReferenceError( + 'No real users found (all users are bots). Skipping reward distribution.', + ); const rewardNumber = parseFloat(reward); @@ -86,7 +93,7 @@ console.log(listText); await $`git config --global user.name "github-actions[bot]"`; await $`git config --global user.email "github-actions[bot]@users.noreply.github.com"`; -await $`git tag -a "reward-${issueNumber}" -m ${listText}`; +await $`git tag -a "reward-${issueNumber}" ${mergeCommitSha} -m ${listText}`; await $`git push origin --tags`; const commentBody = `## Reward data diff --git a/components/Navigator/MainNavigator.tsx b/components/Navigator/MainNavigator.tsx index 87ea119..bc99f46 100644 --- a/components/Navigator/MainNavigator.tsx +++ b/components/Navigator/MainNavigator.tsx @@ -38,7 +38,8 @@ const topNavBarMenu = ({ t }: typeof i18n): MenuItem[] => [ { title: t('open_source_projects'), subs: [ - { href: '/project', title: t('open_source_projects') }, + { href: '/project', title: t('self_developed_projects') }, + { href: '/search/project', title: t('bazaar_projects') }, { href: '/issue', title: 'GitHub issues' }, { href: '/license-filter', title: t('license_filter') }, { href: '/finance', title: t('finance_page_title') }, diff --git a/components/Project/Card.tsx b/components/Project/Card.tsx new file mode 100644 index 0000000..8d08be6 --- /dev/null +++ b/components/Project/Card.tsx @@ -0,0 +1,15 @@ +import { FC } from 'react'; + +import { Project } from '../../models/Project'; +import { GitCard } from '../Git/Card'; + +export const ProjectCard: FC = ({ name, sourceLink, link, languages, tags, summary }) => ( + +); diff --git a/models/Award.ts b/models/Award.ts new file mode 100644 index 0000000..406ddc2 --- /dev/null +++ b/models/Award.ts @@ -0,0 +1,29 @@ +import { BiDataQueryOptions, BiDataTable, BiSearch, TableCellValue } from 'mobx-lark'; + +import { larkClient } from './Base'; +import { AwardTableId, LarkBitableId } from './configuration'; + +export type Award = Record< + | 'awardName' + | `nominee${'Name' | 'Desc'}` + | 'videoUrl' + | 'reason' + | 'nominator' + | 'createdAt' + | 'votes', + TableCellValue +>; + +export class AwardModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; + + constructor(appId = LarkBitableId, tableId = AwardTableId) { + super(appId, tableId); + } +} + +export class SearchAwardModel extends BiSearch(AwardModel) { + searchKeys = ['awardName', 'nomineeName', 'nomineeDesc', 'reason', 'nominator']; +} diff --git a/models/Project.ts b/models/Project.ts new file mode 100644 index 0000000..93ed475 --- /dev/null +++ b/models/Project.ts @@ -0,0 +1,66 @@ +import { + BiDataQueryOptions, + BiDataTable, + BiSearch, + TableCellLink, + TableCellValue, + TableRecord, +} from 'mobx-lark'; + +import { LarkBase, larkClient } from './Base'; +import { LarkBitableId, ProjectTableId } from './configuration'; + +export type Project = LarkBase & + Record< + | 'name' + | 'type' + | 'sourceLink' + | 'link' + | 'license' + | 'languages' + | 'tags' + | 'summary' + | 'logo' + | 'status' + | 'reason', + TableCellValue + >; + +export class ProjectModel extends BiDataTable() { + client = larkClient; + + queryOptions: BiDataQueryOptions = { text_field_as_array: false }; + + constructor(appId = LarkBitableId, tableId = ProjectTableId) { + super(appId, tableId); + } + + extractFields({ + fields: { sourceLink, link, languages, tags, ...fields }, + ...meta + }: TableRecord) { + return { + ...meta, + ...fields, + sourceLink: (sourceLink as TableCellLink)?.link, + link: (link as TableCellLink)?.link, + languages: languages?.toString().split(/\s*,\s*/) || [], + tags: tags?.toString().split(/\s*,\s*/) || [], + }; + } +} + +export class SearchProjectModel extends BiSearch(ProjectModel) { + searchKeys = [ + 'name', + 'type', + 'sourceLink', + 'link', + 'license', + 'languages', + 'tags', + 'summary', + 'status', + 'reason', + ]; +} diff --git a/models/System.ts b/models/System.ts index a8e2684..776ff28 100644 --- a/models/System.ts +++ b/models/System.ts @@ -4,8 +4,10 @@ import { BaseModel, DataObject, Filter, ListModel, toggle } from 'mobx-restful'; import { Constructor } from 'web-utility'; import { SearchActivityModel } from './Activity'; +import { SearchAwardModel } from './Award'; import { ownClient } from './Base'; import { OrganizationModel } from './Organization'; +import { SearchProjectModel } from './Project'; export type SearchableFilter = Filter & { keywords?: string; @@ -22,6 +24,8 @@ export type CityCoordinateMap = Record; export class SystemModel extends BaseModel { searchMap = { activity: SearchActivityModel, + project: SearchProjectModel, + award: SearchAwardModel, NGO: OrganizationModel, } as Record>>; diff --git a/models/configuration.ts b/models/configuration.ts index cf245b9..b7f5a3d 100644 --- a/models/configuration.ts +++ b/models/configuration.ts @@ -35,4 +35,6 @@ export const LarkWikiDomain = hostname; export const LarkWikiId = pathname.split('/').pop()!; export const LarkBitableId = process.env.NEXT_PUBLIC_LARK_BITABLE_ID!, - ActivityTableId = process.env.NEXT_PUBLIC_ACTIVITY_TABLE_ID!; + ActivityTableId = process.env.NEXT_PUBLIC_ACTIVITY_TABLE_ID!, + ProjectTableId = process.env.NEXT_PUBLIC_PROJECT_TABLE_ID!, + AwardTableId = process.env.NEXT_PUBLIC_AWARD_TABLE_ID!; diff --git a/pages/award/index.tsx b/pages/award/index.tsx new file mode 100644 index 0000000..a0e22bd --- /dev/null +++ b/pages/award/index.tsx @@ -0,0 +1,16 @@ +import { cache, compose, errorLogger } from 'next-ssr-middleware'; +import { FC } from 'react'; + +import { Award, AwardModel } from '../../models/Award'; + +export const getServerSideProps = compose(cache(), errorLogger, async () => { + const awards = await new AwardModel().getAll(); + + return { props: { awards } }; +}); + +const AwardPage: FC<{ awards: Award[] }> = ({ awards }) => { + return <>; +}; + +export default AwardPage; diff --git a/pages/search/[model]/index.tsx b/pages/search/[model]/index.tsx index 11a99be..3d5058f 100644 --- a/pages/search/[model]/index.tsx +++ b/pages/search/[model]/index.tsx @@ -10,6 +10,7 @@ import { CardPage, CardPageProps } from '../../../components/Layout/CardPage'; import { PageHead } from '../../../components/Layout/PageHead'; import { SearchBar } from '../../../components/Navigator/SearchBar'; import { OrganizationCard } from '../../../components/Organization/Card'; +import { ProjectCard } from '../../../components/Project/Card'; import systemStore, { SearchPageMeta } from '../../../models/System'; import { i18n, I18nContext } from '../../../models/Translation'; @@ -40,11 +41,13 @@ export const getServerSideProps = compose<{ model: string }, SearchModelPageProp const SearchNameMap = ({ t }: typeof i18n): Record => ({ activity: t('activity'), + project: t('open_source_projects'), NGO: t('NGO'), }); const SearchCardMap: Record = { activity: ActivityCard, + project: ProjectCard, NGO: OrganizationCard, }; diff --git a/translation/en-US.ts b/translation/en-US.ts index ccd393d..b5383b1 100644 --- a/translation/en-US.ts +++ b/translation/en-US.ts @@ -9,6 +9,8 @@ export default { hackathon: 'Hackathon', bounty: 'Open Source Bounty', open_source_projects: 'Open Source projects', + self_developed_projects: 'Self-developed projects', + bazaar_projects: 'Bazaar projects', open_source_bazaar: 'Open Source Bazaar', home_page: 'Home Page', wiki: 'Wiki', diff --git a/translation/zh-CN.ts b/translation/zh-CN.ts index cc954a9..4a6cf03 100644 --- a/translation/zh-CN.ts +++ b/translation/zh-CN.ts @@ -7,6 +7,8 @@ export default { join_us: '参与', open_collaborator_award: '开放协作人奖', open_source_projects: '开源项目', + self_developed_projects: '自研项目', + bazaar_projects: '市集项目', activity: '活动', hackathon: '黑客马拉松', bounty: '开源悬赏', diff --git a/translation/zh-TW.ts b/translation/zh-TW.ts index 58956fe..228b9d2 100644 --- a/translation/zh-TW.ts +++ b/translation/zh-TW.ts @@ -7,6 +7,8 @@ export default { join_us: '參與', open_collaborator_award: '開放協作人獎', open_source_projects: '開源項目', + self_developed_projects: '自研項目', + bazaar_projects: '市集項目', activity: '活動', hackathon: '黑客馬拉松', bounty: '開源懸賞',