Skip to content

Commit 07b954c

Browse files
committed
GitHub fork
1 parent 28ce768 commit 07b954c

28 files changed

+1005
-53
lines changed

HackOMania.Api/Endpoints/Organizers/Hackathon/Create/Endpoint.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using HackOMania.Api.Authorization;
33
using HackOMania.Api.Entities;
44
using HackOMania.Api.Extensions;
5+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
56
using SqlSugar;
67

78
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Create;
@@ -31,6 +32,15 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
3132

3233
var emailTemplates = NormalizeEmailTemplates(req.EmailTemplates);
3334
var challengeSelectionEndDate = req.ChallengeSelectionEndDate ?? req.SubmissionsEndDate;
35+
var gitHubRepositorySettingsResult = HackathonGitHubRepositorySettingsMutation.BuildForCreate(
36+
req.GitHubRepositorySettings
37+
);
38+
AddGitHubRepositorySettingsErrors(gitHubRepositorySettingsResult.Errors);
39+
if (ValidationFailed)
40+
{
41+
await Send.ErrorsAsync(cancellation: ct);
42+
return;
43+
}
3444

3545
var hackathon = new Entities.Hackathon
3646
{
@@ -55,6 +65,12 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
5565
.Include(h => h.Organizers)
5666
.ExecuteReturnEntityAsync();
5767

68+
if (HackathonGitHubRepositorySettingsMutation.ShouldPersist(gitHubRepositorySettingsResult.Settings))
69+
{
70+
gitHubRepositorySettingsResult.Settings!.HackathonId = ent.Id;
71+
await sql.Insertable(gitHubRepositorySettingsResult.Settings).ExecuteCommandAsync(ct);
72+
}
73+
5874
if (emailTemplates.Count > 0)
5975
{
6076
var notificationTemplates = emailTemplates.Select(
@@ -89,6 +105,9 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
89105
JudgingStartDate = ent.JudgingStartDate,
90106
JudgingEndDate = ent.JudgingEndDate,
91107
EmailTemplates = emailTemplates,
108+
GitHubRepositorySettings = HackathonGitHubRepositorySettingsMapper.ToResponse(
109+
gitHubRepositorySettingsResult.Settings
110+
),
92111
},
93112
cancellation: ct
94113
);
@@ -110,4 +129,22 @@ private static Dictionary<string, string> NormalizeEmailTemplates(
110129
.GroupBy(kvp => kvp.Key.Trim().ToLowerInvariant(), StringComparer.OrdinalIgnoreCase)
111130
.ToDictionary(g => g.Key, g => g.Last().Value.Trim(), StringComparer.OrdinalIgnoreCase);
112131
}
132+
133+
private void AddGitHubRepositorySettingsErrors(
134+
IReadOnlyList<HackathonGitHubRepositorySettingsValidationError> errors
135+
)
136+
{
137+
foreach (var error in errors)
138+
{
139+
switch (error.Field)
140+
{
141+
case HackathonGitHubRepositorySettingsField.ApiKey:
142+
AddError(r => r.GitHubRepositorySettings!.ApiKey, error.Message);
143+
break;
144+
case HackathonGitHubRepositorySettingsField.OrganizationId:
145+
AddError(r => r.GitHubRepositorySettings!.OrganizationId, error.Message);
146+
break;
147+
}
148+
}
149+
}
113150
}

HackOMania.Api/Endpoints/Organizers/Hackathon/Create/Request.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
2+
13
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Create;
24

35
public class Request
@@ -16,4 +18,5 @@ public class Request
1618
public required DateTimeOffset JudgingEndDate { get; set; }
1719
public bool IsPublished { get; set; } = false;
1820
public Dictionary<string, string>? EmailTemplates { get; set; }
21+
public HackathonGitHubRepositorySettingsRequest? GitHubRepositorySettings { get; set; }
1922
}

HackOMania.Api/Endpoints/Organizers/Hackathon/Create/Response.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
2+
13
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Create;
24

35
public class Response
@@ -31,4 +33,6 @@ public class Response
3133
public required DateTimeOffset JudgingEndDate { get; set; }
3234

3335
public Dictionary<string, string> EmailTemplates { get; set; } = [];
36+
37+
public required HackathonGitHubRepositorySettingsResponse GitHubRepositorySettings { get; set; }
3438
}

HackOMania.Api/Endpoints/Organizers/Hackathon/Get/Endpoint.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using FastEndpoints;
22
using HackOMania.Api.Authorization;
33
using HackOMania.Api.Entities;
4+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
45
using SqlSugar;
56
using HackathonEntity = HackOMania.Api.Entities.Hackathon;
67

@@ -40,6 +41,9 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
4041
var emailTemplateMap = emailTemplates
4142
.GroupBy(t => t.EventKey, StringComparer.OrdinalIgnoreCase)
4243
.ToDictionary(g => g.Key, g => g.Last().TemplateId, StringComparer.OrdinalIgnoreCase);
44+
var gitHubRepositorySettings = await sql.Queryable<HackathonGitHubRepositorySettings>()
45+
.Where(s => s.HackathonId == hackathon.Id)
46+
.FirstAsync(ct);
4347

4448
await Send.OkAsync(
4549
new Response
@@ -59,6 +63,9 @@ await Send.OkAsync(
5963
JudgingStartDate = hackathon.JudgingStartDate,
6064
JudgingEndDate = hackathon.JudgingEndDate,
6165
EmailTemplates = emailTemplateMap,
66+
GitHubRepositorySettings = HackathonGitHubRepositorySettingsMapper.ToResponse(
67+
gitHubRepositorySettings
68+
),
6269
},
6370
ct
6471
);

HackOMania.Api/Endpoints/Organizers/Hackathon/Get/Response.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
2+
13
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Get;
24

35
public class Response
@@ -17,4 +19,5 @@ public class Response
1719
public required DateTimeOffset JudgingStartDate { get; init; }
1820
public required DateTimeOffset JudgingEndDate { get; init; }
1921
public Dictionary<string, string> EmailTemplates { get; init; } = [];
22+
public required HackathonGitHubRepositorySettingsResponse GitHubRepositorySettings { get; init; }
2023
}

HackOMania.Api/Endpoints/Organizers/Hackathon/Update/Endpoint.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using FastEndpoints;
22
using HackOMania.Api.Authorization;
33
using HackOMania.Api.Entities;
4+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
45
using SqlSugar;
56

67
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Update;
@@ -28,6 +29,10 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
2829
return;
2930
}
3031

32+
var gitHubRepositorySettings = await sql.Queryable<HackathonGitHubRepositorySettings>()
33+
.Where(s => s.HackathonId == hackathon.Id)
34+
.FirstAsync(ct);
35+
3136
if (!string.IsNullOrWhiteSpace(req.Name))
3237
{
3338
hackathon.Name = req.Name;
@@ -93,6 +98,22 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
9398
hackathon.IsPublished = req.IsPublished.Value;
9499
}
95100

101+
if (req.GitHubRepositorySettings is not null)
102+
{
103+
var gitHubRepositorySettingsResult = HackathonGitHubRepositorySettingsMutation.Apply(
104+
gitHubRepositorySettings,
105+
req.GitHubRepositorySettings
106+
);
107+
AddGitHubRepositorySettingsErrors(gitHubRepositorySettingsResult.Errors);
108+
if (ValidationFailed)
109+
{
110+
await Send.ErrorsAsync(cancellation: ct);
111+
return;
112+
}
113+
114+
gitHubRepositorySettings = gitHubRepositorySettingsResult.Settings;
115+
}
116+
96117
var emailTemplates = req.EmailTemplates is null
97118
? null
98119
: NormalizeEmailTemplates(req.EmailTemplates);
@@ -121,6 +142,33 @@ await sql.Deleteable<HackathonNotificationTemplate>()
121142

122143
await sql.Updateable(hackathon).ExecuteCommandAsync(ct);
123144

145+
if (req.GitHubRepositorySettings is not null)
146+
{
147+
if (HackathonGitHubRepositorySettingsMutation.ShouldPersist(gitHubRepositorySettings))
148+
{
149+
gitHubRepositorySettings!.HackathonId = hackathon.Id;
150+
var hasPersistedSettings = await sql.Queryable<HackathonGitHubRepositorySettings>()
151+
.Where(s => s.HackathonId == hackathon.Id)
152+
.AnyAsync(ct);
153+
154+
if (hasPersistedSettings)
155+
{
156+
await sql.Updateable(gitHubRepositorySettings).ExecuteCommandAsync(ct);
157+
}
158+
else
159+
{
160+
await sql.Insertable(gitHubRepositorySettings).ExecuteCommandAsync(ct);
161+
}
162+
}
163+
else
164+
{
165+
await sql.Deleteable<HackathonGitHubRepositorySettings>()
166+
.Where(s => s.HackathonId == hackathon.Id)
167+
.ExecuteCommandAsync(ct);
168+
gitHubRepositorySettings = null;
169+
}
170+
}
171+
124172
var persistedTemplates = await sql.Queryable<HackathonNotificationTemplate>()
125173
.Where(t => t.HackathonId == hackathon.Id)
126174
.ToListAsync(ct);
@@ -146,6 +194,9 @@ await Send.OkAsync(
146194
JudgingStartDate = hackathon.JudgingStartDate,
147195
JudgingEndDate = hackathon.JudgingEndDate,
148196
EmailTemplates = emailTemplateMap,
197+
GitHubRepositorySettings = HackathonGitHubRepositorySettingsMapper.ToResponse(
198+
gitHubRepositorySettings
199+
),
149200
},
150201
ct
151202
);
@@ -167,4 +218,22 @@ private static Dictionary<string, string> NormalizeEmailTemplates(
167218
.GroupBy(kvp => kvp.Key.Trim().ToLowerInvariant(), StringComparer.OrdinalIgnoreCase)
168219
.ToDictionary(g => g.Key, g => g.Last().Value.Trim(), StringComparer.OrdinalIgnoreCase);
169220
}
221+
222+
private void AddGitHubRepositorySettingsErrors(
223+
IReadOnlyList<HackathonGitHubRepositorySettingsValidationError> errors
224+
)
225+
{
226+
foreach (var error in errors)
227+
{
228+
switch (error.Field)
229+
{
230+
case HackathonGitHubRepositorySettingsField.ApiKey:
231+
AddError(r => r.GitHubRepositorySettings!.ApiKey, error.Message);
232+
break;
233+
case HackathonGitHubRepositorySettingsField.OrganizationId:
234+
AddError(r => r.GitHubRepositorySettings!.OrganizationId, error.Message);
235+
break;
236+
}
237+
}
238+
}
170239
}

HackOMania.Api/Endpoints/Organizers/Hackathon/Update/Request.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
2+
13
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Update;
24

35
/// <summary>
@@ -24,4 +26,5 @@ public class Request
2426
public DateTimeOffset? JudgingEndDate { get; set; }
2527
public bool? IsPublished { get; set; }
2628
public Dictionary<string, string>? EmailTemplates { get; set; }
29+
public HackathonGitHubRepositorySettingsRequest? GitHubRepositorySettings { get; set; }
2730
}

HackOMania.Api/Endpoints/Organizers/Hackathon/Update/Response.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using HackOMania.Api.Features.Hackathons.GitHubRepositorySettings;
2+
13
namespace HackOMania.Api.Endpoints.Organizers.Hackathon.Update;
24

35
public class Response
@@ -17,4 +19,5 @@ public class Response
1719
public required DateTimeOffset JudgingStartDate { get; init; }
1820
public required DateTimeOffset JudgingEndDate { get; init; }
1921
public Dictionary<string, string> EmailTemplates { get; init; } = [];
22+
public required HackathonGitHubRepositorySettingsResponse GitHubRepositorySettings { get; init; }
2023
}

HackOMania.Api/Endpoints/Participants/Hackathon/Submissions/Create/Endpoint.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
using HackOMania.Api.Authorization;
33
using HackOMania.Api.Entities;
44
using HackOMania.Api.Extensions;
5+
using HackOMania.Api.Services;
56
using Jint;
67
using SqlSugar;
78

89
namespace HackOMania.Api.Endpoints.Participants.Hackathon.Submissions.Create;
910

10-
public class Endpoint(ISqlSugarClient sql) : Endpoint<Request, Response>
11+
public class Endpoint(ISqlSugarClient sql, IGitHubRepositoryAutomationService gitHubRepositoryAutomation)
12+
: Endpoint<Request, Response>
1113
{
1214
public override void Configure()
1315
{
@@ -113,6 +115,25 @@ public override async Task HandleAsync(Request req, CancellationToken ct)
113115
return;
114116
}
115117

118+
var gitHubRepositorySettings = await sql.Queryable<HackathonGitHubRepositorySettings>()
119+
.Where(s => s.HackathonId == hackathon.Id)
120+
.FirstAsync(ct);
121+
122+
try
123+
{
124+
await gitHubRepositoryAutomation.ValidateAndMaybeForkAsync(
125+
gitHubRepositorySettings,
126+
req.RepoUri!,
127+
ct
128+
);
129+
}
130+
catch (GitHubRepositoryAutomationException ex)
131+
{
132+
AddError(r => r.RepoUri, ex.Message);
133+
await Send.ErrorsAsync(400, ct);
134+
return;
135+
}
136+
116137
var submission = new ChallengeSubmission
117138
{
118139
Id = Guid.NewGuid(),

HackOMania.Api/Endpoints/Participants/Hackathon/Submissions/Create/Validator.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,31 @@ public class Validator : Validator<Request>
77
{
88
public Validator()
99
{
10-
RuleFor(x => x.Title).Must(value => !string.IsNullOrWhiteSpace(value));
10+
RuleFor(x => x.Title).NotEmpty();
1111

1212
RuleFor(x => x.RepoUri)
13+
.Cascade(CascadeMode.Stop)
1314
.NotNull()
14-
.Must(uri => uri!.IsAbsoluteUri)
15-
.Must(uri => uri!.Scheme is "http" or "https");
15+
.Must(IsAbsoluteHttpUri);
1616

1717
RuleFor(x => x.DemoUri)
18-
.Must(uri => uri!.IsAbsoluteUri)
19-
.Must(uri => uri!.Scheme is "http" or "https")
18+
.Must(IsAbsoluteHttpUri)
2019
.When(x => x.DemoUri is not null);
2120

2221
RuleFor(x => x.SlidesUri)
22+
.Cascade(CascadeMode.Stop)
2323
.NotNull()
24-
.Must(uri => uri!.IsAbsoluteUri)
25-
.Must(uri => uri!.Scheme is "http" or "https");
24+
.Must(IsAbsoluteHttpUri);
2625

2726
RuleFor(x => x.DevpostUri)
28-
.Must(uri => uri!.IsAbsoluteUri)
29-
.Must(uri => uri!.Scheme is "http" or "https")
27+
.Must(IsAbsoluteHttpUri)
3028
.When(x => x.DevpostUri is not null);
3129
}
30+
31+
private static bool IsAbsoluteHttpUri(Uri? uri)
32+
{
33+
return uri is not null
34+
&& uri.IsAbsoluteUri
35+
&& uri.Scheme is "http" or "https";
36+
}
3237
}

0 commit comments

Comments
 (0)