Add bug-report / log-upload endpoint (anonymous POST + admin retrieval) - #251
Open
itpick wants to merge 1 commit into
Open
Add bug-report / log-upload endpoint (anonymous POST + admin retrieval)#251itpick wants to merge 1 commit into
itpick wants to merge 1 commit into
Conversation
Lets the game client send a log (plus optional player name + note) and get back a
short Report ID, so players can report bugs and crashes without leaving the game.
- POST ut/api/logs — anonymous, multipart/form-data (fields "player", "note";
file part "log"). 25 MB cap, per-IP rate limit (10/hour, in-memory sliding
window), client IP stored only as a SHA-256 hash. Body stored in GridFS (bucket
"bugreports") with a LogReports metadata collection and an 8-char Report ID.
Returns {"reportId":"XXXXXXXX"}.
- GET ut/api/logs — admin only, paged metadata list (no bodies).
- GET ut/api/logs/{reportId} — admin only, streams the stored log.
New LogsController + LogReportService (GridFS + indexes) + LogReportRateLimitService
+ LogReport model + DTOs + LogReportSettings (bucket/size/limit tunables with
defaults). Wired into DI and startup index creation. Adds MongoDB.Driver.GridFS
(same version as the existing MongoDB.Driver). Admin endpoints reuse the existing
bearer auth + ACL_Maintenance/Admin check.
Saibamen
requested changes
Aug 3, 2026
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" /> | ||
| <PackageReference Include="Microsoft.Extensions.Options" Version="6.0.0" /> | ||
| <PackageReference Include="MongoDB.Driver.GridFS" Version="2.25.0" /> |
Collaborator
There was a problem hiding this comment.
This is not latest version
There was a problem hiding this comment.
Pull request overview
Adds an anonymous in-game log upload (“bug report”) flow backed by MongoDB GridFS, plus admin-only endpoints to list and download uploaded logs. This fits into the existing UT API surface by introducing a new controller + services and wiring them into DI/startup index creation.
Changes:
- Adds
POST ut/api/logs(anonymous multipart upload) returning an 8-char report ID, plus admin-onlyGET ut/api/logsandGET ut/api/logs/{reportId}retrieval endpoints. - Implements
LogReportService(GridFS + metadata collection + indexes) and an in-memory per-IP (hashed) sliding-window rate limiter. - Wires new settings (
LogReportSettings), DI registrations, startup index creation, and adds the GridFS driver package.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| UT4MasterServer/Program.cs | Registers LogReportSettings, LogReportService, and LogReportRateLimitService in DI. |
| UT4MasterServer/Controllers/UT/LogsController.cs | Implements upload + admin listing/download endpoints and request validation. |
| UT4MasterServer.Services/UT4MasterServer.Services.csproj | Adds MongoDB.Driver.GridFS package reference. |
| UT4MasterServer.Services/Singleton/LogReportRateLimitService.cs | Adds in-memory sliding-window rate limiting for anonymous uploads. |
| UT4MasterServer.Services/Scoped/LogReportService.cs | Adds GridFS-backed persistence and metadata/index management for reports. |
| UT4MasterServer.Services/Hosted/ApplicationStartupService.cs | Ensures new MongoDB indexes are created on startup. |
| UT4MasterServer.Models/Settings/LogReportSettings.cs | Adds configurable bucket name, max size, and rate-limit parameters. |
| UT4MasterServer.Models/DTO/Response/LogReportResponse.cs | Adds admin-facing metadata response DTO for listing reports. |
| UT4MasterServer.Models/DTO/Response/LogReportCreatedResponse.cs | Adds upload response DTO containing reportId. |
| UT4MasterServer.Models/Database/LogReport.cs | Adds MongoDB model for log report metadata (GridFS file reference + fields). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+132
to
+133
| if (skip < 0) skip = 0; | ||
| if (limit < 1 || limit > 100) limit = 100; |
Comment on lines
+190
to
+193
| private static bool IsValidReportID(string reportID) | ||
| { | ||
| return reportID.Length == 8 && reportID.All(x => x is (>= '0' and <= '9') or (>= 'A' and <= 'Z')); | ||
| } |
Comment on lines
+11
to
+28
| private const int CleanupThreshold = 1024; | ||
|
|
||
| private readonly Dictionary<string, List<DateTime>> uploadsPerClient = new(); | ||
|
|
||
| /// <summary> | ||
| /// Records an upload attempt for <paramref name="clientKey"/> and returns | ||
| /// whether the attempt is allowed to proceed. | ||
| /// </summary> | ||
| public bool TryRecordUpload(string clientKey, int maxUploads, TimeSpan window) | ||
| { | ||
| var now = DateTime.UtcNow; | ||
|
|
||
| lock (uploadsPerClient) // Make sure counters are thread-safe | ||
| { | ||
| if (uploadsPerClient.Count >= CleanupThreshold) | ||
| { | ||
| RemoveStaleClients(now, window); | ||
| } |
Comment on lines
+195
to
+199
| private static string HashIP(IPAddress ip) | ||
| { | ||
| var hashedBytes = SHA256.HashData(Encoding.UTF8.GetBytes(ip.ToString())); | ||
| return Convert.ToHexString(hashedBytes).ToLower(); | ||
| } |
Comment on lines
+61
to
+85
| var reportID = await GenerateUniqueReportIDAsync(); | ||
|
|
||
| ObjectId fileID = await bucket.UploadFromStreamAsync(reportID, logStream, new GridFSUploadOptions() | ||
| { | ||
| Metadata = new BsonDocument() | ||
| { | ||
| { "ReportID", reportID }, | ||
| { "ContentType", contentType } | ||
| } | ||
| }); | ||
|
|
||
| var report = new LogReport() | ||
| { | ||
| ReportID = reportID, | ||
| FileID = fileID, | ||
| Player = player, | ||
| Note = note, | ||
| ClientIPHash = clientIPHash, | ||
| UserAgent = userAgent, | ||
| CreatedAt = DateTime.UtcNow, | ||
| Length = length, | ||
| ContentType = contentType | ||
| }; | ||
|
|
||
| await logReportCollection.InsertOneAsync(report); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds an in-game bug-report / log-upload ingest so players can send a log (plus an optional name + note) and get back a short Report ID, without leaving the game. Admins can list and download the reports.
Endpoints
POST ut/api/logs— anonymous,multipart/form-data:player(optional),note(optional); file partlogbugreports) with aLogReportsmetadata doc + an 8-char Crockford Report ID{"reportId":"XXXXXXXX"}GET ut/api/logs— admin only, paged metadata list (no bodies)GET ut/api/logs/{reportId}— admin only, streams the stored logNotes
LogsController+LogReportService(GridFS + indexes) +LogReportRateLimitService+LogReportmodel + DTOs +LogReportSettings(bucket/size/limit tunables, sensible defaults).MongoDB.Driver.GridFSat the same version as the existingMongoDB.Driver.ACL_Maintenance/Admincheck.Built + running in production on a downstream deployment.