diff --git a/.yarn/versions/ecb1a3b6.yml b/.yarn/versions/ecb1a3b6.yml new file mode 100644 index 0000000..85ee0e5 --- /dev/null +++ b/.yarn/versions/ecb1a3b6.yml @@ -0,0 +1,3 @@ +releases: + "@aoi-js/frontend": patch + "@aoi-js/server": patch diff --git a/apps/frontend/src/components/solution/SolutionList.vue b/apps/frontend/src/components/solution/SolutionList.vue index 764eef0..606b92e 100644 --- a/apps/frontend/src/components/solution/SolutionList.vue +++ b/apps/frontend/src/components/solution/SolutionList.vue @@ -50,6 +50,7 @@ import { computed, ref } from 'vue' import { useAppState } from '@/stores/app' import { useContestProblemTitle } from '@/utils/contest/problem/inject' import type { ISolutionDTO } from './types' +import { useRouteQuery } from '@vueuse/router' const { t } = useI18n() const app = useAppState() @@ -78,7 +79,7 @@ const headersProblem = [ { title: t('common.submitted-at'), key: 'submittedAt', align: 'start', sortable: false } ] as const -const userId = ref(app.userId as string) +const userId = useRouteQuery('userId') const { page, diff --git a/apps/frontend/src/components/utils/RanklistViewer.vue b/apps/frontend/src/components/utils/RanklistViewer.vue index b153777..5a65159 100644 --- a/apps/frontend/src/components/utils/RanklistViewer.vue +++ b/apps/frontend/src/components/utils/RanklistViewer.vue @@ -22,7 +22,10 @@ {{ man.rank }} - +
@@ -55,11 +58,17 @@ import { renderMarkdown } from '@/utils/md' import PrincipalProfile from '@/components/utils/PrincipalProfile.vue' import { useI18n } from 'vue-i18n' import RanklistTopstars from '../contest/RanklistTopstars.vue' +import { useContestCapability, useContestData } from '@/utils/contest/inject' const { t } = useI18n() const props = defineProps<{ endpoint: string }>() + +const admin = useContestCapability('admin') +const contest = useContestData() +const participantUrl = (userId: string) => + `/org/${contest.value.orgId}/contest/${contest.value._id}/participant/${userId}` diff --git a/apps/frontend/src/pages/org/[orgId]/contest/[contestId]/participant/[userId].vue b/apps/frontend/src/pages/org/[orgId]/contest/[contestId]/participant/[userId].vue index 1dfc8b7..30419c2 100644 --- a/apps/frontend/src/pages/org/[orgId]/contest/[contestId]/participant/[userId].vue +++ b/apps/frontend/src/pages/org/[orgId]/contest/[contestId]/participant/[userId].vue @@ -2,18 +2,27 @@ - - - - + + + +en: + goto-solutions: Go to solutions +zh-Hans: + goto-solutions: 查看提交 + diff --git a/apps/server/src/routes/contest/admin.ts b/apps/server/src/routes/contest/admin.ts index 90a47e3..9ae57d2 100644 --- a/apps/server/src/routes/contest/admin.ts +++ b/apps/server/src/routes/contest/admin.ts @@ -60,21 +60,23 @@ export const contestAdminRoutes = defineRoutes(async (s) => { } }, async (req) => { - const { modifiedCount } = await solutions.updateOne( + const { modifiedCount } = await solutions.updateMany( { contestId: req.inject(kContestContext)._contestId, state: SolutionState.CREATED }, - { - $set: { - state: SolutionState.PENDING, - submittedAt: req._now, - score: 0, - status: '', - metrics: {}, - message: '' + [ + { + $set: { + state: SolutionState.PENDING, + submittedAt: { $convert: { input: '$$NOW', to: 'double' } }, + score: 0, + status: '', + metrics: {}, + message: '' + } } - }, + ], { ignoreUndefined: true } ) return { modifiedCount } @@ -120,13 +122,15 @@ export const contestAdminRoutes = defineRoutes(async (s) => { _id: req.inject(kContestContext)._contestId, ranklists: { $exists: true, $ne: [] } }, - { - $set: { - ranklistUpdatedAt: req._now, - ranklistState: ContestRanklistState.INVALID + [ + { + $set: { + ranklistUpdatedAt: { $convert: { input: '$$NOW', to: 'double' } }, + ranklistState: ContestRanklistState.INVALID + } }, - $unset: req.body.resetRunner ? { ranklistRunnerId: 1 } : {} - }, + ...(req.body.resetRunner ? [{ $unset: ['ranklistRunnerId'] }] : []) + ], { ignoreUndefined: true } ) return 0 diff --git a/apps/server/src/routes/contest/participant/index.ts b/apps/server/src/routes/contest/participant/index.ts index 5c8cb39..7040276 100644 --- a/apps/server/src/routes/contest/participant/index.ts +++ b/apps/server/src/routes/contest/participant/index.ts @@ -127,10 +127,14 @@ const contestParticipantAdminRoutes = defineRoutes(async (s) => { async (req) => { const ctx = req.inject(kContestContext) const userId = new BSON.UUID(req.params.userId) - await contestParticipants.updateOne( - { contestId: ctx._contestId, userId }, - { $set: { tags: req.body.tags, updatedAt: req._now } } - ) + await contestParticipants.updateOne({ contestId: ctx._contestId, userId }, [ + { + $set: { + tags: req.body.tags, + updatedAt: { $convert: { input: '$$NOW', to: 'double' } } + } + } + ]) return 0 } ) diff --git a/apps/server/src/routes/contest/problem/index.ts b/apps/server/src/routes/contest/problem/index.ts index 9d115ef..1914b31 100644 --- a/apps/server/src/routes/contest/problem/index.ts +++ b/apps/server/src/routes/contest/problem/index.ts @@ -264,6 +264,8 @@ const problemViewRoutes = defineRoutes(async (s) => { metrics: {}, status: '', message: '', + // createdAt is only a reference time, + // so use local time here createdAt: req._now }) const uploadUrl = await getUploadUrl(oss, solutionDataKey(insertedId), { diff --git a/apps/server/src/routes/contest/solution/index.ts b/apps/server/src/routes/contest/solution/index.ts index 844b715..7943426 100644 --- a/apps/server/src/routes/contest/solution/index.ts +++ b/apps/server/src/routes/contest/solution/index.ts @@ -52,16 +52,18 @@ const solutionScopedRoutes = defineRoutes(async (s) => { userId: admin ? undefined : req.user.userId, state: admin ? undefined : SolutionState.CREATED }, - { - $set: { - state: SolutionState.PENDING, - submittedAt: req._now, - score: 0, - status: '', - metrics: {}, - message: '' + [ + { + $set: { + state: SolutionState.PENDING, + submittedAt: { $convert: { input: '$$NOW', to: 'double' } }, + score: 0, + status: '', + metrics: {}, + message: '' + } } - }, + ], { ignoreUndefined: true } ) if (modifiedCount === 0) return rep.notFound() diff --git a/apps/server/src/routes/problem/solution.ts b/apps/server/src/routes/problem/solution.ts index c8a7d03..89e707f 100644 --- a/apps/server/src/routes/problem/solution.ts +++ b/apps/server/src/routes/problem/solution.ts @@ -33,6 +33,8 @@ const solutionScopedRoutes = defineRoutes(async (s) => { { $set: { state: SolutionState.PENDING, + // Problem solutions are not synced with rankers, + // so we can use local time here submittedAt: req._now, score: 0, status: '', diff --git a/apps/server/src/routes/runner/solution.ts b/apps/server/src/routes/runner/solution.ts index d65ca20..0cdb729 100644 --- a/apps/server/src/routes/runner/solution.ts +++ b/apps/server/src/routes/runner/solution.ts @@ -101,7 +101,6 @@ const runnerTaskRoutes = defineRoutes(async (s) => { } }, async (req, rep) => { - const now = req._now const ctx = req.inject(kRunnerSolutionContext) const value = await solutions.findOneAndUpdate( { @@ -109,7 +108,9 @@ const runnerTaskRoutes = defineRoutes(async (s) => { taskId: ctx._taskId, state: { $in: [SolutionState.QUEUED, SolutionState.RUNNING] } }, - { $set: { state: SolutionState.COMPLETED, completedAt: now } }, + // Since completedAt is only for reference, + // use local time here + { $set: { state: SolutionState.COMPLETED, completedAt: req._now } }, { projection: { userId: 1, problemId: 1, contestId: 1, score: 1, status: 1 } } ) if (!value) return rep.conflict() @@ -120,17 +121,19 @@ const runnerTaskRoutes = defineRoutes(async (s) => { contestId: value.contestId, [`results.${value.problemId}.lastSolutionId`]: value._id }, - { - $set: { - [`results.${value.problemId}.lastSolution`]: { - _id: value._id, - score: value.score, - status: value.status, - completedAt: now - }, - updatedAt: now + [ + { + $set: { + [`results.${value.problemId}.lastSolution`]: { + _id: value._id, + score: value.score, + status: value.status, + completedAt: req._now + }, + updatedAt: { $convert: { input: '$$NOW', to: 'double' } } + } } - } + ] ) if (modifiedCount) { // update contest ranklist state @@ -139,12 +142,14 @@ const runnerTaskRoutes = defineRoutes(async (s) => { _id: value.contestId, ranklists: { $exists: true, $ne: [] } }, - { - $set: { - ranklistUpdatedAt: now, - ranklistState: ContestRanklistState.INVALID + [ + { + $set: { + ranklistUpdatedAt: { $convert: { input: '$$NOW', to: 'double' } }, + ranklistState: ContestRanklistState.INVALID + } } - } + ] ) } } else {