Skip to content

Commit

Permalink
wip: ExerciseInstances
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Oct 9, 2023
1 parent 8e5104c commit 3cd9244
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 25 deletions.
3 changes: 3 additions & 0 deletions src/GZCTF/Models/AppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ protected override void OnModelCreating(ModelBuilder builder)
entity.Property(e => e.UserName)
.HasMaxLength(16);

entity.Property(e => e.ExerciseVisible)
.HasDefaultValue(true);

entity.HasMany(e => e.Submissions)
.WithOne(e => e.User)
.HasForeignKey(e => e.UserId)
Expand Down
8 changes: 4 additions & 4 deletions src/GZCTF/Models/Data/ExerciseChallenge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@ namespace GZCTF.Models.Data;
public class ExerciseChallenge : Challenge
{
/// <summary>
/// 题目的积分
/// 练习题目的积分
/// </summary>
public bool Credit { get; set; }

/// <summary>
/// 题目的难度,用作标签、排序等
/// 练习题目的难度,用作标签、排序等
/// </summary>
public Difficulty Difficulty { get; set; }

/// <summary>
/// 题目附加标签
/// 练习题目附加标签
/// </summary>
public List<string>? Tags { get; set; } = new();

#region Db Relationship

/// <summary>
/// 依赖的题目
/// 依赖的练习题目
/// </summary>
public List<ExerciseChallenge> Dependencies { get; set; } = new();

Expand Down
6 changes: 5 additions & 1 deletion src/GZCTF/Models/Data/ExerciseInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
namespace GZCTF.Models.Data;

[Index(nameof(UserId))]
[Index(nameof(FlagId))]
[PrimaryKey(nameof(UserId), nameof(ExerciseId))]
public class ExerciseInstance : Instance
{
Expand All @@ -22,6 +21,11 @@ public class ExerciseInstance : Instance
? FlagContext?.Attachment?.UrlWithName(Exercise.FileName)
: Exercise.Attachment?.UrlWithName();

/// <summary>
/// 答案解出的时间
/// </summary>
public DateTimeOffset SolveTimeUtc { get; set; } = DateTimeOffset.FromUnixTimeSeconds(0);

#region Db Relationship

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions src/GZCTF/Models/Data/UserInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ public partial class UserInfo : IdentityUser<Guid>
[ProtectedPersonalData]
public string StdNumber { get; set; } = string.Empty;

/// <summary>
/// 在练习排行榜中隐藏
/// </summary>
public bool ExerciseVisible { get; set; } = true;

[NotMapped]
[MemoryPackIgnore]
public string? AvatarUrl => AvatarHash is null ? null : $"/assets/{AvatarHash}/avatar";
Expand Down
16 changes: 13 additions & 3 deletions src/GZCTF/Models/Request/Exercise/ExerciseInfoModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,33 @@ public class ExerciseInfoModel
/// </summary>
public string Title { get; set; } = string.Empty;

/// <summary>
/// 练习题目的难度,用作标签、排序等
/// </summary>
public Difficulty Difficulty { get; set; }

/// <summary>
/// 练习标签
/// </summary>
public ChallengeTag Tag { get; set; }

/// <summary>
/// 练习附加标签
/// </summary>
public List<string>? Tags { get; set; } = new();

/// <summary>
/// 练习积分
/// </summary>
public int Credit { get; set; }

/// <summary>
/// 解出队伍数量
/// 解决题目人数
/// </summary>
public int SolvedCount { get; set; }
public int AcceptedCount { get; set; }

/// <summary>
/// 提交数量
/// 提交答案的数量
/// </summary>
public int SubmissionCount { get; set; }
}
3 changes: 3 additions & 0 deletions src/GZCTF/Repositories/ExerciseChallengeRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public async Task<ExerciseChallenge> CreateExercise(ExerciseChallenge exercise,
return exercise;
}

public Task<int[]> GetDependencies(ExerciseChallenge exercise, CancellationToken token = default)
=> context.ExerciseDependencies.Where(d => d.TargetId == exercise.Id).Select(d => d.SourceId).ToArrayAsync(token);

public Task<ExerciseChallenge[]> GetExercises(CancellationToken token = default) => context.ExerciseChallenges.OrderBy(e => e.Id).ToArrayAsync(token);

public async Task RemoveExercise(ExerciseChallenge exercise, CancellationToken token = default)
Expand Down
66 changes: 59 additions & 7 deletions src/GZCTF/Repositories/ExerciseInstanceRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,17 @@ public async Task<ExerciseInstance[]> GetExerciseInstances(UserInfo user, Cancel
if (exercises.Length > 0)
return exercises;

using var transaction = await context.Database.BeginTransactionAsync(token);

var result = new List<ExerciseInstance>();

foreach (var instance in context.ExerciseChallenges.Include(e => e.Dependencies)
.Where(e => e.IsEnabled && e.Dependencies.Count == 0))
await foreach (var id in context.ExerciseChallenges
.Where(e => e.IsEnabled && context.ExerciseDependencies.All(d => d.TargetId != e.Id))
.Select(e => e.Id).AsAsyncEnumerable())
{
var newInst = new ExerciseInstance
{
Exercise = instance,
ExerciseId = id,
UserId = user.Id,
IsLoaded = false
};
Expand All @@ -50,6 +53,8 @@ public async Task<ExerciseInstance[]> GetExerciseInstances(UserInfo user, Cancel
}

await SaveAsync(token);
await transaction.CommitAsync(token);

return result.ToArray();
}

Expand Down Expand Up @@ -181,22 +186,69 @@ public async Task<TaskResult<Container>> CreateContainer(ExerciseInstance instan
return new TaskResult<Container>(TaskStatus.Success, instance.Container);
}

public async Task<AnswerResult> VerifyAnswer(ExerciseInstance instance, string answer, CancellationToken token = default)
public async Task<AnswerResult> VerifyAnswer(UserInfo user, ExerciseInstance instance, string answer, CancellationToken token = default)
{
if (instance.Exercise.Type == ChallengeType.DynamicContainer)
{
if (instance.FlagContext is null)
return AnswerResult.NotFound;

if (instance.FlagContext.Flag == answer)
{
await MarkSolved(instance, token);
await UnlockExercises(user, token);
return AnswerResult.Accepted;
}

return AnswerResult.WrongAnswer;
}

var ret = await context.FlagContexts.AsNoTracking()
.AnyAsync(f => f.ExerciseId == instance.ExerciseId && f.Flag == answer, token);
if (await context.FlagContexts.AsNoTracking()
.AnyAsync(f => f.ExerciseId == instance.ExerciseId && f.Flag == answer, token))
{
await MarkSolved(instance, token);
await UnlockExercises(user, token);
return AnswerResult.Accepted;
}

return AnswerResult.WrongAnswer;
}

internal async Task MarkSolved(ExerciseInstance instance, CancellationToken token = default)
{
if (instance.IsSolved)
return;

using var transaction = await context.Database.BeginTransactionAsync(token);

instance.IsSolved = true;
instance.SolveTimeUtc = DateTimeOffset.UtcNow;
await SaveAsync(token);

await transaction.CommitAsync(token);
}

internal async Task UnlockExercises(UserInfo user, CancellationToken token = default)
{
using var transaction = await context.Database.BeginTransactionAsync(token);

await foreach (var id in context.ExerciseChallenges.Where(chal => chal.IsEnabled &&
context.ExerciseInstances.All(i => i.UserId == user.Id && i.ExerciseId != chal.Id) &&
context.ExerciseDependencies.All(dep => dep.TargetId == chal.Id &&
context.ExerciseInstances.Any(e => e.IsSolved && e.ExerciseId == dep.SourceId
))).Select(e => e.Id).AsAsyncEnumerable())
{
var newInst = new ExerciseInstance
{
ExerciseId = id,
UserId = user.Id,
IsLoaded = false
};

return ret ? AnswerResult.Accepted : AnswerResult.WrongAnswer;
context.ExerciseInstances.Add(newInst);
}

await SaveAsync(token);
await transaction.CommitAsync(token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public interface IExerciseChallengeRepository : IRepository
/// <returns></returns>
public Task<ExerciseChallenge[]> GetExercises(CancellationToken token = default);

/// <summary>
/// 获取练习题目依赖
/// </summary>
/// <param name="exercise">练习题目</param>
/// <param name="token"></param>
/// <returns></returns>
public Task<int[]> GetDependencies(ExerciseChallenge exercise, CancellationToken token = default);

/// <summary>
/// 更新附件
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ public interface IExerciseInstanceRepository : IRepository
public Task<ExerciseInstance[]> GetExerciseInstances(UserInfo user, CancellationToken token = default);

/// <summary>
/// 验证答案
/// 验证答案并解锁题目
/// </summary>
/// <param name="user">当前用户</param>
/// <param name="instance">当前实例</param>
/// <param name="answer">当前提交</param>
/// <param name="token"></param>
/// <returns></returns>
public Task<AnswerResult> VerifyAnswer(ExerciseInstance instance, string answer, CancellationToken token = default);
public Task<AnswerResult> VerifyAnswer(UserInfo user, ExerciseInstance instance, string answer, CancellationToken token = default);

/// <summary>
/// 创建容器实例
Expand Down
16 changes: 8 additions & 8 deletions src/GZCTF/Utils/Enums.cs
Original file line number Diff line number Diff line change
Expand Up @@ -353,14 +353,14 @@ public enum ChallengeTag : byte
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum Difficulty : byte
{
Baby,
Trivial,
Easy,
Normal,
Medium,
Hard,
Expert,
Insane
Baby = 0,
Trivial = 1,
Easy = 2,
Normal = 3,
Medium = 4,
Hard = 5,
Expert = 6,
Insane = 7
}

/// <summary>
Expand Down

0 comments on commit 3cd9244

Please sign in to comment.