diff --git a/src/GZCTF/ClientApp/src/Api.ts b/src/GZCTF/ClientApp/src/Api.ts index 73fe2f38c..8a8d66c6d 100644 --- a/src/GZCTF/ClientApp/src/Api.ts +++ b/src/GZCTF/ClientApp/src/Api.ts @@ -616,12 +616,12 @@ export interface ChallengeModel { id?: number; /** 题目名称 */ title?: string; - /** 题目标签 */ - tag?: ChallengeTag; + /** 题目类别 */ + category?: ChallengeCategory; } -/** 题目标签 */ -export enum ChallengeTag { +/** 题目类别 */ +export enum ChallengeCategory { Misc = "Misc", Crypto = "Crypto", Pwn = "Pwn", @@ -875,8 +875,8 @@ export interface ChallengeEditDetailModel { title: string; /** 题目内容 */ content?: string; - /** 题目标签 */ - tag: ChallengeTag; + /** 题目类别 */ + category: ChallengeCategory; /** 题目类型 */ type: ChallengeType; /** 题目提示 */ @@ -1023,8 +1023,8 @@ export interface ChallengeInfoModel { * @minLength 1 */ title: string; - /** 题目标签 */ - tag?: ChallengeTag; + /** 题目类别 */ + category?: ChallengeCategory; /** 题目类型 */ type?: ChallengeType; /** 是否启用题目 */ @@ -1060,8 +1060,8 @@ export interface ChallengeUpdateModel { * @maxLength 120 */ flagTemplate?: string | null; - /** 题目标签 */ - tag?: ChallengeTag | null; + /** 题目类别 */ + category?: ChallengeCategory | null; /** 题目提示 */ hints?: string[] | null; /** 是否启用题目 */ @@ -1376,8 +1376,8 @@ export interface ChallengeInfo { id?: number; /** 题目名称 */ title?: string; - /** 题目标签 */ - tag?: ChallengeTag; + /** 题目类别 */ + category?: ChallengeCategory; /** * 题目分值 * @format int32 @@ -1509,8 +1509,8 @@ export interface ChallengeTrafficModel { * @minLength 1 */ title: string; - /** 题目标签 */ - tag?: ChallengeTag; + /** 题目类别 */ + category?: ChallengeCategory; /** 题目类型 */ type?: ChallengeType; /** 是否启用题目 */ @@ -1640,8 +1640,8 @@ export interface ChallengeDetailModel { title?: string; /** 题目内容 */ content?: string; - /** 题目标签 */ - tag?: ChallengeTag; + /** 题目类别 */ + category?: ChallengeCategory; /** 题目提示 */ hints?: string[] | null; /** diff --git a/src/GZCTF/ClientApp/src/components/ChallengeCard.tsx b/src/GZCTF/ClientApp/src/components/ChallengeCard.tsx index 6f704d33a..1e010bb43 100644 --- a/src/GZCTF/ClientApp/src/components/ChallengeCard.tsx +++ b/src/GZCTF/ClientApp/src/components/ChallengeCard.tsx @@ -18,7 +18,7 @@ import cx from 'clsx' import dayjs from 'dayjs' import { FC } from 'react' import { Trans } from 'react-i18next' -import { BloodsTypes, PartialIconProps, useChallengeTagLabelMap } from '@Utils/Shared' +import { BloodsTypes, PartialIconProps, useChallengeCategoryLabelMap } from '@Utils/Shared' import { ChallengeInfo, SubmissionType } from '@Api' import classes from '@Styles/ChallengeCard.module.css' import hoverClasses from '@Styles/HoverCard.module.css' @@ -35,8 +35,8 @@ interface ChallengeCardProps { const ChallengeCard: FC = (props: ChallengeCardProps) => { const { challenge, solved, onClick, iconMap, teamId, colorMap } = props - const challengeTagLabelMap = useChallengeTagLabelMap() - const tagData = challengeTagLabelMap.get(challenge.tag!) + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() + const cateData = challengeCategoryLabelMap.get(challenge.category!) const theme = useMantineTheme() return ( @@ -53,7 +53,7 @@ const ChallengeCard: FC = (props: ChallengeCardProps) => { {challenge.title} - + {challenge.score} pts @@ -113,11 +113,11 @@ const ChallengeCard: FC = (props: ChallengeCardProps) => { - {tagData && ( + {cateData && ( )} diff --git a/src/GZCTF/ClientApp/src/components/ChallengeModal.tsx b/src/GZCTF/ClientApp/src/components/ChallengeModal.tsx index 11f58bc1f..ac6f05368 100644 --- a/src/GZCTF/ClientApp/src/components/ChallengeModal.tsx +++ b/src/GZCTF/ClientApp/src/components/ChallengeModal.tsx @@ -18,13 +18,13 @@ import { FC, useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import InstanceEntry from '@Components/InstanceEntry' import Markdown, { InlineMarkdown } from '@Components/MarkdownRenderer' -import { ChallengeTagItemProps } from '@Utils/Shared' +import { ChallengeCategoryItemProps } from '@Utils/Shared' import { ChallengeDetailModel, ChallengeType } from '@Api' import classes from '@Styles/ChallengeModal.module.css' export interface ChallengeModalProps extends ModalProps { challenge?: ChallengeDetailModel - tagData: ChallengeTagItemProps + cateData: ChallengeCategoryItemProps solved?: boolean disabled?: boolean flag: string @@ -39,7 +39,7 @@ export interface ChallengeModalProps extends ModalProps { const ChallengeModal: FC = (props) => { const { challenge, - tagData, + cateData, solved, disabled, flag, @@ -72,8 +72,8 @@ const ChallengeModal: FC = (props) => { - {tagData && ( - + {cateData && ( + )} {challenge?.title ?? ''} @@ -83,7 +83,7 @@ const ChallengeModal: FC<ChallengeModalProps> = (props) => { {challenge?.score ?? 0} pts </Text> </Group> - <Divider size="md" color={tagData?.color} /> + <Divider size="md" color={cateData?.color} /> </Stack> ) diff --git a/src/GZCTF/ClientApp/src/components/ChallengePanel.tsx b/src/GZCTF/ClientApp/src/components/ChallengePanel.tsx index 8e951b35b..ba932f5f6 100644 --- a/src/GZCTF/ClientApp/src/components/ChallengePanel.tsx +++ b/src/GZCTF/ClientApp/src/components/ChallengePanel.tsx @@ -24,9 +24,9 @@ import ChallengeCard from '@Components/ChallengeCard' import Empty from '@Components/Empty' import GameChallengeModal from '@Components/GameChallengeModal' import WriteupSubmitModal from '@Components/WriteupSubmitModal' -import { useChallengeTagLabelMap, SubmissionTypeIconMap } from '@Utils/Shared' +import { useChallengeCategoryLabelMap, SubmissionTypeIconMap } from '@Utils/Shared' import { useGame, useGameTeamInfo } from '@Utils/useGame' -import { ChallengeInfo, ChallengeTag, SubmissionType } from '@Api' +import { ChallengeInfo, ChallengeCategory, SubmissionType } from '@Api' import classes from '@Styles/ChallengePanel.module.css' const ChallengePanel: FC = () => { @@ -38,8 +38,8 @@ const ChallengePanel: FC = () => { const { game } = useGame(numId) - const tags = Object.keys(challenges ?? {}) - const [activeTab, setActiveTab] = useState<ChallengeTag | 'All'>('All') + const categories = Object.keys(challenges ?? {}) + const [activeTab, setActiveTab] = useState<ChallengeCategory | 'All'>('All') const [hideSolved, setHideSolved] = useLocalStorage({ key: 'hide-solved', defaultValue: false, @@ -60,7 +60,7 @@ const ChallengePanel: FC = () => { const [detailOpened, setDetailOpened] = useState(false) const { iconMap, colorMap } = SubmissionTypeIconMap(0.8) const [writeupSubmitOpened, setWriteupSubmitOpened] = useState(false) - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const { t } = useTranslation() // skeleton for loading @@ -157,7 +157,7 @@ const ChallengePanel: FC = () => { orientation="vertical" variant="pills" value={activeTab} - onChange={(value) => setActiveTab(value as ChallengeTag)} + onChange={(value) => setActiveTab(value as ChallengeCategory)} classNames={{ list: classes.tabList, tabLabel: classes.tabLabel, @@ -175,8 +175,8 @@ const ChallengePanel: FC = () => { </Text> </Group> </Tabs.Tab> - {tags.map((tab) => { - const data = challengeTagLabelMap.get(tab as ChallengeTag)! + {categories.map((tab) => { + const data = challengeCategoryLabelMap.get(tab as ChallengeCategory)! return ( <Tabs.Tab key={tab} @@ -268,7 +268,11 @@ const ChallengePanel: FC = () => { onClose={() => setDetailOpened(false)} gameEnded={dayjs(game?.end) < dayjs()} status={teamInfo?.rank?.solvedChallenges?.find((c) => c.id === challenge?.id)?.type} - tagData={challengeTagLabelMap.get((challenge?.tag as ChallengeTag) ?? ChallengeTag.Misc)!} + cateData={ + challengeCategoryLabelMap.get( + (challenge?.category as ChallengeCategory) ?? ChallengeCategory.Misc + )! + } title={challenge?.title ?? ''} score={challenge?.score ?? 0} challengeId={challenge.id} diff --git a/src/GZCTF/ClientApp/src/components/GameChallengeModal.tsx b/src/GZCTF/ClientApp/src/components/GameChallengeModal.tsx index 4463aa899..3b460f47a 100644 --- a/src/GZCTF/ClientApp/src/components/GameChallengeModal.tsx +++ b/src/GZCTF/ClientApp/src/components/GameChallengeModal.tsx @@ -7,13 +7,13 @@ import React, { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import ChallengeModal from '@Components/ChallengeModal' import { showErrorNotification } from '@Utils/ApiHelper' -import { ChallengeTagItemProps } from '@Utils/Shared' +import { ChallengeCategoryItemProps } from '@Utils/Shared' import api, { AnswerResult, ChallengeType, SubmissionType } from '@Api' interface GameChallengeModalProps extends ModalProps { gameId: number gameEnded: boolean - tagData: ChallengeTagItemProps + cateData: ChallengeCategoryItemProps title: string score: number challengeId: number @@ -21,7 +21,7 @@ interface GameChallengeModalProps extends ModalProps { } const GameChallengeModal: FC<GameChallengeModalProps> = (props) => { - const { gameId, gameEnded, challengeId, tagData, status, title, score, ...modalProps } = props + const { gameId, gameEnded, challengeId, cateData, status, title, score, ...modalProps } = props const { data: challenge, mutate } = api.game.useGameGetChallenge(gameId, challengeId, { refreshInterval: 120 * 1000, @@ -216,7 +216,7 @@ const GameChallengeModal: FC<GameChallengeModalProps> = (props) => { <ChallengeModal {...modalProps} challenge={challenge ?? { title, score }} - tagData={tagData} + cateData={cateData} solved={status !== SubmissionType.Unaccepted && status !== undefined} flag={flag} setFlag={setFlag} diff --git a/src/GZCTF/ClientApp/src/components/MobileScoreboardItemModal.tsx b/src/GZCTF/ClientApp/src/components/MobileScoreboardItemModal.tsx index 17a2aa65f..c141454b6 100644 --- a/src/GZCTF/ClientApp/src/components/MobileScoreboardItemModal.tsx +++ b/src/GZCTF/ClientApp/src/components/MobileScoreboardItemModal.tsx @@ -40,18 +40,18 @@ const MobileScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => { const indicator = challenges && - Object.keys(challenges).map((tag) => ({ - name: tag, - scoreSum: challenges[tag].reduce((sum, chal) => sum + (!chal.solved ? 0 : chal.score!), 0), + Object.keys(challenges).map((cate) => ({ + name: cate, + scoreSum: challenges[cate].reduce((sum, chal) => sum + (!chal.solved ? 0 : chal.score!), 0), max: 1, })) const values = indicator?.map((ind) => { const solvedChallenges = item?.solvedChallenges?.filter( - (chal) => challengeIdMap?.get(chal.id!)?.tag === ind.name + (chal) => challengeIdMap?.get(chal.id!)?.category === ind.name ) - const tagScore = solvedChallenges?.reduce((sum, chal) => sum + chal.score!, 0) ?? 0 - return Math.min(tagScore / ind.scoreSum, 1) + const cateScore = solvedChallenges?.reduce((sum, chal) => sum + chal.score!, 0) ?? 0 + return Math.min(cateScore / ind.scoreSum, 1) }) return ( diff --git a/src/GZCTF/ClientApp/src/components/ScoreboardItemModal.tsx b/src/GZCTF/ClientApp/src/components/ScoreboardItemModal.tsx index c0dc66be8..37599d147 100644 --- a/src/GZCTF/ClientApp/src/components/ScoreboardItemModal.tsx +++ b/src/GZCTF/ClientApp/src/components/ScoreboardItemModal.tsx @@ -48,18 +48,18 @@ const ScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => { const indicator = challenges && - Object.keys(challenges).map((tag) => ({ - name: tag, - scoreSum: challenges[tag].reduce((sum, chal) => sum + (!chal.solved ? 0 : chal.score!), 0), + Object.keys(challenges).map((cate) => ({ + name: cate, + scoreSum: challenges[cate].reduce((sum, chal) => sum + (!chal.solved ? 0 : chal.score!), 0), max: 1, })) const values = indicator?.map((ind) => { const solvedChallenges = item?.solvedChallenges?.filter( - (chal) => challengeIdMap?.get(chal.id!)?.tag === ind.name + (chal) => challengeIdMap?.get(chal.id!)?.category === ind.name ) - const tagScore = solvedChallenges?.reduce((sum, chal) => sum + chal.score!, 0) ?? 0 - return Math.min(tagScore / ind.scoreSum, 1) + const cateScore = solvedChallenges?.reduce((sum, chal) => sum + chal.score!, 0) ?? 0 + return Math.min(cateScore / ind.scoreSum, 1) }) return ( @@ -166,7 +166,7 @@ const ScoreboardItemModal: FC<ScoreboardItemModalProps> = (props) => { /> </Table.Td> <Table.Td ff="monospace" fz="sm"> - {info.tag} + {info.category} </Table.Td> <Table.Td ff="monospace" fz="sm"> {chal.score} diff --git a/src/GZCTF/ClientApp/src/components/ScoreboardTable.tsx b/src/GZCTF/ClientApp/src/components/ScoreboardTable.tsx index b5d5a43e3..2f5e6a16e 100644 --- a/src/GZCTF/ClientApp/src/components/ScoreboardTable.tsx +++ b/src/GZCTF/ClientApp/src/components/ScoreboardTable.tsx @@ -25,13 +25,13 @@ import ScoreboardItemModal from '@Components/ScoreboardItemModal' import { BloodBonus, BloodsTypes, - useChallengeTagLabelMap, + useChallengeCategoryLabelMap, SubmissionTypeIconMap, useBonusLabels, PartialIconProps, } from '@Utils/Shared' import { useGameScoreboard } from '@Utils/useGame' -import { ChallengeInfo, ChallengeTag, ScoreboardItem, SubmissionType } from '@Api' +import { ChallengeInfo, ChallengeCategory, ScoreboardItem, SubmissionType } from '@Api' import classes from '@Styles/ScoreboardTable.module.css' import tooltipClasses from '@Styles/Tooltip.module.css' @@ -45,7 +45,7 @@ const TableHeader = (table: Record<string, ChallengeInfo[]>) => { const theme = useMantineTheme() const { colorScheme } = useMantineColorScheme() const { t } = useTranslation() - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const hiddenCol = [...Array(5).keys()].map((i) => ( <Table.Th @@ -64,11 +64,10 @@ const TableHeader = (table: Record<string, ChallengeInfo[]>) => { return ( <Table.Thead className={classes.thead}> - {/* Challenge Tag */} <Table.Tr style={{ border: 'none' }}> {hiddenCol} {Object.keys(table).map((key) => { - const tag = challengeTagLabelMap.get(key as ChallengeTag)! + const cate = challengeCategoryLabelMap.get(key as ChallengeCategory)! return ( <Table.Th key={key} @@ -76,18 +75,18 @@ const TableHeader = (table: Record<string, ChallengeInfo[]>) => { h="3rem" style={{ backgroundColor: alpha( - theme.colors[tag.color][colorScheme === 'dark' ? 8 : 6], + theme.colors[cate.color][colorScheme === 'dark' ? 8 : 6], colorScheme === 'dark' ? 0.15 : 0.2 ), }} > <Group gap={4} wrap="nowrap" justify="center" w="100%"> <Icon - path={tag.icon} + path={cate.icon} size={1} - color={theme.colors[tag.color][colorScheme === 'dark' ? 8 : 6]} + color={theme.colors[cate.color][colorScheme === 'dark' ? 8 : 6]} /> - <Text c={tag.color} className={classes.text} ff="text" fz="sm"> + <Text c={cate.color} className={classes.text} ff="text" fz="sm"> {key} </Text> </Group> @@ -140,7 +139,7 @@ const TableRow: FC<{ challenges?: Record<string, ChallengeInfo[]> }> = ({ item, challenges, onOpenDetail, iconMap, tableRank, allRank }) => { const theme = useMantineTheme() - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const solved = item.solvedChallenges return ( @@ -190,7 +189,7 @@ const TableRow: FC<{ if (!icon) return <Table.Td key={item.id} className={classes.mono} /> - const tag = challengeTagLabelMap.get(item.tag as ChallengeTag)! + const cate = challengeCategoryLabelMap.get(item.category as ChallengeCategory)! return ( <Table.Td key={item.id} className={classes.mono}> @@ -202,7 +201,7 @@ const TableRow: FC<{ <Text lineClamp={3} fz="xs" className={classes.text}> {item.title} </Text> - <Text c={tag.color} fz="xs" className={classes.text}> + <Text c={cate.color} fz="xs" className={classes.text}> + {chal?.score} pts </Text> <Text c="dimmed" fz="xs" className={classes.text}> diff --git a/src/GZCTF/ClientApp/src/components/TrafficItems.tsx b/src/GZCTF/ClientApp/src/components/TrafficItems.tsx index 77b427707..e5aacee00 100644 --- a/src/GZCTF/ClientApp/src/components/TrafficItems.tsx +++ b/src/GZCTF/ClientApp/src/components/TrafficItems.tsx @@ -10,10 +10,10 @@ import { SelectableItemComponent, SelectableItemProps, } from '@Components/ScrollSelect' -import { useChallengeTagLabelMap, HunamizeSize } from '@Utils/Shared' +import { useChallengeCategoryLabelMap, HunamizeSize } from '@Utils/Shared' import { useDisplayInputStyles } from '@Utils/ThemeOverride' import { - ChallengeTag, + ChallengeCategory, ChallengeTrafficModel, ChallengeType, FileRecord, @@ -25,8 +25,8 @@ const itemHeight = rem(60) export const ChallengeItem: SelectableItemComponent<ChallengeTrafficModel> = (itemProps) => { const { item, ...props } = itemProps - const challengeTagLabelMap = useChallengeTagLabelMap() - const data = challengeTagLabelMap.get(item.tag as ChallengeTag)! + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() + const data = challengeCategoryLabelMap.get(item.category as ChallengeCategory)! const theme = useMantineTheme() const type = item.type === ChallengeType.DynamicContainer ? 'dyn' : 'sta' const { classes } = useDisplayInputStyles({ fw: 'bold' }) diff --git a/src/GZCTF/ClientApp/src/components/admin/ChallengeCreateModal.tsx b/src/GZCTF/ClientApp/src/components/admin/ChallengeCreateModal.tsx index cb9aad303..9e93ef8d6 100644 --- a/src/GZCTF/ClientApp/src/components/admin/ChallengeCreateModal.tsx +++ b/src/GZCTF/ClientApp/src/components/admin/ChallengeCreateModal.tsx @@ -8,13 +8,13 @@ import { useTranslation } from 'react-i18next' import { useNavigate, useParams } from 'react-router-dom' import { showErrorNotification } from '@Utils/ApiHelper' import { - ChallengeTagItem, - ChallengeTagList, + ChallengeCategoryItem, + ChallengeCategoryList, ChallengeTypeItem, - useChallengeTagLabelMap, + useChallengeCategoryLabelMap, useChallengeTypeLabelMap, } from '@Utils/Shared' -import api, { ChallengeInfoModel, ChallengeTag, ChallengeType } from '@Api' +import api, { ChallengeInfoModel, ChallengeCategory, ChallengeType } from '@Api' interface ChallengeCreateModalProps extends ModalProps { onAddChallenge: (game: ChallengeInfoModel) => void @@ -25,17 +25,17 @@ const ChallengeCreateModal: FC<ChallengeCreateModalProps> = (props) => { const { onAddChallenge, ...modalProps } = props const [disabled, setDisabled] = useState(false) const navigate = useNavigate() - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const challengeTypeLabelMap = useChallengeTypeLabelMap() const [title, setTitle] = useInputState('') - const [tag, setTag] = useState<string | null>(null) + const [category, setCategory] = useState<string | null>(null) const [type, setType] = useState<string | null>(null) const { t } = useTranslation() const onCreate = () => { - if (!title || !tag || !type) return + if (!title || !category || !type) return setDisabled(true) const numId = parseInt(id ?? '-1') @@ -43,7 +43,7 @@ const ChallengeCreateModal: FC<ChallengeCreateModalProps> = (props) => { api.edit .editAddGameChallenge(numId, { title: title, - tag: tag as ChallengeTag, + category: category as ChallengeCategory, type: type as ChallengeType, }) .then((data) => { @@ -74,14 +74,14 @@ const ChallengeCreateModal: FC<ChallengeCreateModalProps> = (props) => { /> <Select required - label={t('admin.content.games.challenges.tag')} - placeholder="Tag" - value={tag} - onChange={setTag} - renderOption={ChallengeTagItem} - data={ChallengeTagList.map((tag) => { - const data = challengeTagLabelMap.get(tag) - return { value: tag, label: data?.name, ...data } as ComboboxItem + label={t('admin.content.games.challenges.category')} + placeholder="Category" + value={category} + onChange={setCategory} + renderOption={ChallengeCategoryItem} + data={ChallengeCategoryList.map((category) => { + const data = challengeCategoryLabelMap.get(category) + return { value: category, label: data?.name, ...data } as ComboboxItem })} /> <Select diff --git a/src/GZCTF/ClientApp/src/components/admin/ChallengeEditCard.tsx b/src/GZCTF/ClientApp/src/components/admin/ChallengeEditCard.tsx index 6683fbb5e..8f74a69af 100644 --- a/src/GZCTF/ClientApp/src/components/admin/ChallengeEditCard.tsx +++ b/src/GZCTF/ClientApp/src/components/admin/ChallengeEditCard.tsx @@ -15,8 +15,8 @@ import { Icon } from '@mdi/react' import { Dispatch, FC, SetStateAction, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate, useParams } from 'react-router-dom' -import { useChallengeTagLabelMap } from '@Utils/Shared' -import { ChallengeInfoModel, ChallengeTag } from '@Api' +import { useChallengeCategoryLabelMap } from '@Utils/Shared' +import { ChallengeInfoModel, ChallengeCategory } from '@Api' interface ChallengeEditCardProps { challenge: ChallengeInfoModel @@ -24,8 +24,8 @@ interface ChallengeEditCardProps { } const ChallengeEditCard: FC<ChallengeEditCardProps> = ({ challenge, onToggle }) => { - const challengeTagLabelMap = useChallengeTagLabelMap() - const data = challengeTagLabelMap.get(challenge.tag as ChallengeTag) + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() + const data = challengeCategoryLabelMap.get(challenge.category as ChallengeCategory) const theme = useMantineTheme() const navigate = useNavigate() const { id } = useParams() diff --git a/src/GZCTF/ClientApp/src/components/admin/ChallengePreviewModal.tsx b/src/GZCTF/ClientApp/src/components/admin/ChallengePreviewModal.tsx index 6a1815576..ea1a040ca 100644 --- a/src/GZCTF/ClientApp/src/components/admin/ChallengePreviewModal.tsx +++ b/src/GZCTF/ClientApp/src/components/admin/ChallengePreviewModal.tsx @@ -7,12 +7,12 @@ import dayjs from 'dayjs' import React, { FC, useState } from 'react' import { useTranslation } from 'react-i18next' import ChallengeModal from '@Components/ChallengeModal' -import { ChallengeTagItemProps } from '@Utils/Shared' +import { ChallengeCategoryItemProps } from '@Utils/Shared' import { ChallengeDetailModel } from '@Api' interface ChallengePreviewModalProps extends ModalProps { challenge: ChallengeDetailModel - tagData: ChallengeTagItemProps + cateData: ChallengeCategoryItemProps } interface FakeContext { @@ -22,7 +22,7 @@ interface FakeContext { } const ChallengePreviewModal: FC<ChallengePreviewModalProps> = (props) => { - const { challenge, tagData, ...modalProps } = props + const { challenge, cateData, ...modalProps } = props const [context, setContext] = useState<FakeContext>({ closeTime: null, @@ -79,7 +79,7 @@ const ChallengePreviewModal: FC<ChallengePreviewModalProps> = (props) => { <ChallengeModal {...modalProps} challenge={{ ...challenge, context: context }} - tagData={tagData} + cateData={cateData} flag={flag} setFlag={setFlag} onCreate={onCreate} diff --git a/src/GZCTF/ClientApp/src/components/admin/WithChallengeEdit.tsx b/src/GZCTF/ClientApp/src/components/admin/WithChallengeEdit.tsx index 5663e7f0f..2a9887477 100644 --- a/src/GZCTF/ClientApp/src/components/admin/WithChallengeEdit.tsx +++ b/src/GZCTF/ClientApp/src/components/admin/WithChallengeEdit.tsx @@ -4,9 +4,9 @@ import Icon from '@mdi/react' import { FC } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate, useParams, useLocation } from 'react-router-dom' -import { useChallengeTagLabelMap } from '@Utils/Shared' +import { useChallengeCategoryLabelMap } from '@Utils/Shared' import { useEditChallenges } from '@Utils/useEdit' -import { ChallengeInfoModel, ChallengeTag } from '@Api' +import { ChallengeInfoModel, ChallengeCategory } from '@Api' import WithGameEditTab, { GameEditTabProps } from './WithGameEditTab' const WithChallengeEdit: FC<GameEditTabProps> = (props) => { @@ -31,12 +31,13 @@ const WithChallengeEdit: FC<GameEditTabProps> = (props) => { const { previous, current, next } = challenges ? getBeforeNext(challenges, numCId) : { previous: null, current: null, next: null } - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const color = (chal: ChallengeInfoModel | null) => { const c = !chal ? theme.primaryColor - : (challengeTagLabelMap.get(chal.tag as ChallengeTag)?.color ?? theme.primaryColor) + : (challengeCategoryLabelMap.get(chal.category as ChallengeCategory)?.color ?? + theme.primaryColor) return c } diff --git a/src/GZCTF/ClientApp/src/locales/en_US/admin.json b/src/GZCTF/ClientApp/src/locales/en_US/admin.json index 3ec741282..2f7c016c7 100644 --- a/src/GZCTF/ClientApp/src/locales/en_US/admin.json +++ b/src/GZCTF/ClientApp/src/locales/en_US/admin.json @@ -96,6 +96,7 @@ "second_blood": "Second Blood Reward (%)", "third_blood": "Third Blood Reward (%)" }, + "category": "Category", "container_image": "Container Image", "cpu_limit": { "description": "Multiplication of 0.1 CPU core", @@ -150,7 +151,6 @@ "description": "Storage (including image)", "label": "Storage Limit (MB)" }, - "tag": "Tag", "title": "Title", "traffic_capture": { "description": "Capturing the traffic when the team solves challenges. Platform proxy needs to be enabled", diff --git a/src/GZCTF/ClientApp/src/locales/en_US/challenge.json b/src/GZCTF/ClientApp/src/locales/en_US/challenge.json index e19c97964..2d7764138 100644 --- a/src/GZCTF/ClientApp/src/locales/en_US/challenge.json +++ b/src/GZCTF/ClientApp/src/locales/en_US/challenge.json @@ -15,6 +15,21 @@ }, "submit_flag": "Submit Flag" }, + "category": { + "ai": "AI security", + "blockchain": "Blockchain", + "crypto": "Cryptography", + "forensics": "Digital forensics", + "hardware": "Hardware security", + "misc": "Miscellaneous", + "mobile": "Mobile security", + "osint": "Open-source intelligence", + "pentest": "Penetration testing", + "ppc": "Programming", + "pwn": "Binary exploitation", + "reverse": "Reverse engineering", + "web": "Web security" + }, "content": { "already_solved": "This challenge has been solved", "flag_placeholders": [ @@ -142,21 +157,6 @@ } } }, - "tag": { - "ai": "AI security", - "blockchain": "Blockchain", - "crypto": "Cryptography", - "forensics": "Digital forensics", - "hardware": "Hardware security", - "misc": "Miscellaneous", - "mobile": "Mobile security", - "osint": "Open-source intelligence", - "pentest": "Penetration testing", - "ppc": "Programming", - "pwn": "Binary exploitation", - "reverse": "Reverse engineering", - "web": "Web security" - }, "type": { "dynamic_attachment": { "desrc": "Distribute attachments by team, ensuring that the number of attachments is greater than the number of teams", diff --git a/src/GZCTF/ClientApp/src/locales/ja_JP/admin.json b/src/GZCTF/ClientApp/src/locales/ja_JP/admin.json index 009fa670a..deb848b80 100644 --- a/src/GZCTF/ClientApp/src/locales/ja_JP/admin.json +++ b/src/GZCTF/ClientApp/src/locales/ja_JP/admin.json @@ -96,6 +96,7 @@ "second_blood": "セカンドブラッドボーナス (%)", "third_blood": "サードブラッドボーナス (%)" }, + "category": "カテゴリ", "container_image": "コンテナイメージ", "cpu_limit": { "description": "0.1を掛けると、CPUのコア数になります", @@ -150,7 +151,6 @@ "description": "ストレージ容量の制限、イメージサイズも含みます", "label": "ストレージ上限 (MB)" }, - "tag": "タグ", "title": "タイトル", "traffic_capture": { "description": "チームの解答トラフィックをキャプチャするには、プラットフォームプロキシを有効にする必要があります", diff --git a/src/GZCTF/ClientApp/src/locales/ja_JP/challenge.json b/src/GZCTF/ClientApp/src/locales/ja_JP/challenge.json index e43955b57..46a3cee5b 100644 --- a/src/GZCTF/ClientApp/src/locales/ja_JP/challenge.json +++ b/src/GZCTF/ClientApp/src/locales/ja_JP/challenge.json @@ -15,6 +15,21 @@ }, "submit_flag": "フラッグを提出" }, + "category": { + "ai": "AIセキュリティ", + "blockchain": "ブロックチェーン", + "crypto": "暗号学", + "forensics": "デジタル・フォレンジック", + "hardware": "ハードウェアセキュリティ", + "misc": "その他", + "mobile": "モバイルセキュリティ", + "osint": "オープンソース情報", + "pentest": "ペネトレーションテスト", + "ppc": "プログラミング", + "pwn": "エクスプロイト", + "reverse": "リバースエンジニアリング", + "web": "ウェブセキュリティ" + }, "content": { "already_solved": "このチャレンジはすでに解かれています", "flag_placeholders": [ @@ -142,21 +157,6 @@ } } }, - "tag": { - "ai": "AIセキュリティ", - "blockchain": "ブロックチェーン", - "crypto": "暗号学", - "forensics": "デジタル・フォレンジック", - "hardware": "ハードウェアセキュリティ", - "misc": "その他", - "mobile": "モバイルセキュリティ", - "osint": "オープンソース情報", - "pentest": "ペネトレーションテスト", - "ppc": "プログラミング", - "pwn": "エクスプロイト", - "reverse": "リバースエンジニアリング", - "web": "ウェブセキュリティ" - }, "type": { "dynamic_attachment": { "desrc": "チームごとにアタッチメントを配布するので、アタッチメントの数はチームの数よりも多くする必要があります", diff --git a/src/GZCTF/ClientApp/src/locales/zh_CN/admin.json b/src/GZCTF/ClientApp/src/locales/zh_CN/admin.json index 052b19535..f99b156a1 100644 --- a/src/GZCTF/ClientApp/src/locales/zh_CN/admin.json +++ b/src/GZCTF/ClientApp/src/locales/zh_CN/admin.json @@ -96,6 +96,7 @@ "second_blood": "二血奖励 (%)", "third_blood": "三血奖励 (%)" }, + "category": "题目类别", "container_image": "容器镜像", "cpu_limit": { "description": "乘以 0.1 即为 CPU 核心数", @@ -150,7 +151,6 @@ "description": "限制存储空间,含镜像大小", "label": "存储限制 (MB)" }, - "tag": "题目标签", "title": "题目标题", "traffic_capture": { "description": "捕获队伍解题流量,需要开启平台代理", diff --git a/src/GZCTF/ClientApp/src/locales/zh_CN/challenge.json b/src/GZCTF/ClientApp/src/locales/zh_CN/challenge.json index 1d1f9c00e..2bc6311e7 100644 --- a/src/GZCTF/ClientApp/src/locales/zh_CN/challenge.json +++ b/src/GZCTF/ClientApp/src/locales/zh_CN/challenge.json @@ -15,6 +15,21 @@ }, "submit_flag": "提交 flag" }, + "category": { + "ai": "人工智能安全", + "blockchain": "区块链", + "crypto": "密码学", + "forensics": "数字取证", + "hardware": "硬件安全", + "misc": "安全杂项", + "mobile": "移动安全", + "osint": "开源情报", + "pentest": "渗透测试", + "ppc": "专业编程", + "pwn": "二进制漏洞利用", + "reverse": "逆向工程", + "web": "网络安全" + }, "content": { "already_solved": "该题目已被解出", "flag_placeholders": [ @@ -142,21 +157,6 @@ } } }, - "tag": { - "ai": "人工智能安全", - "blockchain": "区块链", - "crypto": "密码学", - "forensics": "数字取证", - "hardware": "硬件安全", - "misc": "安全杂项", - "mobile": "移动安全", - "osint": "开源情报", - "pentest": "渗透测试", - "ppc": "专业编程", - "pwn": "二进制漏洞利用", - "reverse": "逆向工程", - "web": "网络安全" - }, "type": { "dynamic_attachment": { "desrc": "按照队伍分发附件,需保证附件数量大于队伍数", diff --git a/src/GZCTF/ClientApp/src/pages/admin/Instances.tsx b/src/GZCTF/ClientApp/src/pages/admin/Instances.tsx index cf41df5ff..b38725d7c 100644 --- a/src/GZCTF/ClientApp/src/pages/admin/Instances.tsx +++ b/src/GZCTF/ClientApp/src/pages/admin/Instances.tsx @@ -30,8 +30,8 @@ import { Trans, useTranslation } from 'react-i18next' import { ActionIconWithConfirm } from '@Components/ActionIconWithConfirm' import AdminPage from '@Components/admin/AdminPage' import { showErrorNotification } from '@Utils/ApiHelper' -import { useChallengeTagLabelMap, getProxyUrl } from '@Utils/Shared' -import api, { ChallengeModel, ChallengeTag, TeamModel } from '@Api' +import { useChallengeCategoryLabelMap, getProxyUrl } from '@Utils/Shared' +import api, { ChallengeModel, ChallengeCategory, TeamModel } from '@Api' import tableClasses from '@Styles/Table.module.css' import tooltipClasses from '@Styles/Tooltip.module.css' @@ -54,14 +54,14 @@ const SelectTeamItem: SelectProps['renderOption'] = ({ option }) => { } const SelectChallengeItem: SelectProps['renderOption'] = ({ option }) => { - const { title, id, tag } = option as SelectChallengeItemProps - const challengeTagLabelMap = useChallengeTagLabelMap() - const tagInfo = challengeTagLabelMap.get(tag ?? ChallengeTag.Misc)! + const { title, id, category } = option as SelectChallengeItemProps + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() + const cateData = challengeCategoryLabelMap.get(category ?? ChallengeCategory.Misc)! const theme = useMantineTheme() return ( <Group wrap="nowrap" gap="sm"> - <Icon color={theme.colors[tagInfo.color][4]} path={tagInfo.icon} size={1} /> + <Icon color={theme.colors[cateData.color][4]} path={cateData.icon} size={1} /> <Text fw={500} size="sm" lineClamp={1} style={{ wordBreak: 'break-all' }}> <Text span c="dimmed"> {`#${id} `} @@ -82,7 +82,7 @@ const Instances: FC = () => { const [challenge, setChallenge] = useState<ChallengeModel[]>() const [disabled, setDisabled] = useState(false) const clipBoard = useClipboard() - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const { t } = useTranslation() @@ -221,8 +221,8 @@ const Instances: FC = () => { <Table.Tbody> {filteredInstances && filteredInstances.map((inst) => { - const color = challengeTagLabelMap.get( - inst.challenge?.tag ?? ChallengeTag.Misc + const color = challengeCategoryLabelMap.get( + inst.challenge?.category ?? ChallengeCategory.Misc )!.color return ( <Table.Tr key={inst.containerGuid}> diff --git a/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/Index.tsx b/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/Index.tsx index 91e45fc43..cba7bef38 100644 --- a/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/Index.tsx +++ b/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/Index.tsx @@ -22,9 +22,13 @@ import ChallengeCreateModal from '@Components/admin/ChallengeCreateModal' import ChallengeEditCard from '@Components/admin/ChallengeEditCard' import WithGameEditTab from '@Components/admin/WithGameEditTab' import { showErrorNotification } from '@Utils/ApiHelper' -import { ChallengeTagItem, ChallengeTagList, useChallengeTagLabelMap } from '@Utils/Shared' +import { + ChallengeCategoryItem, + ChallengeCategoryList, + useChallengeCategoryLabelMap, +} from '@Utils/Shared' import { useEditChallenges } from '@Utils/useEdit' -import api, { ChallengeInfoModel, ChallengeTag } from '@Api' +import api, { ChallengeInfoModel, ChallengeCategory } from '@Api' const GameChallengeEdit: FC = () => { const { id } = useParams() @@ -32,8 +36,8 @@ const GameChallengeEdit: FC = () => { const [createOpened, setCreateOpened] = useState(false) const [bonusOpened, setBonusOpened] = useState(false) - const [category, setCategory] = useState<ChallengeTag | null>(null) - const challengeTagLabelMap = useChallengeTagLabelMap() + const [category, setCategory] = useState<ChallengeCategory | null>(null) + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const [disabled, setDisabled] = useState(false) const { t } = useTranslation() @@ -41,7 +45,7 @@ const GameChallengeEdit: FC = () => { const { challenges, mutate } = useEditChallenges(numId) const filteredChallenges = - category && challenges ? challenges?.filter((c) => c.tag === category) : challenges + category && challenges ? challenges?.filter((c) => c.category === category) : challenges const modals = useModals() @@ -126,11 +130,11 @@ const GameChallengeEdit: FC = () => { w="16rem" value={category} nothingFoundMessage={t('admin.content.nothing_found')} - onChange={(value) => setCategory(value as ChallengeTag | null)} - renderOption={ChallengeTagItem} - data={ChallengeTagList.map((tag) => { - const data = challengeTagLabelMap.get(tag) - return { value: tag, label: data?.name, ...data } as ComboboxItem + onChange={(value) => setCategory(value as ChallengeCategory | null)} + renderOption={ChallengeCategoryItem} + data={ChallengeCategoryList.map((cate) => { + const data = challengeCategoryLabelMap.get(cate) + return { value: cate, label: data?.name, ...data } as ComboboxItem })} /> <Group justify="right"> diff --git a/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx b/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx index 103392fc5..0e784fc72 100644 --- a/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx +++ b/src/GZCTF/ClientApp/src/pages/admin/games/[id]/challenges/[chalId]/Index.tsx @@ -36,14 +36,14 @@ import { SwitchLabel } from '@Components/admin/SwitchLabel' import WithChallengeEdit from '@Components/admin/WithChallengeEdit' import { showErrorNotification } from '@Utils/ApiHelper' import { - ChallengeTagItem, - useChallengeTagLabelMap, + ChallengeCategoryItem, + useChallengeCategoryLabelMap, ChallengeTypeItem, useChallengeTypeLabelMap, - ChallengeTagList, + ChallengeCategoryList, } from '@Utils/Shared' import { useEditChallenge, useEditChallenges } from '@Utils/useEdit' -import api, { ChallengeTag, ChallengeType, ChallengeUpdateModel } from '@Api' +import api, { ChallengeCategory, ChallengeType, ChallengeUpdateModel } from '@Api' const GameChallengeEdit: FC = () => { const navigate = useNavigate() @@ -57,14 +57,16 @@ const GameChallengeEdit: FC = () => { const [disabled, setDisabled] = useState(false) const [minRate, setMinRate] = useState((challenge?.minScoreRate ?? 0.25) * 100) - const [tag, setTag] = useState<string | null>(challenge?.tag ?? ChallengeTag.Misc) + const [category, setCategory] = useState<string | null>( + challenge?.category ?? ChallengeCategory.Misc + ) const [type, setType] = useState<string | null>(challenge?.type ?? ChallengeType.StaticAttachment) const [currentAcceptCount, setCurrentAcceptCount] = useState(0) const [previewOpened, setPreviewOpened] = useState(false) const modals = useModals() const challengeTypeLabelMap = useChallengeTypeLabelMap() - const challengeTagLabelMap = useChallengeTagLabelMap() + const challengeCategoryLabelMap = useChallengeCategoryLabelMap() const { colorScheme } = useMantineColorScheme() const { t } = useTranslation() @@ -72,7 +74,7 @@ const GameChallengeEdit: FC = () => { useEffect(() => { if (challenge) { setChallengeInfo({ ...challenge }) - setTag(challenge.tag) + setCategory(challenge.category) setType(challenge.type) setMinRate((challenge?.minScoreRate ?? 0.25) * 100) setCurrentAcceptCount(challenge.acceptedCount) @@ -169,7 +171,7 @@ const GameChallengeEdit: FC = () => { onUpdate( { ...challengeInfo, - tag: tag as ChallengeTag, + category: category as ChallengeCategory, minScoreRate: minRate / 100, }, true @@ -236,7 +238,7 @@ const GameChallengeEdit: FC = () => { onClick={() => onUpdate({ ...challengeInfo, - tag: tag as ChallengeTag, + category: category as ChallengeCategory, minScoreRate: minRate / 100, }) } @@ -282,18 +284,18 @@ const GameChallengeEdit: FC = () => { <Grid.Col span={1}> <Select required - label={t('admin.content.games.challenges.tag')} - placeholder="Tag" - value={tag} + label={t('admin.content.games.challenges.category')} + placeholder="Category" + value={category} disabled={disabled} onChange={(e) => { - setTag(e) - setChallengeInfo({ ...challengeInfo, tag: e as ChallengeTag }) + setCategory(e) + setChallengeInfo({ ...challengeInfo, category: e as ChallengeCategory }) }} - renderOption={ChallengeTagItem} - data={ChallengeTagList.map((tag) => { - const data = challengeTagLabelMap.get(tag) - return { value: tag, label: data?.name, ...data } as ComboboxItem + renderOption={ChallengeCategoryItem} + data={ChallengeCategoryList.map((category) => { + const data = challengeCategoryLabelMap.get(category) + return { value: category, label: data?.name, ...data } as ComboboxItem })} /> </Grid.Col> @@ -532,13 +534,15 @@ const GameChallengeEdit: FC = () => { content: tryDefault([challengeInfo?.content, challenge?.content]), hints: tryDefault([challengeInfo?.hints, challenge?.hints], []), score: tryDefault([challengeInfo?.originalScore, challenge?.originalScore], 500), - tag: tag as ChallengeTag, + category: category as ChallengeCategory, type: challenge?.type ?? ChallengeType.StaticAttachment, }} opened={previewOpened} onClose={() => setPreviewOpened(false)} - tagData={ - challengeTagLabelMap.get((challengeInfo?.tag as ChallengeTag) ?? ChallengeTag.Misc)! + cateData={ + challengeCategoryLabelMap.get( + (challengeInfo?.category as ChallengeCategory) ?? ChallengeCategory.Misc + )! } /> </WithChallengeEdit> diff --git a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/Traffic.tsx b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/Traffic.tsx index f0881b5e3..248a98fc2 100644 --- a/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/Traffic.tsx +++ b/src/GZCTF/ClientApp/src/pages/games/[id]/monitor/Traffic.tsx @@ -138,7 +138,7 @@ const Traffic: FC = () => { const srollHeight = 'calc(100vh - 174px)' const headerHeight = rem(32) - challengeTraffic?.sort((a, b) => a.tag?.localeCompare(b.tag ?? '') ?? 0) + challengeTraffic?.sort((a, b) => a.category?.localeCompare(b.category ?? '') ?? 0) teamTraffic?.sort((a, b) => (a.teamId ?? 0) - (b.teamId ?? 0)) return ( diff --git a/src/GZCTF/ClientApp/src/pages/posts/[postId]/edit.tsx b/src/GZCTF/ClientApp/src/pages/posts/[postId]/edit.tsx index b742abe85..b9a0577d5 100644 --- a/src/GZCTF/ClientApp/src/pages/posts/[postId]/edit.tsx +++ b/src/GZCTF/ClientApp/src/pages/posts/[postId]/edit.tsx @@ -154,7 +154,7 @@ const PostEdit: FC = () => { onChange={(e) => setPost({ ...post, title: e.currentTarget.value })} /> <TagsInput - label={t('post.label.tag')} + label={t('post.label.category')} data={tags.map((o) => ({ value: o, label: o })) || []} placeholder={t('post.label.add_tag')} value={post?.tags ?? []} diff --git a/src/GZCTF/ClientApp/src/styles/shared/Typography.module.css b/src/GZCTF/ClientApp/src/styles/shared/Typography.module.css index 2fea45adb..261051916 100644 --- a/src/GZCTF/ClientApp/src/styles/shared/Typography.module.css +++ b/src/GZCTF/ClientApp/src/styles/shared/Typography.module.css @@ -141,7 +141,7 @@ & :global(.token.deleted), & :global(.token.entity), & :global(.token.selector), - & :global(.token.tag), + & :global(.token.category), & :global(.token.url), & :global(.token.variable) { color: light-dark(#e53935, #ff6666); diff --git a/src/GZCTF/ClientApp/src/utils/Shared.tsx b/src/GZCTF/ClientApp/src/utils/Shared.tsx index ba23c8e9e..249bb8268 100644 --- a/src/GZCTF/ClientApp/src/utils/Shared.tsx +++ b/src/GZCTF/ClientApp/src/utils/Shared.tsx @@ -40,7 +40,7 @@ import { import { Icon } from '@mdi/react' import { useTranslation } from 'react-i18next' import { - ChallengeTag, + ChallengeCategory, ChallengeType, NoticeType, ParticipationStatus, @@ -103,141 +103,141 @@ export const ChallengeTypeItem: SelectProps['renderOption'] = ({ option }) => { ) } -export const ChallengeTagList = Object.values(ChallengeTag) +export const ChallengeCategoryList = Object.values(ChallengeCategory) -export const useChallengeTagLabelMap = () => { +export const useChallengeCategoryLabelMap = () => { const { t } = useTranslation() const theme = useMantineTheme() const { colorScheme } = useMantineColorScheme() const revert = colorScheme === 'dark' ? 'light' : 'dark' - return new Map<ChallengeTag, ChallengeTagItemProps>([ + return new Map<ChallengeCategory, ChallengeCategoryItemProps>([ [ - ChallengeTag.Misc, + ChallengeCategory.Misc, { - desrc: t('challenge.tag.misc'), + desrc: t('challenge.category.misc'), icon: mdiGamepadVariantOutline, - name: ChallengeTag.Misc, + name: ChallengeCategory.Misc, color: 'teal', colors: theme.colors['teal'], }, ], [ - ChallengeTag.Pwn, + ChallengeCategory.Pwn, { - desrc: t('challenge.tag.pwn'), + desrc: t('challenge.category.pwn'), icon: mdiBomb, - name: ChallengeTag.Pwn, + name: ChallengeCategory.Pwn, color: 'red', colors: theme.colors['red'], }, ], [ - ChallengeTag.Web, + ChallengeCategory.Web, { - desrc: t('challenge.tag.web'), + desrc: t('challenge.category.web'), icon: mdiWeb, - name: ChallengeTag.Web, + name: ChallengeCategory.Web, color: 'blue', colors: theme.colors['blue'], }, ], [ - ChallengeTag.Reverse, + ChallengeCategory.Reverse, { - desrc: t('challenge.tag.reverse'), + desrc: t('challenge.category.reverse'), icon: mdiChevronTripleLeft, - name: ChallengeTag.Reverse, + name: ChallengeCategory.Reverse, color: 'yellow', colors: theme.colors['yellow'], }, ], [ - ChallengeTag.Crypto, + ChallengeCategory.Crypto, { - desrc: t('challenge.tag.crypto'), + desrc: t('challenge.category.crypto'), icon: mdiMatrix, - name: ChallengeTag.Crypto, + name: ChallengeCategory.Crypto, color: 'violet', colors: theme.colors['violet'], }, ], [ - ChallengeTag.Blockchain, + ChallengeCategory.Blockchain, { - desrc: t('challenge.tag.blockchain'), + desrc: t('challenge.category.blockchain'), icon: mdiEthereum, - name: ChallengeTag.Blockchain, + name: ChallengeCategory.Blockchain, color: 'green', colors: theme.colors['green'], }, ], [ - ChallengeTag.Forensics, + ChallengeCategory.Forensics, { - desrc: t('challenge.tag.forensics'), + desrc: t('challenge.category.forensics'), icon: mdiFingerprint, - name: ChallengeTag.Forensics, + name: ChallengeCategory.Forensics, color: 'indigo', colors: theme.colors['indigo'], }, ], [ - ChallengeTag.Hardware, + ChallengeCategory.Hardware, { - desrc: t('challenge.tag.hardware'), + desrc: t('challenge.category.hardware'), icon: mdiChip, - name: ChallengeTag.Hardware, + name: ChallengeCategory.Hardware, color: revert, colors: theme.colors[revert], }, ], [ - ChallengeTag.Mobile, + ChallengeCategory.Mobile, { - desrc: t('challenge.tag.mobile'), + desrc: t('challenge.category.mobile'), icon: mdiCellphoneCog, - name: ChallengeTag.Mobile, + name: ChallengeCategory.Mobile, color: 'pink', colors: theme.colors['pink'], }, ], [ - ChallengeTag.PPC, + ChallengeCategory.PPC, { - desrc: t('challenge.tag.ppc'), + desrc: t('challenge.category.ppc'), icon: mdiConsole, - name: ChallengeTag.PPC, + name: ChallengeCategory.PPC, color: 'cyan', colors: theme.colors['cyan'], }, ], [ - ChallengeTag.AI, + ChallengeCategory.AI, { - desrc: t('challenge.tag.ai'), + desrc: t('challenge.category.ai'), icon: mdiRobotLoveOutline, - name: ChallengeTag.AI, + name: ChallengeCategory.AI, color: 'lime', colors: theme.colors['lime'], }, ], [ - ChallengeTag.OSINT, + ChallengeCategory.OSINT, { - desrc: t('challenge.tag.osint'), + desrc: t('challenge.category.osint'), icon: mdiSearchWeb, - name: ChallengeTag.OSINT, + name: ChallengeCategory.OSINT, color: 'orange', colors: theme.colors['orange'], }, ], [ - ChallengeTag.Pentest, + ChallengeCategory.Pentest, { - desrc: t('challenge.tag.pentest'), + desrc: t('challenge.category.pentest'), icon: mdiLanPending, - name: ChallengeTag.Pentest, + name: ChallengeCategory.Pentest, color: 'grape', colors: theme.colors['grape'], }, @@ -245,18 +245,18 @@ export const useChallengeTagLabelMap = () => { ]) } -export interface ChallengeTagItemProps { - name: ChallengeTag +export interface ChallengeCategoryItemProps { + name: ChallengeCategory desrc: string icon: string color: string colors: MantineColorsTuple } -type SelectChallengeTagItemProps = ChallengeTagItemProps & ComboboxItem +type SelectChallengeCategoryItemProps = ChallengeCategoryItemProps & ComboboxItem -export const ChallengeTagItem: SelectProps['renderOption'] = ({ option }) => { - const { colors, icon, name, desrc } = option as SelectChallengeTagItemProps +export const ChallengeCategoryItem: SelectProps['renderOption'] = ({ option }) => { + const { colors, icon, name, desrc } = option as SelectChallengeCategoryItemProps return ( <Group wrap="nowrap"> diff --git a/src/GZCTF/ClientApp/src/utils/useEdit.ts b/src/GZCTF/ClientApp/src/utils/useEdit.ts index 5680bbae1..9411b90b1 100644 --- a/src/GZCTF/ClientApp/src/utils/useEdit.ts +++ b/src/GZCTF/ClientApp/src/utils/useEdit.ts @@ -19,7 +19,9 @@ export const useEditChallenges = (numId: number) => { useEffect(() => { if (data) { - setSortedChallenges(data.toSorted((a, b) => ((a.tag ?? '') > (b.tag ?? '') ? -1 : 1))) + setSortedChallenges( + data.toSorted((a, b) => ((a.category ?? '') > (b.category ?? '') ? -1 : 1)) + ) } }, [data]) diff --git a/src/GZCTF/Controllers/EditController.cs b/src/GZCTF/Controllers/EditController.cs index f7305aa8f..d00a1e708 100644 --- a/src/GZCTF/Controllers/EditController.cs +++ b/src/GZCTF/Controllers/EditController.cs @@ -463,7 +463,7 @@ public async Task<IActionResult> AddGameChallenge([FromRoute] int id, [FromBody] StatusCodes.Status404NotFound)); GameChallenge res = await challengeRepository.CreateChallenge(game, - new GameChallenge { Title = model.Title, Type = model.Type, Tag = model.Tag }, token); + new GameChallenge { Title = model.Title, Type = model.Type, Category = model.Category }, token); return Ok(ChallengeEditDetailModel.FromChallenge(res)); } diff --git a/src/GZCTF/Migrations/20240913074608_RenameTagToCategory.Designer.cs b/src/GZCTF/Migrations/20240913074608_RenameTagToCategory.Designer.cs new file mode 100644 index 000000000..26294bf92 --- /dev/null +++ b/src/GZCTF/Migrations/20240913074608_RenameTagToCategory.Designer.cs @@ -0,0 +1,1620 @@ +// <auto-generated /> +using System; +using GZCTF.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace GZCTF.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20240913074608_RenameTagToCategory")] + partial class RenameTagToCategory + { + /// <inheritdoc /> + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("GZCTF.Models.Data.Attachment", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int?>("LocalFileId") + .HasColumnType("integer"); + + b.Property<string>("RemoteUrl") + .HasColumnType("text"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("LocalFileId"); + + b.ToTable("Attachments"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.CheatInfo", b => + { + b.Property<int>("SubmissionId") + .HasColumnType("integer"); + + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<int>("SourceTeamId") + .HasColumnType("integer"); + + b.Property<int>("SubmitTeamId") + .HasColumnType("integer"); + + b.HasKey("SubmissionId"); + + b.HasIndex("GameId"); + + b.HasIndex("SourceTeamId"); + + b.HasIndex("SubmissionId") + .IsUnique(); + + b.HasIndex("SubmitTeamId"); + + b.ToTable("CheatInfo"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Config", b => + { + b.Property<string>("ConfigKey") + .HasColumnType("text"); + + b.Property<string>("Value") + .HasColumnType("text"); + + b.HasKey("ConfigKey"); + + b.ToTable("Configs"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Container", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("ContainerId") + .IsRequired() + .HasColumnType("text"); + + b.Property<int?>("ExerciseInstanceId") + .HasColumnType("integer"); + + b.Property<DateTimeOffset>("ExpectStopAt") + .HasColumnType("timestamp with time zone"); + + b.Property<int?>("GameInstanceId") + .HasColumnType("integer"); + + b.Property<string>("IP") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Image") + .IsRequired() + .HasColumnType("text"); + + b.Property<bool>("IsProxy") + .HasColumnType("boolean"); + + b.Property<int>("Port") + .HasColumnType("integer"); + + b.Property<string>("PublicIP") + .HasColumnType("text"); + + b.Property<int?>("PublicPort") + .HasColumnType("integer"); + + b.Property<DateTimeOffset>("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property<byte>("Status") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("ExerciseInstanceId") + .IsUnique(); + + b.HasIndex("GameInstanceId") + .IsUnique(); + + b.ToTable("Containers"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseChallenge", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int>("AcceptedCount") + .HasColumnType("integer"); + + b.Property<int?>("AttachmentId") + .HasColumnType("integer"); + + b.Property<int?>("CPUCount") + .HasColumnType("integer"); + + b.Property<byte>("Category") + .HasColumnType("smallint"); + + b.Property<Guid>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("uuid"); + + b.Property<int?>("ContainerExposePort") + .HasColumnType("integer"); + + b.Property<string>("ContainerImage") + .HasColumnType("text"); + + b.Property<string>("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property<bool>("Credit") + .HasColumnType("boolean"); + + b.Property<byte>("Difficulty") + .HasColumnType("smallint"); + + b.Property<string>("FileName") + .HasColumnType("text"); + + b.Property<string>("FlagTemplate") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property<string>("Hints") + .HasColumnType("text"); + + b.Property<bool>("IsEnabled") + .HasColumnType("boolean"); + + b.Property<int?>("MemoryLimit") + .HasColumnType("integer"); + + b.Property<int?>("StorageLimit") + .HasColumnType("integer"); + + b.Property<int>("SubmissionCount") + .HasColumnType("integer"); + + b.Property<string>("Tags") + .HasColumnType("text"); + + b.Property<Guid?>("TestContainerId") + .HasColumnType("uuid"); + + b.Property<string>("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AttachmentId"); + + b.HasIndex("TestContainerId"); + + b.ToTable("ExerciseChallenges"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseDependency", b => + { + b.Property<int>("SourceId") + .HasColumnType("integer"); + + b.Property<int>("TargetId") + .HasColumnType("integer"); + + b.HasKey("SourceId", "TargetId"); + + b.HasIndex("SourceId"); + + b.HasIndex("TargetId"); + + b.ToTable("ExerciseDependencies"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseInstance", b => + { + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<int>("ExerciseId") + .HasColumnType("integer"); + + b.Property<Guid?>("ContainerId") + .HasColumnType("uuid"); + + b.Property<int?>("FlagId") + .HasColumnType("integer"); + + b.Property<bool>("IsLoaded") + .HasColumnType("boolean"); + + b.Property<bool>("IsSolved") + .HasColumnType("boolean"); + + b.Property<DateTimeOffset>("LastContainerOperation") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTimeOffset>("SolveTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("UserId", "ExerciseId"); + + b.HasIndex("ContainerId") + .IsUnique(); + + b.HasIndex("ExerciseId"); + + b.HasIndex("FlagId"); + + b.HasIndex("UserId"); + + b.ToTable("ExerciseInstances"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.FlagContext", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int?>("AttachmentId") + .HasColumnType("integer"); + + b.Property<int?>("ChallengeId") + .HasColumnType("integer"); + + b.Property<int?>("ExerciseId") + .HasColumnType("integer"); + + b.Property<string>("Flag") + .IsRequired() + .HasMaxLength(127) + .HasColumnType("character varying(127)"); + + b.Property<bool>("IsOccupied") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("AttachmentId"); + + b.HasIndex("ChallengeId"); + + b.HasIndex("ExerciseId"); + + b.ToTable("FlagContexts"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Game", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<bool>("AcceptWithoutReview") + .HasColumnType("boolean"); + + b.Property<long>("BloodBonusValue") + .HasColumnType("bigint") + .HasColumnName("BloodBonus"); + + b.Property<int>("ContainerCountLimit") + .HasColumnType("integer"); + + b.Property<string>("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property<DateTimeOffset>("EndTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "end"); + + b.Property<bool>("Hidden") + .HasColumnType("boolean"); + + b.Property<string>("InviteCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property<string>("Organizations") + .HasColumnType("text"); + + b.Property<string>("PosterHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property<bool>("PracticeMode") + .HasColumnType("boolean"); + + b.Property<string>("PrivateKey") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property<string>("PublicKey") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property<DateTimeOffset>("StartTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "start"); + + b.Property<string>("Summary") + .IsRequired() + .HasColumnType("text"); + + b.Property<int>("TeamMemberCountLimit") + .HasColumnType("integer"); + + b.Property<string>("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property<DateTimeOffset>("WriteupDeadline") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("WriteupNote") + .IsRequired() + .HasColumnType("text"); + + b.Property<bool>("WriteupRequired") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Games"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameChallenge", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int>("AcceptedCount") + .HasColumnType("integer"); + + b.Property<int?>("AttachmentId") + .HasColumnType("integer"); + + b.Property<int?>("CPUCount") + .HasColumnType("integer"); + + b.Property<byte>("Category") + .HasColumnType("smallint"); + + b.Property<Guid>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("uuid"); + + b.Property<int?>("ContainerExposePort") + .HasColumnType("integer"); + + b.Property<string>("ContainerImage") + .HasColumnType("text"); + + b.Property<string>("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property<double>("Difficulty") + .HasColumnType("double precision"); + + b.Property<bool>("EnableTrafficCapture") + .HasColumnType("boolean"); + + b.Property<string>("FileName") + .HasColumnType("text"); + + b.Property<string>("FlagTemplate") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<string>("Hints") + .HasColumnType("text"); + + b.Property<bool>("IsEnabled") + .HasColumnType("boolean"); + + b.Property<int?>("MemoryLimit") + .HasColumnType("integer"); + + b.Property<double>("MinScoreRate") + .HasColumnType("double precision"); + + b.Property<int>("OriginalScore") + .HasColumnType("integer"); + + b.Property<int?>("StorageLimit") + .HasColumnType("integer"); + + b.Property<int>("SubmissionCount") + .HasColumnType("integer"); + + b.Property<Guid?>("TestContainerId") + .HasColumnType("uuid"); + + b.Property<string>("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AttachmentId"); + + b.HasIndex("GameId"); + + b.HasIndex("TestContainerId"); + + b.ToTable("GameChallenges"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameEvent", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<DateTimeOffset>("PublishTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "time"); + + b.Property<int>("TeamId") + .HasColumnType("integer"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.Property<string>("Values") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("TeamId"); + + b.HasIndex("UserId"); + + b.ToTable("GameEvents"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameInstance", b => + { + b.Property<int>("ChallengeId") + .HasColumnType("integer"); + + b.Property<int>("ParticipationId") + .HasColumnType("integer"); + + b.Property<Guid?>("ContainerId") + .HasColumnType("uuid"); + + b.Property<int?>("FlagId") + .HasColumnType("integer"); + + b.Property<bool>("IsLoaded") + .HasColumnType("boolean"); + + b.Property<bool>("IsSolved") + .HasColumnType("boolean"); + + b.Property<DateTimeOffset>("LastContainerOperation") + .HasColumnType("timestamp with time zone"); + + b.HasKey("ChallengeId", "ParticipationId"); + + b.HasIndex("ContainerId") + .IsUnique(); + + b.HasIndex("FlagId"); + + b.HasIndex("ParticipationId"); + + b.ToTable("GameInstances"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameNotice", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<DateTimeOffset>("PublishTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "time"); + + b.Property<byte>("Type") + .HasColumnType("smallint"); + + b.Property<string>("Values") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.ToTable("GameNotices"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.LocalFile", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<long>("FileSize") + .HasColumnType("bigint"); + + b.Property<string>("Hash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property<string>("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property<long>("ReferenceCount") + .HasColumnType("bigint"); + + b.Property<DateTimeOffset>("UploadTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Hash"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.LogModel", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("Exception") + .HasColumnType("text"); + + b.Property<string>("Level") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property<string>("Logger") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property<string>("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("RemoteIP") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property<string>("Status") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property<DateTimeOffset>("TimeUtc") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("UserName") + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Participation", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<string>("Organization") + .HasColumnType("text"); + + b.Property<int>("Status") + .HasColumnType("integer"); + + b.Property<int>("TeamId") + .HasColumnType("integer"); + + b.Property<string>("Token") + .IsRequired() + .HasColumnType("text"); + + b.Property<int?>("WriteupId") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.HasIndex("TeamId"); + + b.HasIndex("WriteupId"); + + b.HasIndex("TeamId", "GameId"); + + b.ToTable("Participations"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Post", b => + { + b.Property<string>("Id") + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property<Guid?>("AuthorId") + .HasColumnType("uuid"); + + b.Property<string>("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property<bool>("IsPinned") + .HasColumnType("boolean"); + + b.Property<string>("Summary") + .IsRequired() + .HasColumnType("text"); + + b.Property<string>("Tags") + .HasColumnType("text"); + + b.Property<string>("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property<DateTimeOffset>("UpdateTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Submission", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("Answer") + .IsRequired() + .HasMaxLength(127) + .HasColumnType("character varying(127)"); + + b.Property<int>("ChallengeId") + .HasColumnType("integer"); + + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<int>("ParticipationId") + .HasColumnType("integer"); + + b.Property<string>("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property<DateTimeOffset>("SubmitTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "time"); + + b.Property<int>("TeamId") + .HasColumnType("integer"); + + b.Property<Guid?>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("ChallengeId"); + + b.HasIndex("GameId"); + + b.HasIndex("ParticipationId"); + + b.HasIndex("UserId"); + + b.HasIndex("TeamId", "ChallengeId", "GameId"); + + b.ToTable("Submissions"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Team", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("AvatarHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property<string>("Bio") + .HasMaxLength(72) + .HasColumnType("character varying(72)"); + + b.Property<Guid>("CaptainId") + .HasColumnType("uuid"); + + b.Property<string>("InviteToken") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property<bool>("Locked") + .HasColumnType("boolean"); + + b.Property<string>("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.HasKey("Id"); + + b.HasIndex("CaptainId"); + + b.ToTable("Teams"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.UserInfo", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<int>("AccessFailedCount") + .HasColumnType("integer"); + + b.Property<string>("AvatarHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property<string>("Bio") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property<string>("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<bool>("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property<bool>("ExerciseVisible") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property<string>("IP") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property<DateTimeOffset>("LastSignedInUtc") + .HasColumnType("timestamp with time zone"); + + b.Property<DateTimeOffset>("LastVisitedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property<bool>("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property<DateTimeOffset?>("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property<string>("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("PasswordHash") + .HasColumnType("text"); + + b.Property<string>("PhoneNumber") + .HasColumnType("text"); + + b.Property<bool>("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property<string>("RealName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property<DateTimeOffset>("RegisterTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.Property<int>("Role") + .HasColumnType("integer"); + + b.Property<string>("SecurityStamp") + .HasColumnType("text"); + + b.Property<string>("StdNumber") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property<bool>("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property<string>("UserName") + .HasMaxLength(16) + .HasColumnType("character varying(16)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("GZCTF.Models.Data.UserParticipation", b => + { + b.Property<int>("GameId") + .HasColumnType("integer"); + + b.Property<int>("TeamId") + .HasColumnType("integer"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<int>("ParticipationId") + .HasColumnType("integer"); + + b.HasKey("GameId", "TeamId", "UserId"); + + b.HasIndex("ParticipationId"); + + b.HasIndex("TeamId"); + + b.HasIndex("UserId", "GameId") + .IsUnique(); + + b.ToTable("UserParticipations"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.DataProtectionKey", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("FriendlyName") + .HasColumnType("text"); + + b.Property<string>("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b => + { + b.Property<Guid>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property<string>("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property<string>("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property<string>("NormalizedName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("ClaimType") + .HasColumnType("text"); + + b.Property<string>("ClaimValue") + .HasColumnType("text"); + + b.Property<Guid>("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b => + { + b.Property<int>("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); + + b.Property<string>("ClaimType") + .HasColumnType("text"); + + b.Property<string>("ClaimValue") + .HasColumnType("text"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b => + { + b.Property<string>("LoginProvider") + .HasColumnType("text"); + + b.Property<string>("ProviderKey") + .HasColumnType("text"); + + b.Property<string>("ProviderDisplayName") + .HasColumnType("text"); + + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b => + { + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<Guid>("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b => + { + b.Property<Guid>("UserId") + .HasColumnType("uuid"); + + b.Property<string>("LoginProvider") + .HasColumnType("text"); + + b.Property<string>("Name") + .HasColumnType("text"); + + b.Property<string>("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("TeamUserInfo", b => + { + b.Property<Guid>("MembersId") + .HasColumnType("uuid"); + + b.Property<int>("TeamsId") + .HasColumnType("integer"); + + b.HasKey("MembersId", "TeamsId"); + + b.HasIndex("TeamsId"); + + b.ToTable("TeamUserInfo"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Attachment", b => + { + b.HasOne("GZCTF.Models.Data.LocalFile", "LocalFile") + .WithMany() + .HasForeignKey("LocalFileId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("LocalFile"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.CheatInfo", b => + { + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Participation", "SourceTeam") + .WithMany() + .HasForeignKey("SourceTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Submission", "Submission") + .WithMany() + .HasForeignKey("SubmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Participation", "SubmitTeam") + .WithMany() + .HasForeignKey("SubmitTeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("SourceTeam"); + + b.Navigation("Submission"); + + b.Navigation("SubmitTeam"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseChallenge", b => + { + b.HasOne("GZCTF.Models.Data.Attachment", "Attachment") + .WithMany() + .HasForeignKey("AttachmentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.Container", "TestContainer") + .WithMany() + .HasForeignKey("TestContainerId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Attachment"); + + b.Navigation("TestContainer"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseDependency", b => + { + b.HasOne("GZCTF.Models.Data.ExerciseChallenge", "Source") + .WithMany() + .HasForeignKey("SourceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.ExerciseChallenge", "Target") + .WithMany() + .HasForeignKey("TargetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Source"); + + b.Navigation("Target"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseInstance", b => + { + b.HasOne("GZCTF.Models.Data.Container", "Container") + .WithOne("ExerciseInstance") + .HasForeignKey("GZCTF.Models.Data.ExerciseInstance", "ContainerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.ExerciseChallenge", "Exercise") + .WithMany() + .HasForeignKey("ExerciseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.FlagContext", "FlagContext") + .WithMany() + .HasForeignKey("FlagId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.UserInfo", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Container"); + + b.Navigation("Exercise"); + + b.Navigation("FlagContext"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.FlagContext", b => + { + b.HasOne("GZCTF.Models.Data.Attachment", "Attachment") + .WithMany() + .HasForeignKey("AttachmentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.GameChallenge", "Challenge") + .WithMany("Flags") + .HasForeignKey("ChallengeId"); + + b.HasOne("GZCTF.Models.Data.ExerciseChallenge", "Exercise") + .WithMany("Flags") + .HasForeignKey("ExerciseId"); + + b.Navigation("Attachment"); + + b.Navigation("Challenge"); + + b.Navigation("Exercise"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameChallenge", b => + { + b.HasOne("GZCTF.Models.Data.Attachment", "Attachment") + .WithMany() + .HasForeignKey("AttachmentId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany("Challenges") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Container", "TestContainer") + .WithMany() + .HasForeignKey("TestContainerId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Attachment"); + + b.Navigation("Game"); + + b.Navigation("TestContainer"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameEvent", b => + { + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany("GameEvents") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.UserInfo", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("Game"); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameInstance", b => + { + b.HasOne("GZCTF.Models.Data.GameChallenge", "Challenge") + .WithMany("Instances") + .HasForeignKey("ChallengeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Container", "Container") + .WithOne("GameInstance") + .HasForeignKey("GZCTF.Models.Data.GameInstance", "ContainerId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.FlagContext", "FlagContext") + .WithMany() + .HasForeignKey("FlagId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("GZCTF.Models.Data.Participation", "Participation") + .WithMany("Instances") + .HasForeignKey("ParticipationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Challenge"); + + b.Navigation("Container"); + + b.Navigation("FlagContext"); + + b.Navigation("Participation"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameNotice", b => + { + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany("GameNotices") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Participation", b => + { + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany("Participations") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Team", "Team") + .WithMany("Participations") + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.LocalFile", "Writeup") + .WithMany() + .HasForeignKey("WriteupId"); + + b.Navigation("Game"); + + b.Navigation("Team"); + + b.Navigation("Writeup"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Post", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", "Author") + .WithMany() + .HasForeignKey("AuthorId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Author"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Submission", b => + { + b.HasOne("GZCTF.Models.Data.GameChallenge", "GameChallenge") + .WithMany("Submissions") + .HasForeignKey("ChallengeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany("Submissions") + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Participation", "Participation") + .WithMany("Submissions") + .HasForeignKey("ParticipationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.UserInfo", "User") + .WithMany("Submissions") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Game"); + + b.Navigation("GameChallenge"); + + b.Navigation("Participation"); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Team", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", "Captain") + .WithMany() + .HasForeignKey("CaptainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Captain"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.UserParticipation", b => + { + b.HasOne("GZCTF.Models.Data.Game", "Game") + .WithMany() + .HasForeignKey("GameId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Participation", "Participation") + .WithMany("Members") + .HasForeignKey("ParticipationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Team", "Team") + .WithMany() + .HasForeignKey("TeamId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.UserInfo", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Game"); + + b.Navigation("Participation"); + + b.Navigation("Team"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TeamUserInfo", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("MembersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("GZCTF.Models.Data.Team", null) + .WithMany() + .HasForeignKey("TeamsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Container", b => + { + b.Navigation("ExerciseInstance"); + + b.Navigation("GameInstance"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseChallenge", b => + { + b.Navigation("Flags"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Game", b => + { + b.Navigation("Challenges"); + + b.Navigation("GameEvents"); + + b.Navigation("GameNotices"); + + b.Navigation("Participations"); + + b.Navigation("Submissions"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameChallenge", b => + { + b.Navigation("Flags"); + + b.Navigation("Instances"); + + b.Navigation("Submissions"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Participation", b => + { + b.Navigation("Instances"); + + b.Navigation("Members"); + + b.Navigation("Submissions"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Team", b => + { + b.Navigation("Participations"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.UserInfo", b => + { + b.Navigation("Submissions"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/GZCTF/Migrations/20240913074608_RenameTagToCategory.cs b/src/GZCTF/Migrations/20240913074608_RenameTagToCategory.cs new file mode 100644 index 000000000..6d4989fe4 --- /dev/null +++ b/src/GZCTF/Migrations/20240913074608_RenameTagToCategory.cs @@ -0,0 +1,38 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GZCTF.Migrations +{ + /// <inheritdoc /> + public partial class RenameTagToCategory : Migration + { + /// <inheritdoc /> + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Tag", + table: "GameChallenges", + newName: "Category"); + + migrationBuilder.RenameColumn( + name: "Tag", + table: "ExerciseChallenges", + newName: "Category"); + } + + /// <inheritdoc /> + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.RenameColumn( + name: "Category", + table: "GameChallenges", + newName: "Tag"); + + migrationBuilder.RenameColumn( + name: "Category", + table: "ExerciseChallenges", + newName: "Tag"); + } + } +} diff --git a/src/GZCTF/Migrations/AppDbContextModelSnapshot.cs b/src/GZCTF/Migrations/AppDbContextModelSnapshot.cs index 29e56e637..f28cc200b 100644 --- a/src/GZCTF/Migrations/AppDbContextModelSnapshot.cs +++ b/src/GZCTF/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "8.0.4") + .HasAnnotation("ProductVersion", "8.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -160,6 +160,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property<int?>("CPUCount") .HasColumnType("integer"); + b.Property<byte>("Category") + .HasColumnType("smallint"); + b.Property<Guid>("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnType("uuid"); @@ -202,9 +205,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property<int>("SubmissionCount") .HasColumnType("integer"); - b.Property<byte>("Tag") - .HasColumnType("smallint"); - b.Property<string>("Tags") .HasColumnType("text"); @@ -420,6 +420,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property<int?>("CPUCount") .HasColumnType("integer"); + b.Property<byte>("Category") + .HasColumnType("smallint"); + b.Property<Guid>("ConcurrencyStamp") .IsConcurrencyToken() .HasColumnType("uuid"); @@ -471,9 +474,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property<int>("SubmissionCount") .HasColumnType("integer"); - b.Property<byte>("Tag") - .HasColumnType("smallint"); - b.Property<Guid?>("TestContainerId") .HasColumnType("uuid"); diff --git a/src/GZCTF/Models/Data/Challenge.cs b/src/GZCTF/Models/Data/Challenge.cs index d5046b1e7..856bba173 100644 --- a/src/GZCTF/Models/Data/Challenge.cs +++ b/src/GZCTF/Models/Data/Challenge.cs @@ -26,11 +26,11 @@ public class Challenge public string Content { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> [Required] [JsonConverter(typeof(JsonStringEnumConverter))] - public ChallengeTag Tag { get; set; } = ChallengeTag.Misc; + public ChallengeCategory Category { get; set; } = ChallengeCategory.Misc; /// <summary> /// 题目类型,创建后不可更改 diff --git a/src/GZCTF/Models/Data/GameChallenge.cs b/src/GZCTF/Models/Data/GameChallenge.cs index 0d3c1b7f9..8278f6c25 100644 --- a/src/GZCTF/Models/Data/GameChallenge.cs +++ b/src/GZCTF/Models/Data/GameChallenge.cs @@ -46,7 +46,7 @@ internal void Update(ChallengeUpdateModel model) { Title = model.Title ?? Title; Content = model.Content ?? Content; - Tag = model.Tag ?? Tag; + Category = model.Category ?? Category; Hints = model.Hints ?? Hints; IsEnabled = model.IsEnabled ?? IsEnabled; CPUCount = model.CPUCount ?? CPUCount; diff --git a/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs b/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs index 2fd2c9bbf..a79e6c644 100644 --- a/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs +++ b/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs @@ -28,10 +28,10 @@ public class ChallengeEditDetailModel public string Content { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> [Required] - public ChallengeTag Tag { get; set; } = ChallengeTag.Misc; + public ChallengeCategory Category { get; set; } = ChallengeCategory.Misc; /// <summary> /// 题目类型 @@ -144,7 +144,7 @@ internal static ChallengeEditDetailModel FromChallenge(GameChallenge chal) => Id = chal.Id, Title = chal.Title, Content = chal.Content, - Tag = chal.Tag, + Category = chal.Category, Type = chal.Type, FlagTemplate = chal.FlagTemplate, Hints = chal.Hints ?? [], diff --git a/src/GZCTF/Models/Request/Edit/ChallengeInfoModel.cs b/src/GZCTF/Models/Request/Edit/ChallengeInfoModel.cs index 7bdb4ce35..6566bf7bd 100644 --- a/src/GZCTF/Models/Request/Edit/ChallengeInfoModel.cs +++ b/src/GZCTF/Models/Request/Edit/ChallengeInfoModel.cs @@ -22,9 +22,9 @@ public class ChallengeInfoModel public string Title { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> - public ChallengeTag Tag { get; set; } = ChallengeTag.Misc; + public ChallengeCategory Category { get; set; } = ChallengeCategory.Misc; /// <summary> /// 题目类型 @@ -56,7 +56,7 @@ internal static ChallengeInfoModel FromChallenge(GameChallenge challenge) => { Id = challenge.Id, Title = challenge.Title, - Tag = challenge.Tag, + Category = challenge.Category, Type = challenge.Type, Score = challenge.CurrentScore, MinScore = (int)Math.Floor(challenge.MinScoreRate * challenge.OriginalScore), diff --git a/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs b/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs index 5bb7e8bac..c2fcdf9c3 100644 --- a/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs +++ b/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs @@ -28,9 +28,9 @@ public class ChallengeUpdateModel public string? FlagTemplate { get; set; } /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> - public ChallengeTag? Tag { get; set; } + public ChallengeCategory? Category { get; set; } /// <summary> /// 题目提示 diff --git a/src/GZCTF/Models/Request/Exercise/ExerciseDetailModel.cs b/src/GZCTF/Models/Request/Exercise/ExerciseDetailModel.cs index 9b5012a6e..584e55fd3 100644 --- a/src/GZCTF/Models/Request/Exercise/ExerciseDetailModel.cs +++ b/src/GZCTF/Models/Request/Exercise/ExerciseDetailModel.cs @@ -20,9 +20,9 @@ public class ExerciseDetailModel public string Content { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> - public ChallengeTag Tag { get; set; } = ChallengeTag.Misc; + public ChallengeCategory Category { get; set; } = ChallengeCategory.Misc; /// <summary> /// 题目提示 @@ -62,7 +62,7 @@ internal static ExerciseDetailModel FromInstance(ExerciseInstance instance) => Hints = instance.Exercise.Hints, Credit = instance.Exercise.Credit, Difficulty = instance.Exercise.Difficulty, - Tag = instance.Exercise.Tag, + Category = instance.Exercise.Category, Tags = instance.Exercise.Tags, Title = instance.Exercise.Title, Type = instance.Exercise.Type, diff --git a/src/GZCTF/Models/Request/Exercise/ExerciseInfoModel.cs b/src/GZCTF/Models/Request/Exercise/ExerciseInfoModel.cs index 3e55d6d65..9851a1347 100644 --- a/src/GZCTF/Models/Request/Exercise/ExerciseInfoModel.cs +++ b/src/GZCTF/Models/Request/Exercise/ExerciseInfoModel.cs @@ -23,7 +23,7 @@ public class ExerciseInfoModel /// <summary> /// 练习标签 /// </summary> - public ChallengeTag Tag { get; set; } + public ChallengeCategory Category { get; set; } /// <summary> /// 练习附加标签 diff --git a/src/GZCTF/Models/Request/Game/ChallengeDetailModel.cs b/src/GZCTF/Models/Request/Game/ChallengeDetailModel.cs index db516994f..3030b2feb 100644 --- a/src/GZCTF/Models/Request/Game/ChallengeDetailModel.cs +++ b/src/GZCTF/Models/Request/Game/ChallengeDetailModel.cs @@ -23,9 +23,9 @@ public class ChallengeDetailModel public string Content { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> - public ChallengeTag Tag { get; set; } = ChallengeTag.Misc; + public ChallengeCategory Category { get; set; } = ChallengeCategory.Misc; /// <summary> /// 题目提示 @@ -54,7 +54,7 @@ internal static ChallengeDetailModel FromInstance(GameInstance gameInstance) => Content = gameInstance.Challenge.Content, Hints = gameInstance.Challenge.Hints, Score = gameInstance.Challenge.CurrentScore, - Tag = gameInstance.Challenge.Tag, + Category = gameInstance.Challenge.Category, Title = gameInstance.Challenge.Title, Type = gameInstance.Challenge.Type, Context = new() diff --git a/src/GZCTF/Models/Request/Game/ChallengeTrafficModel.cs b/src/GZCTF/Models/Request/Game/ChallengeTrafficModel.cs index 3fcb10c69..ee52b9e77 100644 --- a/src/GZCTF/Models/Request/Game/ChallengeTrafficModel.cs +++ b/src/GZCTF/Models/Request/Game/ChallengeTrafficModel.cs @@ -19,9 +19,9 @@ public class ChallengeTrafficModel public string Title { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> - public ChallengeTag Tag { get; set; } = ChallengeTag.Misc; + public ChallengeCategory Category { get; set; } = ChallengeCategory.Misc; /// <summary> /// 题目类型 @@ -46,7 +46,7 @@ internal static ChallengeTrafficModel FromChallenge(GameChallenge challenge) { Id = challenge.Id, Title = challenge.Title, - Tag = challenge.Tag, + Category = challenge.Category, Type = challenge.Type, IsEnabled = challenge.IsEnabled, Count = Directory.Exists(trafficPath) diff --git a/src/GZCTF/Models/Request/Game/GameDetailModel.cs b/src/GZCTF/Models/Request/Game/GameDetailModel.cs index 7463f5dc3..7bca756f6 100644 --- a/src/GZCTF/Models/Request/Game/GameDetailModel.cs +++ b/src/GZCTF/Models/Request/Game/GameDetailModel.cs @@ -8,7 +8,7 @@ public class GameDetailModel /// <summary> /// 题目信息 /// </summary> - public Dictionary<ChallengeTag, IEnumerable<ChallengeInfo>> Challenges { get; set; } = new(); + public Dictionary<ChallengeCategory, IEnumerable<ChallengeInfo>> Challenges { get; set; } = new(); /// <summary> /// 题目数量 diff --git a/src/GZCTF/Models/Request/Game/ScoreboardModel.cs b/src/GZCTF/Models/Request/Game/ScoreboardModel.cs index 35235c81c..9d9a79db6 100644 --- a/src/GZCTF/Models/Request/Game/ScoreboardModel.cs +++ b/src/GZCTF/Models/Request/Game/ScoreboardModel.cs @@ -44,7 +44,7 @@ public partial class ScoreboardModel /// <summary> /// 题目信息 /// </summary> - public Dictionary<ChallengeTag, IEnumerable<ChallengeInfo>> Challenges + public Dictionary<ChallengeCategory, IEnumerable<ChallengeInfo>> Challenges { get => _challenges; set @@ -54,7 +54,7 @@ public Dictionary<ChallengeTag, IEnumerable<ChallengeInfo>> Challenges } } - private Dictionary<ChallengeTag, IEnumerable<ChallengeInfo>> _challenges = default!; + private Dictionary<ChallengeCategory, IEnumerable<ChallengeInfo>> _challenges = default!; /// <summary> /// 题目数量 @@ -221,9 +221,9 @@ public partial class ChallengeInfo public string Title { get; set; } = string.Empty; /// <summary> - /// 题目标签 + /// 题目类别 /// </summary> - public ChallengeTag Tag { get; set; } + public ChallengeCategory Category { get; set; } /// <summary> /// 题目分值 diff --git a/src/GZCTF/Repositories/GameRepository.cs b/src/GZCTF/Repositories/GameRepository.cs index 33f3663a5..7e938f4e6 100644 --- a/src/GZCTF/Repositories/GameRepository.cs +++ b/src/GZCTF/Repositories/GameRepository.cs @@ -178,13 +178,13 @@ public async Task<ScoreboardModel> GenScoreboard(Game game, CancellationToken to .AsNoTracking() .IgnoreAutoIncludes() .Where(c => c.GameId == game.Id && c.IsEnabled) - .OrderBy(c => c.Tag) + .OrderBy(c => c.Category) .ThenBy(c => c.Title) .Select(c => new ChallengeInfo { Id = c.Id, Title = c.Title, - Tag = c.Tag, + Category = c.Category, Score = c.CurrentScore, SolvedCount = c.AcceptedCount, // pending fields: Bloods @@ -344,7 +344,7 @@ public async Task<ScoreboardModel> GenScoreboard(Game game, CancellationToken to // 8. construct the final scoreboard model var challengesDict = challenges .Values - .GroupBy(c => c.Tag) + .GroupBy(c => c.Category) .ToDictionary(c => c.Key, c => c.AsEnumerable()); return new() diff --git a/src/GZCTF/Services/Container/Provider/KubernetesProvider.cs b/src/GZCTF/Services/Container/Provider/KubernetesProvider.cs index 0f1633ac1..11a52d7ee 100644 --- a/src/GZCTF/Services/Container/Provider/KubernetesProvider.cs +++ b/src/GZCTF/Services/Container/Provider/KubernetesProvider.cs @@ -43,7 +43,7 @@ public KubernetesProvider(IOptions<RegistryConfig> registry, IOptions<ContainerP }; KubernetesClientConfiguration config; - + if (File.Exists(_kubernetesMetadata.Config.KubeConfig)) { config = KubernetesClientConfiguration.BuildConfigFromConfigFile(_kubernetesMetadata.Config.KubeConfig); @@ -59,7 +59,7 @@ public KubernetesProvider(IOptions<RegistryConfig> registry, IOptions<ContainerP _kubernetesMetadata.Config.KubeConfig]); throw new FileNotFoundException(_kubernetesMetadata.Config.KubeConfig); } - + _kubernetesMetadata.HostIp = new Uri(config.Host).Host; _kubernetesClient = new Kubernetes(config); diff --git a/src/GZCTF/Utils/Enums.cs b/src/GZCTF/Utils/Enums.cs index 38e6db07b..fc02dc6f0 100644 --- a/src/GZCTF/Utils/Enums.cs +++ b/src/GZCTF/Utils/Enums.cs @@ -330,10 +330,10 @@ public static class ChallengeTypeExtensions } /// <summary> -/// 题目标签 +/// 题目类别 /// </summary> [JsonConverter(typeof(JsonStringEnumConverter))] -public enum ChallengeTag : byte +public enum ChallengeCategory : byte { Misc = 0, Crypto = 1, diff --git a/src/GZCTF/Utils/ExcelHelper.cs b/src/GZCTF/Utils/ExcelHelper.cs index ec3803ff5..1f520c73c 100644 --- a/src/GZCTF/Utils/ExcelHelper.cs +++ b/src/GZCTF/Utils/ExcelHelper.cs @@ -127,7 +127,7 @@ int[] WriteBoardHeader(ISheet sheet, ICellStyle style, ScoreboardModel scoreboar cell.CellStyle = style; } - foreach (KeyValuePair<ChallengeTag, IEnumerable<ChallengeInfo>> type in scoreboard.Challenges) + foreach (KeyValuePair<ChallengeCategory, IEnumerable<ChallengeInfo>> type in scoreboard.Challenges) foreach (ChallengeInfo chall in type.Value) { ICell? cell = row.CreateCell(colIndex++); diff --git a/src/GZCTF/Utils/Shared.cs b/src/GZCTF/Utils/Shared.cs index c30e00f96..8c28791c4 100644 --- a/src/GZCTF/Utils/Shared.cs +++ b/src/GZCTF/Utils/Shared.cs @@ -60,10 +60,10 @@ public record TeamModel(int Id, string Name, string? Avatar) /// </summary> /// <param name="Id">题目 ID</param> /// <param name="Title">题目名称</param> -/// <param name="Tag">题目标签</param> -public record ChallengeModel(int Id, string Title, ChallengeTag Tag) +/// <param name="Category">题目类别</param> +public record ChallengeModel(int Id, string Title, ChallengeCategory Category) { - internal static ChallengeModel FromChallenge(GameChallenge chal) => new(chal.Id, chal.Title, chal.Tag); + internal static ChallengeModel FromChallenge(GameChallenge chal) => new(chal.Id, chal.Title, chal.Category); } /// <summary>