diff --git a/src/GZCTF/ClientApp/src/Api.ts b/src/GZCTF/ClientApp/src/Api.ts index c0e917fba..f5224b8c5 100644 --- a/src/GZCTF/ClientApp/src/Api.ts +++ b/src/GZCTF/ClientApp/src/Api.ts @@ -944,6 +944,16 @@ export interface ChallengeEditDetailModel { */ difficulty: number; canSubmit?: boolean; + /** + * 开始时间 + * @format date-time + */ + enableAt?: string | null; + /** + * 结束时间 + * @format date-time + */ + endAt?: string | null; } export enum ChallengeType { @@ -1067,6 +1077,16 @@ export interface ChallengeUpdateModel { isEnabled?: boolean | null; /** 是否可提交 */ canSubmit?: boolean | null; + /** + * 开启时间 + * @format date-time + */ + enableAt?: string | null; + /** + * 结束时间 + * @format date-time + */ + endAt?: string | null; /** 统一文件名 */ fileName?: string | null; /** 镜像名称与标签 */ 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 c9e235b56..7aa262345 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 @@ -43,6 +43,7 @@ import { } from '@Utils/Shared' import { useEditChallenge, useEditChallenges } from '@Utils/useEdit' import api, { ChallengeTag, ChallengeType, ChallengeUpdateModel } from '@Api' +import {DateTimePicker} from "@mantine/dates"; const GameChallengeEdit: FC = () => { const navigate = useNavigate() @@ -323,6 +324,18 @@ const GameChallengeEdit: FC = () => { onChange={(e) => setChallengeInfo({...challengeInfo, canSubmit: e.target.checked})} checked={challengeInfo.canSubmit == true} /> + setChallengeInfo({ ...challengeInfo, enableAt: e?.toISOString() })} + /> + setChallengeInfo({...challengeInfo, endAt: e?.toISOString()})} + /> diff --git a/src/GZCTF/Migrations/20240825191922_AddChallengeTime.Designer.cs b/src/GZCTF/Migrations/20240825191922_AddChallengeTime.Designer.cs new file mode 100644 index 000000000..2dd0b1cbf --- /dev/null +++ b/src/GZCTF/Migrations/20240825191922_AddChallengeTime.Designer.cs @@ -0,0 +1,1632 @@ +// +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("20240825191922_AddChallengeTime")] + partial class AddChallengeTime + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("LocalFileId") + .HasColumnType("integer"); + + b.Property("RemoteUrl") + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("LocalFileId"); + + b.ToTable("Attachments"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.CheatInfo", b => + { + b.Property("SubmissionId") + .HasColumnType("integer"); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("SourceTeamId") + .HasColumnType("integer"); + + b.Property("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("ConfigKey") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("ConfigKey"); + + b.ToTable("Configs"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Container", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ContainerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("ExerciseInstanceId") + .HasColumnType("integer"); + + b.Property("ExpectStopAt") + .HasColumnType("timestamp with time zone"); + + b.Property("GameInstanceId") + .HasColumnType("integer"); + + b.Property("IP") + .IsRequired() + .HasColumnType("text"); + + b.Property("Image") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsProxy") + .HasColumnType("boolean"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("PublicIP") + .HasColumnType("text"); + + b.Property("PublicPort") + .HasColumnType("integer"); + + b.Property("StartedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AcceptedCount") + .HasColumnType("integer"); + + b.Property("AttachmentId") + .HasColumnType("integer"); + + b.Property("CPUCount") + .HasColumnType("integer"); + + b.Property("CanSubmit") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("uuid"); + + b.Property("ContainerExposePort") + .HasColumnType("integer"); + + b.Property("ContainerImage") + .HasColumnType("text"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("Credit") + .HasColumnType("boolean"); + + b.Property("Difficulty") + .HasColumnType("smallint"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FlagTemplate") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("Hints") + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("MemoryLimit") + .HasColumnType("integer"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("SubmissionCount") + .HasColumnType("integer"); + + b.Property("Tag") + .HasColumnType("smallint"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("TestContainerId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.HasKey("Id"); + + b.HasIndex("AttachmentId"); + + b.HasIndex("TestContainerId"); + + b.ToTable("ExerciseChallenges"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.ExerciseDependency", b => + { + b.Property("SourceId") + .HasColumnType("integer"); + + b.Property("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("UserId") + .HasColumnType("uuid"); + + b.Property("ExerciseId") + .HasColumnType("integer"); + + b.Property("ContainerId") + .HasColumnType("uuid"); + + b.Property("FlagId") + .HasColumnType("integer"); + + b.Property("IsLoaded") + .HasColumnType("boolean"); + + b.Property("IsSolved") + .HasColumnType("boolean"); + + b.Property("LastContainerOperation") + .HasColumnType("timestamp with time zone"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AttachmentId") + .HasColumnType("integer"); + + b.Property("ChallengeId") + .HasColumnType("integer"); + + b.Property("ExerciseId") + .HasColumnType("integer"); + + b.Property("Flag") + .IsRequired() + .HasMaxLength(127) + .HasColumnType("character varying(127)"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AcceptWithoutReview") + .HasColumnType("boolean"); + + b.Property("BloodBonusValue") + .HasColumnType("bigint") + .HasColumnName("BloodBonus"); + + b.Property("ContainerCountLimit") + .HasColumnType("integer"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("EndTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "end"); + + b.Property("Hidden") + .HasColumnType("boolean"); + + b.Property("InviteCode") + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Organizations") + .HasColumnType("text"); + + b.Property("PosterHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("PracticeMode") + .HasColumnType("boolean"); + + b.Property("PrivateKey") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("PublicKey") + .IsRequired() + .HasMaxLength(63) + .HasColumnType("character varying(63)"); + + b.Property("StartTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "start"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("text"); + + b.Property("TeamMemberCountLimit") + .HasColumnType("integer"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("WriteupDeadline") + .HasColumnType("timestamp with time zone"); + + b.Property("WriteupNote") + .IsRequired() + .HasColumnType("text"); + + b.Property("WriteupRequired") + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.ToTable("Games"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.GameChallenge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AcceptedCount") + .HasColumnType("integer"); + + b.Property("AttachmentId") + .HasColumnType("integer"); + + b.Property("CPUCount") + .HasColumnType("integer"); + + b.Property("CanSubmit") + .HasColumnType("boolean"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("uuid"); + + b.Property("ContainerExposePort") + .HasColumnType("integer"); + + b.Property("ContainerImage") + .HasColumnType("text"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("Difficulty") + .HasColumnType("double precision"); + + b.Property("EnableAt") + .HasColumnType("timestamp with time zone"); + + b.Property("EnableTrafficCapture") + .HasColumnType("boolean"); + + b.Property("EndAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FileName") + .HasColumnType("text"); + + b.Property("FlagTemplate") + .HasMaxLength(120) + .HasColumnType("character varying(120)"); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("Hints") + .HasColumnType("text"); + + b.Property("IsEnabled") + .HasColumnType("boolean"); + + b.Property("MemoryLimit") + .HasColumnType("integer"); + + b.Property("MinScoreRate") + .HasColumnType("double precision"); + + b.Property("OriginalScore") + .HasColumnType("integer"); + + b.Property("StorageLimit") + .HasColumnType("integer"); + + b.Property("SubmissionCount") + .HasColumnType("integer"); + + b.Property("Tag") + .HasColumnType("smallint"); + + b.Property("TestContainerId") + .HasColumnType("uuid"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("PublishTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "time"); + + b.Property("TeamId") + .HasColumnType("integer"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("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("ChallengeId") + .HasColumnType("integer"); + + b.Property("ParticipationId") + .HasColumnType("integer"); + + b.Property("ContainerId") + .HasColumnType("uuid"); + + b.Property("FlagId") + .HasColumnType("integer"); + + b.Property("IsLoaded") + .HasColumnType("boolean"); + + b.Property("IsSolved") + .HasColumnType("boolean"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("PublishTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "time"); + + b.Property("Type") + .HasColumnType("smallint"); + + b.Property("Values") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("GameId"); + + b.ToTable("GameNotices"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.LocalFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FileSize") + .HasColumnType("bigint"); + + b.Property("Hash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text"); + + b.Property("ReferenceCount") + .HasColumnType("bigint"); + + b.Property("UploadTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("Hash"); + + b.ToTable("Files"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.LogModel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Exception") + .HasColumnType("text"); + + b.Property("Level") + .IsRequired() + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.Property("Logger") + .IsRequired() + .HasMaxLength(250) + .HasColumnType("character varying(250)"); + + b.Property("Message") + .IsRequired() + .HasColumnType("text"); + + b.Property("RemoteIP") + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("Status") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("TimeUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("UserName") + .HasMaxLength(15) + .HasColumnType("character varying(15)"); + + b.HasKey("Id"); + + b.ToTable("Logs"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Participation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("Organization") + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TeamId") + .HasColumnType("integer"); + + b.Property("Token") + .IsRequired() + .HasColumnType("text"); + + b.Property("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("Id") + .HasMaxLength(8) + .HasColumnType("character varying(8)"); + + b.Property("AuthorId") + .HasColumnType("uuid"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("IsPinned") + .HasColumnType("boolean"); + + b.Property("Summary") + .IsRequired() + .HasColumnType("text"); + + b.Property("Tags") + .HasColumnType("text"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("UpdateTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AuthorId"); + + b.ToTable("Posts"); + }); + + modelBuilder.Entity("GZCTF.Models.Data.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("Answer") + .IsRequired() + .HasMaxLength(127) + .HasColumnType("character varying(127)"); + + b.Property("ChallengeId") + .HasColumnType("integer"); + + b.Property("GameId") + .HasColumnType("integer"); + + b.Property("ParticipationId") + .HasColumnType("integer"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("SubmitTimeUtc") + .HasColumnType("timestamp with time zone") + .HasAnnotation("Relational:JsonPropertyName", "time"); + + b.Property("TeamId") + .HasColumnType("integer"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("AvatarHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Bio") + .HasMaxLength(72) + .HasColumnType("character varying(72)"); + + b.Property("CaptainId") + .HasColumnType("uuid"); + + b.Property("InviteToken") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("Locked") + .HasColumnType("boolean"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("AccessFailedCount") + .HasColumnType("integer"); + + b.Property("AvatarHash") + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("Bio") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean"); + + b.Property("ExerciseVisible") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IP") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("character varying(40)"); + + b.Property("LastSignedInUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LastVisitedUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean"); + + b.Property("RealName") + .IsRequired() + .HasMaxLength(128) + .HasColumnType("character varying(128)"); + + b.Property("RegisterTimeUtc") + .HasColumnType("timestamp with time zone"); + + b.Property("Role") + .HasColumnType("integer"); + + b.Property("SecurityStamp") + .HasColumnType("text"); + + b.Property("StdNumber") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean"); + + b.Property("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("GameId") + .HasColumnType("integer"); + + b.Property("TeamId") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("FriendlyName") + .HasColumnType("text"); + + b.Property("Xml") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("DataProtectionKeys"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("character varying(256)"); + + b.Property("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", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("text"); + + b.Property("ClaimValue") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("ProviderKey") + .HasColumnType("text"); + + b.Property("ProviderDisplayName") + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("uuid"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("RoleId") + .HasColumnType("uuid"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("uuid"); + + b.Property("LoginProvider") + .HasColumnType("text"); + + b.Property("Name") + .HasColumnType("text"); + + b.Property("Value") + .HasColumnType("text"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("TeamUserInfo", b => + { + b.Property("MembersId") + .HasColumnType("uuid"); + + b.Property("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", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("GZCTF.Models.Data.UserInfo", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", 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", 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/20240825191922_AddChallengeTime.cs b/src/GZCTF/Migrations/20240825191922_AddChallengeTime.cs new file mode 100644 index 000000000..57a0f6172 --- /dev/null +++ b/src/GZCTF/Migrations/20240825191922_AddChallengeTime.cs @@ -0,0 +1,39 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace GZCTF.Migrations +{ + /// + public partial class AddChallengeTime : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EnableAt", + table: "GameChallenges", + type: "timestamp with time zone", + nullable: true); + + migrationBuilder.AddColumn( + name: "EndAt", + table: "GameChallenges", + type: "timestamp with time zone", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "EnableAt", + table: "GameChallenges"); + + migrationBuilder.DropColumn( + name: "EndAt", + table: "GameChallenges"); + } + } +} diff --git a/src/GZCTF/Migrations/AppDbContextModelSnapshot.cs b/src/GZCTF/Migrations/AppDbContextModelSnapshot.cs index 51bb3d44f..bc9f6e02e 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.7") + .HasAnnotation("ProductVersion", "8.0.8") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -443,9 +443,15 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Difficulty") .HasColumnType("double precision"); + b.Property("EnableAt") + .HasColumnType("timestamp with time zone"); + b.Property("EnableTrafficCapture") .HasColumnType("boolean"); + b.Property("EndAt") + .HasColumnType("timestamp with time zone"); + b.Property("FileName") .HasColumnType("text"); diff --git a/src/GZCTF/Models/Data/GameChallenge.cs b/src/GZCTF/Models/Data/GameChallenge.cs index 5283305b5..32f1ecb98 100644 --- a/src/GZCTF/Models/Data/GameChallenge.cs +++ b/src/GZCTF/Models/Data/GameChallenge.cs @@ -29,6 +29,10 @@ public class GameChallenge : Challenge /// [Required] public double Difficulty { get; set; } = 25; + + public DateTimeOffset? EnableAt { get; set; } + + public DateTimeOffset? EndAt { get; set; } /// /// 当前题目分值 @@ -59,6 +63,8 @@ internal void Update(ChallengeUpdateModel model) MinScoreRate = model.MinScoreRate ?? MinScoreRate; Difficulty = model.Difficulty ?? Difficulty; FileName = model.FileName ?? FileName; + EnableAt = model.EnableAt ?? EnableAt; + EndAt = model.EndAt ?? EndAt; // only set FlagTemplate to null when it pass an empty string (but not null) FlagTemplate = model.FlagTemplate is null ? FlagTemplate : diff --git a/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs b/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs index eb1876f8b..0b7f218f6 100644 --- a/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs +++ b/src/GZCTF/Models/Request/Edit/ChallengeEditDetailModel.cs @@ -139,6 +139,16 @@ public class ChallengeEditDetailModel public double Difficulty { get; set; } = 3; public bool CanSubmit { get; set; } = true; + + /// + /// 开始时间 + /// + public DateTimeOffset? EnableAt { get; set; } + + /// + /// 结束时间 + /// + public DateTimeOffset? EndAt { get; set; } internal static ChallengeEditDetailModel FromChallenge(GameChallenge chal) => new() @@ -164,6 +174,8 @@ internal static ChallengeEditDetailModel FromChallenge(GameChallenge chal) => FileName = chal.FileName, AcceptedCount = chal.AcceptedCount, Attachment = chal.Attachment, + EnableAt = chal.EnableAt, + EndAt = chal.EndAt, TestContainer = chal.TestContainer is null ? null : ContainerInfoModel.FromContainer(chal.TestContainer), Flags = chal.Flags.Select(FlagInfoModel.FromFlagContext).ToList() }; diff --git a/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs b/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs index 1198a6a9a..68465d301 100644 --- a/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs +++ b/src/GZCTF/Models/Request/Edit/ChallengeUpdateModel.cs @@ -46,6 +46,16 @@ public class ChallengeUpdateModel /// 是否可提交 /// public bool? CanSubmit { get; set; } + + /// + /// 开启时间 + /// + public DateTimeOffset? EnableAt { get; set; } + + /// + /// 结束时间 + /// + public DateTimeOffset? EndAt { get; set; } /// /// 统一文件名 diff --git a/src/GZCTF/Properties/launchSettings.json b/src/GZCTF/Properties/launchSettings.json index f58773ceb..3cd329603 100644 --- a/src/GZCTF/Properties/launchSettings.json +++ b/src/GZCTF/Properties/launchSettings.json @@ -6,7 +6,8 @@ "applicationUrl": "http://localhost:55000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy", + "ConnectionStrings__Database": "Host=127.0.0.1:5432;Database=gzctf;Username=postgres;Password=123456" } } } diff --git a/src/GZCTF/Services/CronJobService.cs b/src/GZCTF/Services/CronJobService.cs index 7ff97ff13..0e7b10e63 100644 --- a/src/GZCTF/Services/CronJobService.cs +++ b/src/GZCTF/Services/CronJobService.cs @@ -18,7 +18,7 @@ public void Dispose() public Task StartAsync(CancellationToken token) { - _timer = new Timer(Execute, null, TimeSpan.Zero, TimeSpan.FromMinutes(3)); + _timer = new Timer(Execute, null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); logger.SystemLog(Program.StaticLocalizer[nameof(Resources.Program.CronJob_Started)], TaskStatus.Success, LogLevel.Debug); return Task.CompletedTask; @@ -71,11 +71,56 @@ async Task BootstrapCache(AsyncServiceScope scope) } } + async Task UpdateChallengeStatus(AsyncServiceScope scope) + { + var gamesRepository = scope.ServiceProvider.GetRequiredService(); + var challengesRepository = scope.ServiceProvider.GetRequiredService(); + var gameNoticeRepository = scope.ServiceProvider.GetRequiredService(); + var cacheHelper = scope.ServiceProvider.GetRequiredService(); + var games = await gamesRepository.GetGames(); + foreach (Game game in games) + { + var challenges = await challengesRepository.GetChallenges(game.Id); + foreach (GameChallenge gameChallenge in challenges) + { + if (gameChallenge.EnableAt is null && gameChallenge.EndAt is null) + continue; + + if (gameChallenge.EnableAt <= DateTimeOffset.Now && + gameChallenge.EndAt > DateTimeOffset.Now && + !gameChallenge.IsEnabled) + { + gameChallenge.IsEnabled = true; + await challengesRepository.EnsureInstances(gameChallenge, game); + if (game.IsActive) + await gameNoticeRepository.AddNotice( + new() { Game = game, Type = NoticeType.NewChallenge, Values = [gameChallenge.Title] }); + await cacheHelper.FlushScoreboardCache(game.Id, CancellationToken.None); + } + + if (!gameChallenge.IsEnabled) + continue; + + if (gameChallenge.EndAt <= DateTimeOffset.Now) + { + gameChallenge.CanSubmit = false; + } + + } + } + + await gamesRepository.SaveAsync(); + } + + private int _counter = 6; + async void Execute(object? state) { await using AsyncServiceScope scope = provider.CreateAsyncScope(); - + await UpdateChallengeStatus(scope); + if (--_counter > 0) return; await ContainerChecker(scope); await BootstrapCache(scope); + _counter = 6; } }