Skip to content

Commit

Permalink
refactor(scoreboard): split queries (GZTimeWalker#325)
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker authored Aug 31, 2024
1 parent e32e6db commit 609de5f
Show file tree
Hide file tree
Showing 16 changed files with 338 additions and 274 deletions.
2 changes: 2 additions & 0 deletions cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ commit_parsers = [
{ message = "^[fF]ix", group = "<!-- 2 -->🐛 Bug Fixes" },
{ message = "^[sS]tyle", group = "<!-- 3 -->🎨 Styling" },
{ message = "^[pP]erf", group = "<!-- 4 -->🚀 Performances" },
{ message = "^[rR]efactor", group = "<!-- 5 -->🔨 Refactor" },
{ message = "^[tT]est", group = "<!-- 6 -->🧪 Tests" },
{ message = "^[dD]oc", skip = true },
{ message = "^[rR]elease", skip = true },
{ message = "^[rR]evert", skip = true },
Expand Down
28 changes: 19 additions & 9 deletions src/GZCTF/ClientApp/src/Api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1260,6 +1260,11 @@ export interface ScoreboardModel {
items?: ScoreboardItem[];
/** 题目信息 */
challenges?: Record<string, ChallengeInfo[]>;
/**
* 题目数量
* @format int32
*/
challengeCount?: number;
}

export interface TopTimeLine {
Expand Down Expand Up @@ -1316,18 +1321,18 @@ export interface ScoreboardItem {
* @format int32
*/
organizationRank?: number | null;
/**
* 已解出的题目数量
* @format int32
*/
solvedCount?: number;
/**
* 得分时间
* @format date-time
*/
lastSubmissionTime?: string;
/** 题目情况列表 */
challenges?: ChallengeItem[];
/** 解出的题目列表 */
solvedChallenges?: ChallengeItem[];
/**
* 已解出的题目数量
* @format int32
*/
solvedCount?: number;
}

export interface ChallengeItem {
Expand All @@ -1349,7 +1354,7 @@ export interface ChallengeItem {
* 题目提交的时间,为了计算时间线
* @format date-time
*/
time?: string | null;
time?: string;
}

/** 提交类型 */
Expand Down Expand Up @@ -1382,7 +1387,7 @@ export interface ChallengeInfo {
*/
solved?: number;
/** 题目三血 */
bloods?: (Blood | null)[];
bloods?: Blood[];
}

export interface Blood {
Expand Down Expand Up @@ -1559,6 +1564,11 @@ export interface FileRecord {
export interface GameDetailModel {
/** 题目信息 */
challenges?: Record<string, ChallengeInfo[]>;
/**
* 题目数量
* @format int32
*/
challengeCount?: number;
/** 积分榜信息 */
rank?: ScoreboardItem | null;
/**
Expand Down
11 changes: 5 additions & 6 deletions src/GZCTF/ClientApp/src/components/ChallengePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,13 @@ const ChallengePanel: FC = () => {
})

const allChallenges = Object.values(challenges ?? {}).flat()

const currentChallenges =
challenges &&
(activeTab !== 'All' ? (challenges[activeTab] ?? []) : allChallenges).filter(
(chal) =>
!hideSolved ||
(teamInfo &&
teamInfo.rank?.challenges?.find((c) => c.id === chal.id)?.type ===
SubmissionType.Unaccepted)
(teamInfo && teamInfo.rank?.solvedChallenges?.find((c) => c.id === chal.id)) === undefined
)

const [challenge, setChallenge] = useState<ChallengeInfo | null>(null)
Expand Down Expand Up @@ -115,7 +114,7 @@ const ChallengePanel: FC = () => {

if (allChallenges.length === 0) {
return (
<Center h="calc(100vh - 100px)">
<Center h="calc(100vh - 100px)" w="100%">
<Empty
bordered
description={t('game.content.no_challenge')}
Expand Down Expand Up @@ -233,7 +232,7 @@ const ChallengePanel: FC = () => {
cols={{ base: 3, w18: 4, w24: 6, w30: 8, w36: 10, w42: 12, w48: 14 }}
>
{currentChallenges?.map((chal) => {
const status = teamInfo?.rank?.challenges?.find((c) => c.id === chal.id)?.type
const status = teamInfo?.rank?.solvedChallenges?.find((c) => c.id === chal.id)?.type
const solved = status !== SubmissionType.Unaccepted && status !== undefined

return (
Expand Down Expand Up @@ -278,7 +277,7 @@ const ChallengePanel: FC = () => {
withCloseButton={false}
onClose={() => setDetailOpened(false)}
gameEnded={dayjs(game?.end) < dayjs()}
status={teamInfo?.rank?.challenges?.find((c) => c.id === challenge?.id)?.type}
status={teamInfo?.rank?.solvedChallenges?.find((c) => c.id === challenge?.id)?.type}
tagData={challengeTagLabelMap.get((challenge?.tag as ChallengeTag) ?? ChallengeTag.Misc)!}
title={challenge?.title ?? ''}
score={challenge?.score ?? 0}
Expand Down
39 changes: 14 additions & 25 deletions src/GZCTF/ClientApp/src/components/MobileScoreboardItemModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
Input,
LoadingOverlay,
Modal,
ModalProps,
Progress,
ScrollArea,
Stack,
Expand All @@ -19,20 +18,15 @@ import dayjs from 'dayjs'
import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import TeamRadarMap from '@Components/TeamRadarMap'
import { BonusLabel } from '@Utils/Shared'
import { ChallengeInfo, ScoreboardItem, SubmissionType } from '@Api'
import { ChallengeInfo } from '@Api'
import tableClasses from '@Styles/Table.module.css'
import { ScoreboardItemModalProps } from './ScoreboardItemModal'

interface MobileScoreboardItemModalProps extends ModalProps {
item?: ScoreboardItem | null
bloodBonusMap: Map<SubmissionType, BonusLabel>
challenges?: Record<string, ChallengeInfo[]>
}

const MobileScoreboardItemModal: FC<MobileScoreboardItemModalProps> = (props) => {
const { item, challenges, ...modalProps } = props
const MobileScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => {
const { item, scoreboard, ...modalProps } = props
const { t } = useTranslation()

const challenges = scoreboard?.challenges
const challengeIdMap =
challenges &&
Object.keys(challenges).reduce((map, key) => {
Expand All @@ -42,7 +36,7 @@ const MobileScoreboardItemModal: FC<MobileScoreboardItemModalProps> = (props) =>
return map
}, new Map<number, ChallengeInfo>())

const solved = (item?.solvedCount ?? 0) / (item?.challenges?.length ?? 1)
const solved = (item?.solvedCount ?? 0) / (scoreboard?.challengeCount ?? 1)

const indicator =
challenges &&
Expand All @@ -52,16 +46,12 @@ const MobileScoreboardItemModal: FC<MobileScoreboardItemModalProps> = (props) =>
max: 1,
}))

const values = Array.from({ length: item?.challenges?.length ?? 0 }, () => 0)

item?.challenges?.forEach((chal) => {
if (indicator && challengeIdMap && chal) {
const challenge = challengeIdMap.get(chal.id!)
const index = challenge && indicator?.findIndex((ch) => ch.name === challenge.tag)
if (chal?.score && challenge?.score && index !== undefined && index !== -1) {
values[index] += challenge?.score / indicator[index].scoreSum
}
}
const values = indicator?.map((ind) => {
const solvedChallenges = item?.solvedChallenges?.filter(
(chal) => challengeIdMap?.get(chal.id!)?.tag === ind.name
)
const tagScore = solvedChallenges?.reduce((sum, chal) => sum + chal.score!, 0) ?? 0
return Math.min(tagScore / ind.scoreSum, 1)
})

return (
Expand Down Expand Up @@ -141,10 +131,9 @@ const MobileScoreboardItemModal: FC<MobileScoreboardItemModalProps> = (props) =>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{item?.challenges &&
{item?.solvedChallenges &&
challengeIdMap &&
item.challenges
.filter((c) => c.type !== SubmissionType.Unaccepted)
item.solvedChallenges
.sort((a, b) => dayjs(b.time).diff(dayjs(a.time)))
.map((chal) => {
const info = challengeIdMap.get(chal.id!)
Expand Down
6 changes: 3 additions & 3 deletions src/GZCTF/ClientApp/src/components/MobileScoreboardTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ import MobileScoreboardItemModal from '@Components/MobileScoreboardItemModal'
import { ScoreboardProps } from '@Components/ScoreboardTable'
import { BloodBonus, useBonusLabels } from '@Utils/Shared'
import { useGameScoreboard } from '@Utils/useGame'
import { ScoreboardItem, SubmissionType } from '@Api'
import { ScoreboardItem } from '@Api'
import classes from '@Styles/ScoreboardTable.module.css'

const TableRow: FC<{
item: ScoreboardItem
onOpenDetail: () => void
}> = ({ item, onOpenDetail }) => {
const theme = useMantineTheme()
const solved = item.challenges?.filter((c) => c.type !== SubmissionType.Unaccepted)
const solved = item.solvedChallenges
return (
<Table.Tr>
<Table.Td className={cx(classes.mono, classes.left)}>{item.rank}</Table.Td>
Expand Down Expand Up @@ -178,7 +178,7 @@ const MobileScoreboardTable: FC<ScoreboardProps> = ({ organization, setOrganizat
</Group>
</Stack>
<MobileScoreboardItemModal
challenges={scoreboard?.challenges}
scoreboard={scoreboard}
bloodBonusMap={bloodData}
opened={itemDetailOpened}
withCloseButton={false}
Expand Down
32 changes: 14 additions & 18 deletions src/GZCTF/ClientApp/src/components/ScoreboardItemModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,21 @@ import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import TeamRadarMap from '@Components/TeamRadarMap'
import { BloodsTypes, BonusLabel } from '@Utils/Shared'
import { ChallengeInfo, ScoreboardItem, SubmissionType } from '@Api'
import { ChallengeInfo, ScoreboardItem, ScoreboardModel, SubmissionType } from '@Api'
import tableClasses from '@Styles/Table.module.css'

interface ScoreboardItemModalProps extends ModalProps {
export interface ScoreboardItemModalProps extends ModalProps {
item?: ScoreboardItem | null
bloodBonusMap: Map<SubmissionType, BonusLabel>
challenges?: Record<string, ChallengeInfo[]>
scoreboard?: ScoreboardModel
}

const ScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => {
const { item, challenges, bloodBonusMap, ...modalProps } = props
const { item, scoreboard, bloodBonusMap, ...modalProps } = props

const { t } = useTranslation()

const challenges = scoreboard?.challenges
const challengeIdMap =
challenges &&
Object.keys(challenges).reduce((map, key) => {
Expand All @@ -41,7 +42,7 @@ const ScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => {
return map
}, new Map<number, ChallengeInfo>())

const solved = (item?.solvedCount ?? 0) / (item?.challenges?.length ?? 1)
const solved = (item?.solvedCount ?? 0) / (scoreboard?.challengeCount ?? 1)

const indicator =
challenges &&
Expand All @@ -51,16 +52,12 @@ const ScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => {
max: 1,
}))

const values = Array.from({ length: item?.challenges?.length ?? 0 }, () => 0)

item?.challenges?.forEach((chal) => {
if (indicator && challengeIdMap && chal) {
const challenge = challengeIdMap.get(chal.id!)
const index = challenge && indicator?.findIndex((ch) => ch.name === challenge.tag)
if (chal?.score && challenge?.score && index !== undefined && index !== -1) {
values[index] += challenge?.score / indicator[index].scoreSum
}
}
const values = indicator?.map((ind) => {
const solvedChallenges = item?.solvedChallenges?.filter(
(chal) => challengeIdMap?.get(chal.id!)?.tag === ind.name
)
const tagScore = solvedChallenges?.reduce((sum, chal) => sum + chal.score!, 0) ?? 0
return Math.min(tagScore / ind.scoreSum, 1)
})

return (
Expand Down Expand Up @@ -140,10 +137,9 @@ const ScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => {
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{item?.challenges &&
{item?.solvedChallenges &&
challengeIdMap &&
item.challenges
.filter((c) => c.type !== SubmissionType.Unaccepted)
item.solvedChallenges
.sort((a, b) => dayjs(b.time).diff(dayjs(a.time)))
.map((chal) => {
const info = challengeIdMap.get(chal.id!)!
Expand Down
4 changes: 2 additions & 2 deletions src/GZCTF/ClientApp/src/components/ScoreboardTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ const TableRow: FC<{
}> = ({ item, challenges, onOpenDetail, iconMap, tableRank, allRank }) => {
const theme = useMantineTheme()
const challengeTagLabelMap = useChallengeTagLabelMap()
const solved = item.challenges?.filter((c) => c.type !== SubmissionType.Unaccepted)
const solved = item.solvedChallenges

return (
<Table.Tr>
Expand Down Expand Up @@ -357,7 +357,7 @@ const ScoreboardTable: FC<ScoreboardProps> = ({ organization, setOrganization })
</Group>
</Stack>
<ScoreboardItemModal
challenges={scoreboard?.challenges}
scoreboard={scoreboard}
bloodBonusMap={bloodData}
opened={itemDetailOpened}
withCloseButton={false}
Expand Down
2 changes: 1 addition & 1 deletion src/GZCTF/ClientApp/src/components/TeamRank.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const TeamRank: FC<CardProps> = (props) => {

const { t } = useTranslation()

const solved = (teamInfo?.rank?.solvedCount ?? 0) / (teamInfo?.rank?.challenges?.length ?? 1)
const solved = (teamInfo?.rank?.solvedCount ?? 0) / (teamInfo?.challengeCount ?? 1)

useEffect(() => {
if (error?.status === ErrorCodes.GameEnded) {
Expand Down
2 changes: 1 addition & 1 deletion src/GZCTF/Controllers/GameController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,6 @@ public async Task<IActionResult> ChallengesWithTeamInfo([FromRoute] int id, Canc
: new()
{
Avatar = context.Participation!.Team.AvatarUrl,
SolvedCount = 0,
Rank = 0,
Name = context.Participation!.Team.Name,
Id = context.Participation!.TeamId
Expand All @@ -623,6 +622,7 @@ public async Task<IActionResult> ChallengesWithTeamInfo([FromRoute] int id, Canc
ScoreboardItem = boardItem,
TeamToken = context.Participation!.Token,
Challenges = scoreboard.Challenges,
ChallengeCount = scoreboard.ChallengeCount,
WriteupRequired = context.Game!.WriteupRequired,
WriteupDeadline = context.Game!.WriteupDeadline
});
Expand Down
4 changes: 2 additions & 2 deletions src/GZCTF/Middlewares/RateLimiter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ await context.HttpContext.Response.WriteAsJsonAsync(
});
options.AddTokenBucketLimiter(nameof(LimitPolicy.Submit), o =>
{
o.TokenLimit = 60;
o.TokensPerPeriod = 30;
o.TokenLimit = 100;
o.TokensPerPeriod = 50;
o.ReplenishmentPeriod = TimeSpan.FromSeconds(5);
});
}
Expand Down
5 changes: 5 additions & 0 deletions src/GZCTF/Models/Request/Game/GameDetailModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ public class GameDetailModel
/// </summary>
public Dictionary<ChallengeTag, IEnumerable<ChallengeInfo>> Challenges { get; set; } = new();

/// <summary>
/// 题目数量
/// </summary>
public int ChallengeCount { get; set; }

/// <summary>
/// 积分榜信息
/// </summary>
Expand Down
Loading

0 comments on commit 609de5f

Please sign in to comment.