Skip to content

Commit ce5c228

Browse files
ManuelMaduclaude
andcommitted
Add ops & quality quick wins: health check, format gate, Dependabot, license
- /health endpoint backed by an EF Core DbContext check (verifies the DB). - CI format gate: .editorconfig + `dotnet format --verify-no-changes`. - Dependabot for NuGet + GitHub Actions (weekly). - MIT LICENSE. - README updated to reflect these as implemented (moved out of limitations). Applying the format rules also normalized whitespace/braces in a few existing files (cosmetic only; build + 27 tests + format gate all green). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5a7ef69 commit ce5c228

11 files changed

Lines changed: 473 additions & 121 deletions

File tree

.editorconfig

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
indent_style = space
9+
indent_size = 4
10+
11+
[*.{json,yml,yaml,csproj,props,targets}]
12+
indent_size = 2
13+
14+
[*.md]
15+
trim_trailing_whitespace = false
16+
17+
[*.{cs,razor}]
18+
indent_size = 4
19+
20+
# ---- C# code style ----
21+
[*.cs]
22+
# Prefer modern C# idioms already used across the codebase
23+
csharp_style_namespace_declarations = file_scoped:warning
24+
csharp_using_directive_placement = outside_namespace:warning
25+
dotnet_sort_system_directives_first = true
26+
dotnet_separate_import_directive_groups = false
27+
28+
csharp_style_var_for_built_in_types = false:silent
29+
csharp_style_var_when_type_is_apparent = true:silent
30+
csharp_prefer_braces = true:warning
31+
csharp_style_expression_bodied_methods = when_on_single_line:silent
32+
csharp_style_expression_bodied_properties = true:silent
33+
34+
# Nullable / quality
35+
dotnet_style_qualification_for_field = false:warning
36+
dotnet_style_qualification_for_property = false:warning
37+
dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
38+
dotnet_style_readonly_field = true:warning

.github/dependabot.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
version: 2
2+
updates:
3+
# .NET NuGet dependencies across the solution
4+
- package-ecosystem: "nuget"
5+
directory: "/"
6+
schedule:
7+
interval: "weekly"
8+
open-pull-requests-limit: 5
9+
groups:
10+
microsoft:
11+
patterns:
12+
- "Microsoft.*"
13+
- "System.*"
14+
15+
# GitHub Actions used in workflows
16+
- package-ecosystem: "github-actions"
17+
directory: "/"
18+
schedule:
19+
interval: "weekly"

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ jobs:
2222
- name: Restore
2323
run: dotnet restore Qwertide.sln
2424

25+
- name: Verify formatting
26+
run: dotnet format Qwertide.sln --verify-no-changes --no-restore
27+
2528
- name: Build
2629
run: dotnet build Qwertide.sln --configuration Release --no-restore -warnaserror
2730

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Manuel Madubugini
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 318 additions & 75 deletions
Large diffs are not rendered by default.

src/Qwertide.Api/Data/DbSeeder.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ public static void Seed(QwertideDbContext db)
1919
var now = DateTime.UtcNow;
2020
db.Scores.AddRange(
2121
new Score { PlayerName = "kayl_okafor", Wpm = 138, Accuracy = 98.1, DurationSecs = 21.4, CreatedAtUtc = now.AddDays(-2) },
22-
new Score { PlayerName = "m.santoro", Wpm = 121, Accuracy = 96.7, DurationSecs = 24.9, CreatedAtUtc = now.AddDays(-5) },
23-
new Score { PlayerName = "ferra", Wpm = 117, Accuracy = 99.2, DurationSecs = 25.8, CreatedAtUtc = now.AddHours(-9) },
24-
new Score { PlayerName = "noah_si", Wpm = 104, Accuracy = 94.3, DurationSecs = 29.1, CreatedAtUtc = now.AddDays(-1) },
25-
new Score { PlayerName = "tunde.dev", Wpm = 99, Accuracy = 97.5, DurationSecs = 30.6, CreatedAtUtc = now.AddDays(-3) },
26-
new Score { PlayerName = "p_renaud", Wpm = 92, Accuracy = 95.0, DurationSecs = 32.8, CreatedAtUtc = now.AddHours(-30) },
27-
new Score { PlayerName = "isla.k", Wpm = 86, Accuracy = 98.8, DurationSecs = 35.2, CreatedAtUtc = now.AddDays(-6) });
22+
new Score { PlayerName = "m.santoro", Wpm = 121, Accuracy = 96.7, DurationSecs = 24.9, CreatedAtUtc = now.AddDays(-5) },
23+
new Score { PlayerName = "ferra", Wpm = 117, Accuracy = 99.2, DurationSecs = 25.8, CreatedAtUtc = now.AddHours(-9) },
24+
new Score { PlayerName = "noah_si", Wpm = 104, Accuracy = 94.3, DurationSecs = 29.1, CreatedAtUtc = now.AddDays(-1) },
25+
new Score { PlayerName = "tunde.dev", Wpm = 99, Accuracy = 97.5, DurationSecs = 30.6, CreatedAtUtc = now.AddDays(-3) },
26+
new Score { PlayerName = "p_renaud", Wpm = 92, Accuracy = 95.0, DurationSecs = 32.8, CreatedAtUtc = now.AddHours(-30) },
27+
new Score { PlayerName = "isla.k", Wpm = 86, Accuracy = 98.8, DurationSecs = 35.2, CreatedAtUtc = now.AddDays(-6) });
2828

2929
db.SaveChanges();
3030
}
Lines changed: 33 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,44 @@
1-
using System;
1+
using System;
22
using Microsoft.EntityFrameworkCore.Migrations;
33

44
#nullable disable
55

6-
namespace Qwertide.Api.Migrations
6+
namespace Qwertide.Api.Migrations;
7+
8+
/// <inheritdoc />
9+
public partial class InitialCreate : Migration
710
{
811
/// <inheritdoc />
9-
public partial class InitialCreate : Migration
12+
protected override void Up(MigrationBuilder migrationBuilder)
1013
{
11-
/// <inheritdoc />
12-
protected override void Up(MigrationBuilder migrationBuilder)
13-
{
14-
migrationBuilder.CreateTable(
15-
name: "Scores",
16-
columns: table => new
17-
{
18-
Id = table.Column<int>(type: "INTEGER", nullable: false)
19-
.Annotation("Sqlite:Autoincrement", true),
20-
PlayerName = table.Column<string>(type: "TEXT", maxLength: 30, nullable: false),
21-
Wpm = table.Column<int>(type: "INTEGER", nullable: false),
22-
Accuracy = table.Column<double>(type: "REAL", nullable: false),
23-
DurationSecs = table.Column<double>(type: "REAL", nullable: false),
24-
PassageId = table.Column<int>(type: "INTEGER", nullable: true),
25-
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
26-
},
27-
constraints: table =>
28-
{
29-
table.PrimaryKey("PK_Scores", x => x.Id);
30-
});
14+
migrationBuilder.CreateTable(
15+
name: "Scores",
16+
columns: table => new
17+
{
18+
Id = table.Column<int>(type: "INTEGER", nullable: false)
19+
.Annotation("Sqlite:Autoincrement", true),
20+
PlayerName = table.Column<string>(type: "TEXT", maxLength: 30, nullable: false),
21+
Wpm = table.Column<int>(type: "INTEGER", nullable: false),
22+
Accuracy = table.Column<double>(type: "REAL", nullable: false),
23+
DurationSecs = table.Column<double>(type: "REAL", nullable: false),
24+
PassageId = table.Column<int>(type: "INTEGER", nullable: true),
25+
CreatedAtUtc = table.Column<DateTime>(type: "TEXT", nullable: false)
26+
},
27+
constraints: table =>
28+
{
29+
table.PrimaryKey("PK_Scores", x => x.Id);
30+
});
3131

32-
migrationBuilder.CreateIndex(
33-
name: "IX_Scores_Wpm",
34-
table: "Scores",
35-
column: "Wpm");
36-
}
32+
migrationBuilder.CreateIndex(
33+
name: "IX_Scores_Wpm",
34+
table: "Scores",
35+
column: "Wpm");
36+
}
3737

38-
/// <inheritdoc />
39-
protected override void Down(MigrationBuilder migrationBuilder)
40-
{
41-
migrationBuilder.DropTable(
42-
name: "Scores");
43-
}
38+
/// <inheritdoc />
39+
protected override void Down(MigrationBuilder migrationBuilder)
40+
{
41+
migrationBuilder.DropTable(
42+
name: "Scores");
4443
}
4544
}

src/Qwertide.Api/Program.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
builder.Services.AddDbContext<QwertideDbContext>(options =>
4040
options.UseSqlite(builder.Configuration.GetConnectionString("Qwertide")));
4141

42+
// Liveness/readiness probe at /health that also verifies the database connection,
43+
// so a deploy or platform health check fails fast if the data store is unreachable.
44+
builder.Services.AddHealthChecks()
45+
.AddDbContextCheck<QwertideDbContext>();
46+
4247
builder.Services.AddControllers();
4348
builder.Services.AddEndpointsApiExplorer();
4449
builder.Services.AddSwaggerGen();
@@ -99,6 +104,7 @@
99104
app.UseRateLimiter();
100105
app.UseAuthorization();
101106

107+
app.MapHealthChecks("/health");
102108
app.MapControllers();
103109
// Unmatched API routes must return a real 404 instead of falling through to the
104110
// SPA shell below, which would answer 200 with index.html and surface on the

src/Qwertide.Api/Qwertide.Api.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
<PrivateAssets>all</PrivateAssets>
1414
</PackageReference>
1515
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.2" />
16+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.2" />
1617
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
1718
</ItemGroup>
1819

src/Qwertide.Client/Services/PassageLibrary.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,11 @@ public sealed class PassageLibrary
5252
public Passage Random(Difficulty difficulty)
5353
{
5454
var pool = _passages.Where(p => p.Difficulty == difficulty).ToArray();
55-
if (pool.Length == 0) pool = _passages;
55+
if (pool.Length == 0)
56+
{
57+
pool = _passages;
58+
}
59+
5660
return pool[_rng.Next(pool.Length)];
5761
}
5862

0 commit comments

Comments
 (0)