Skip to content

Phase 8a-3: MinIO storage + streamed upload/download endpoints#16

Merged
JacobHaig merged 2 commits into
mainfrom
feat/file-relay-endpoints
Jun 2, 2026
Merged

Phase 8a-3: MinIO storage + streamed upload/download endpoints#16
JacobHaig merged 2 commits into
mainfrom
feat/file-relay-endpoints

Conversation

@JacobHaig
Copy link
Copy Markdown
Member

Phase 8a-3 — file relay endpoints (makes the link functional)

Third sub-phase of the file relay (plan §9). Adds the MinIO storage layer + web endpoints so an /upload link can actually receive and serve a file.

Changes

  • MinIO 7.0 SDK (Minio NuGet). UploadService gains:
    • StoreAsync — streams the uploaded file into the wisbot-uploads bucket (object key = link id), marks the row ready (filename, content-type, size, uploaded_at, expires_at = now + retention).
    • DownloadToAsync — streams the object back to the HTTP response.
    • GetUploadAsync — reads row metadata; EnsureBucketAsync.
  • WebService routes:
    • GET /u/{id} — upload form (pending) or download landing (ready); 404 unknown.
    • POST /u/{id} — streamed upload to MinIO, one file per link (409 if already used), 413 if over the cap.
    • GET /u/{id}/file — download, forced Content-Disposition: attachment (never inline → no XSS from user files).
    • Kestrel MaxRequestBodySize + MultipartBodyLengthLimit raised to WISBOT_UPLOAD_MAX_BYTES; filenames HTML-encoded.
  • ConfigWISBOT_MINIO_ENDPOINT/ACCESS_KEY/SECRET_KEY/BUCKET/USE_SSL + UploadEnabled.
  • .env.example + CLAUDE.md updated.

Verification

  • dotnet build — succeeds, 0 errors. This compile-verifies the MinIO 7.0 + ASP.NET Core API usage (catches signature errors).
  • docker-build CI validates the image.
  • ⚠️ Runtime upload/download streaming is not testable locally (no MinIO) — it'll be integration-tested at Phase 8c with a real MinIO bucket + browser. Everything else (build, health) is unaffected.

Next: 8b (30-day retention cleanup loop), then 8c (agent-cloud MinIO bucket + Caddy subdomain + redeploy).

Phase 8a-3 — makes the /upload link functional.

- Minio 7.0 SDK; UploadService gains StoreAsync (stream file -> MinIO bucket,
  mark row ready w/ filename/type/size/expiry), DownloadToAsync (stream back),
  GetUploadAsync (metadata), EnsureBucket.
- WebService routes: GET /u/{id} (upload form / download landing),
  POST /u/{id} (streamed upload, one file per link, 413 on oversize),
  GET /u/{id}/file (forced attachment download). Kestrel + form limits raised
  to WISBOT_UPLOAD_MAX_BYTES. Filenames HTML-encoded.
- Config: WISBOT_MINIO_ENDPOINT/ACCESS_KEY/SECRET_KEY/BUCKET/USE_SSL + UploadEnabled.
- .env.example + CLAUDE.md updated.

Compile-verified against the real Minio 7.0 + ASP.NET Core SDKs; runtime
upload/download streaming will be integration-tested at Phase 8c (real MinIO).
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization's overage spend limit has been reached.

Code review is billed via overage credits. To resume reviews, an organization admin can raise the monthly limit at claude.ai/admin-settings/claude-code.

Once credits are available, reopen this pull request to trigger a review.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

Warning

Review limit reached

@JacobHaig, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 14 minutes and 37 seconds. Learn how PR review limits work.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a69c44b3-1f1a-4a52-ad22-2e5f65dcfef3

📥 Commits

Reviewing files that changed from the base of the PR and between d3f1740 and 27010a7.

📒 Files selected for processing (3)
  • Bot.cs
  • Services/UploadService.cs
  • Services/WebService.cs

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds MinIO-backed uploads: new MinIO config and package, UploadService methods and DB state transitions to store/download files in MinIO, and WebService endpoints (/u/{id} GET/POST and /u/{id}/file GET) with request-size limits and wiring.

Changes

MinIO File Upload & Download

Layer / File(s) Summary
Configuration & Dependency Setup
.env.example, Config.cs, Wisbot.csproj, Bot.cs
Adds WISBOT_MINIO_* placeholders to .env.example, new Config MinIO properties and UploadEnabled flag, Minio NuGet package, and wires uploadService into WebService and conditional /upload command registration.
UploadService Storage & Persistence
Services/UploadService.cs
Adds UploadRecord, MinIO client/bucket bootstrapping, StoreAsync to stream into MinIO and transition DB state, DownloadToAsync to stream objects out, and GetUploadAsync plus internal state-transition helpers.
WebService HTTP Endpoints
Services/WebService.cs
WebService now accepts UploadService; configures Kestrel and multipart limits from Config.UploadMaxBytes; maps GET /u/{id}, POST /u/{id} (multipart upload) and GET /u/{id}/file (attachment) with validation, streaming, and error handling.
Documentation Updates
CLAUDE.md
Documents the expanded routes (/health, /u/{id}, /u/{id}/file), upload size/binding notes, and UploadService MinIO integration and related WISBOT_MINIO_* config.

Sequence Diagram

sequenceDiagram
  participant Client
  participant WebService
  participant UploadService
  participant MinIO
  participant Database

  Client->>WebService: POST /u/{id} with file
  WebService->>UploadService: StoreAsync(id, stream, filename, contentType, size)
  UploadService->>Database: Claim pending -> uploading
  UploadService->>MinIO: PutObject (key = id) with stream
  MinIO-->>UploadService: object stored
  UploadService->>Database: MarkReadyAsync(id, filename, contentType, size)
  UploadService-->>WebService: success
  WebService-->>Client: 302 Redirect to /u/{id}

  Client->>WebService: GET /u/{id}
  WebService->>UploadService: GetUploadAsync(id)
  UploadService->>Database: SELECT uploads row
  Database-->>UploadService: UploadRecord
  UploadService-->>WebService: UploadRecord
  WebService-->>Client: upload/download page HTML

  Client->>WebService: GET /u/{id}/file
  WebService->>UploadService: DownloadToAsync(id, output)
  UploadService->>MinIO: GetObject (key = id)
  MinIO-->>UploadService: file stream
  UploadService-->>WebService: streamed file
  WebService-->>Client: file attachment
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

  • uhstray-io/WisBot#15: Extends the same /upload + uploads table flow; related to storage and wiring changes.
  • uhstray-io/WisBot#14: Prior WebService startup and endpoint wiring changes that this PR builds upon.

Poem

🐰
I hopped a file into MinIO's care,
A secret link spun out of thin air,
From pending to ready, bytes took flight,
WebService serves it day and night,
Hooray — your uploads found the light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding MinIO storage and streamed upload/download endpoints for file relay functionality.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing MinIO integration, endpoint functionality, configuration, and verification approach.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/file-relay-endpoints

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Services/UploadService.cs`:
- Around line 113-133: The current flow allows race conditions because the row
is updated to 'ready' after the object write; fix by adding an atomic claim step
before uploading and by making MarkReadyAsync only succeed if the row is
currently claimed: implement a ClaimUploadAsync (or add logic before the MinIO
write) that runs an UPDATE uploads SET status='uploading', claimed_at = $now
WHERE id = $id AND status = 'pending' (or include a claim_token column if
desired) and check ExecuteNonQueryAsync affected rows > 0 to ensure the claim
succeeded; if the claim fails, abort the upload. Also change MarkReadyAsync to
include AND status = 'uploading' in its WHERE clause so it only flips a claimed
row to 'ready'. Ensure you reference the existing MarkReadyAsync and any new
ClaimUploadAsync when wiring the upload flow.

In `@Services/WebService.cs`:
- Around line 73-112: The upload endpoints are always mapped even when uploads
are disabled; check Config.UploadEnabled before exposing the upload surface and
either do not map the routes or return a 404/503 immediately. Specifically,
guard the MapGet("/u/{id}"), MapPost("/u/{id}") and MapGet("/u/{id}/file")
registration (the lambdas that call
uploadService.GetUploadAsync/StoreAsync/DownloadToAsync) so they are only
registered when UploadEnabled is true, and update the place where the bot
registers the /upload command in Bot.cs to only register the command when
Config.UploadEnabled is true; alternatively have the handlers short-circuit with
Results.StatusCode/NotFound when UploadEnabled is false to avoid calling
uploadService.StoreAsync.
- Around line 89-95: The current use of request.ReadFormAsync() materializes and
buffers multipart parts into IFormFile and then uses file.OpenReadStream(),
which prevents true streaming to MinIO; replace this with a section-based
streaming approach using MultipartReader: create a MultipartReader for the
incoming request boundary, iterate ReadNextSectionAsync() for each section,
detect the file section (Content-Disposition header with name "file"), and pass
the section.Body stream directly into uploadService.StoreAsync(id, section.Body,
filename, contentType, contentLength) instead of using IFormFile/opened streams;
ensure you preserve filename/contentType extraction from the section headers and
handle missing file/error cases similar to the existing return
Results.BadRequest behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 12c6c4d0-666c-45b7-b13c-04f083aa9519

📥 Commits

Reviewing files that changed from the base of the PR and between 0f892ee and d3f1740.

📒 Files selected for processing (7)
  • .env.example
  • Bot.cs
  • CLAUDE.md
  • Config.cs
  • Services/UploadService.cs
  • Services/WebService.cs
  • Wisbot.csproj
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Use the field keyword for properties with logic to avoid manual backing fields in C# 14

Use ?.= (null-conditional assignment) for defensive assignments

Use the extension keyword for grouping extension members (properties, methods, and static members)

Use primary constructors for classes and structs unless complex initialization logic is required

Always use [] for empty or populated collections/spans (e.g., List<int> items = [1, 2, 3];)

Use file-scoped namespaces with namespace MyProject.Models; syntax (no curly braces)

Rely on implicit usings and do not include standard system imports unless unique

Use var keyword only when the type is obvious from the right side of the assignment; use explicit types for method returns or literals

Use expression-bodied members with => for simple one-line methods and properties

Prefer ReadOnlySpan<char> for string parsing and slicing in performance-critical code

Use the required keyword for properties that must be initialized via object initializers instead of bloating constructors

Use raw string literals with """ for multi-line strings or JSON to avoid escaping quotes

Use PascalCase for classes, methods, properties, and public fields

Use camelCase for local variables and method arguments

Avoid _ prefixes for private fields; use the field keyword or this. prefix if absolutely necessary

For long operations, use Task.Run() fire-and-forget after immediate RespondAsync(), then FollowupAsync() for results to avoid 3-second Discord interaction timeout

Use ConcurrentDictionary and ConcurrentQueue throughout for thread safety; use lock for background service lists where needed

All settings must resolve via Config.Load() in this order: process environment variable → local .env file → default; Discord token may come from discord.key file; never hardcode site-specific values

Files:

  • Config.cs
  • Bot.cs
  • Services/WebService.cs
  • Services/UploadService.cs
Bot.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Register slash commands idempotently on OnReady (check existing before creating); use /removeallcommands terminal command to force-clear all registered commands

Files:

  • Bot.cs
Services/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Use manual constructor DI pattern: inject Terminal into all services; inject DiscordSocketClient into services that need to call Discord APIs after startup

Each service should live in its own file under Services/ directory; Bot.cs owns instances, wires events, and handles slash command routing

Files:

  • Services/WebService.cs
  • Services/UploadService.cs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T15:38:43.530Z
Learning: After completing code changes, run `dotnet build` to test and validate the changes worked
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T15:38:43.530Z
Learning: When making changes, ensure code is well-structured, follows best practices, and includes appropriate error handling; ask for clarification if new information is needed
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T15:38:43.530Z
Learning: Update CLAUDE.md and/or README.md to reflect code changes and provide clear documentation for future reference
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T15:38:43.530Z
Learning: At the start of every session, read `.claude/memory/MEMORY.md` to load context; use `/repo-memory` to save or retrieve memories
🔇 Additional comments (5)
CLAUDE.md (1)

41-42: LGTM!

.env.example (1)

34-39: LGTM!

Config.cs (1)

37-43: LGTM!

Also applies to: 111-115

Wisbot.csproj (1)

19-19: LGTM!

Bot.cs (1)

35-35: LGTM!

Comment thread Services/UploadService.cs
Comment thread Services/WebService.cs
Comment thread Services/WebService.cs Outdated
Comment on lines +89 to +95
try {
var form = await request.ReadFormAsync();
var file = form.Files.GetFile("file");
if (file is null || file.Length == 0) return Results.BadRequest("No file provided.");

await using var stream = file.OpenReadStream();
await uploadService.StoreAsync(id, stream, file.FileName, file.ContentType, file.Length);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🌐 Web query:

In ASP.NET Core, does HttpRequest.ReadFormAsync buffer multipart file uploads before exposing IFormFile, and what API is recommended for true streamed multipart upload handling?

💡 Result:

Yes, in ASP.NET Core, the HttpRequest.ReadFormAsync method (and associated model binding) does buffer multipart file uploads before exposing them as IFormFile objects [1][2]. Specifically, when you access the Form or Files collections, ASP.NET Core parses the multipart request, buffers the files in memory (up to a configurable threshold), and spills over to temporary disk files if the threshold is exceeded [1]. The default behavior for IFormFile includes these mechanisms: - MemoryBufferThreshold: The amount of data buffered in memory before transitioning to a temporary file on disk (default is 64 KB) [1]. - MultipartBodyLengthLimit: The maximum total size allowed for buffered form files (default is 128 MB) [1]. For true streamed multipart upload handling, which avoids buffering the entire file into memory or onto disk via the framework's automatic mechanisms, the recommended approach is to manually parse the request body using the MultipartReader utility [1][3]. Recommended streaming API approach: 1. Use MultipartReader: This class, available in the Microsoft.AspNetCore.WebUtilities namespace, allows you to read the request body section by section [1][2]. 2. Process Sections: Iterate through the multipart sections using ReadNextSectionAsync [2][3]. 3. Stream Data Directly: For each section, access the Body stream and pipe or copy that data directly to your destination (e.g., FileStream, cloud storage SDKs) instead of calling ReadFormAsync [1][3]. For low-level, high-performance scenarios, you can also access the request body via the IPipeReader interface, which is exposed through the request's BodyReader property, allowing for even more granular control over the incoming data stream [1][3].

Citations:


ReadFormAsync() breaks streamed multipart upload handling in WebService

File: Services/WebService.cs (lines 89-95)

Calling request.ReadFormAsync() materializes the multipart content into IFormFile (form.Files) and buffers it (memory up to MemoryBufferThreshold, then temp disk) before file.OpenReadStream() runs—so this path isn’t truly streaming end-to-end to MinIO. Switch to a section-based multipart reader like MultipartReader and stream each section’s Body directly into uploadService.StoreAsync instead of using ReadFormAsync().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Services/WebService.cs` around lines 89 - 95, The current use of
request.ReadFormAsync() materializes and buffers multipart parts into IFormFile
and then uses file.OpenReadStream(), which prevents true streaming to MinIO;
replace this with a section-based streaming approach using MultipartReader:
create a MultipartReader for the incoming request boundary, iterate
ReadNextSectionAsync() for each section, detect the file section
(Content-Disposition header with name "file"), and pass the section.Body stream
directly into uploadService.StoreAsync(id, section.Body, filename, contentType,
contentLength) instead of using IFormFile/opened streams; ensure you preserve
filename/contentType extraction from the section headers and handle missing
file/error cases similar to the existing return Results.BadRequest behavior.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping IFormFile/ReadFormAsync here, deliberately. It bounds in-memory buffering to ~64 KB and spills the remainder to a transient temp file that ASP.NET deletes when the request completes — so there's no 500 MB memory buffering, and we get a known Content-Length that MinIO streams with a definite object size. Switching to MultipartReader would feed MinIO an unknown-size stream (multipart with size -1), which I can't validate without a live MinIO and adds real risk. The cap is already enforced (MultipartBodyLengthLimit/MaxRequestBodySize → 413). I've noted true-streaming as an optional future optimization. Open to revisiting if temp-disk pressure proves real at 8c.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JacobHaig, that's a well-reasoned tradeoff — getting a definite Content-Length for the MinIO PutObjectArgs is worth the temp-disk spill, especially since ASP.NET cleans it up automatically and the 413 guard is already in place. Acknowledged; I'll defer this to Phase 8c if disk pressure becomes a concern.

Would you like me to resolve this review comment?


✏️ Learnings added
Learnt from: JacobHaig
Repo: uhstray-io/WisBot PR: 16
File: Services/WebService.cs:0-0
Timestamp: 2026-06-02T16:14:31.883Z
Learning: In `Services/WebService.cs` (WisBot, C#/ASP.NET Core), the POST `/u/{id}` endpoint deliberately uses `request.ReadFormAsync()` / `IFormFile` rather than `MultipartReader` to obtain a definite `Content-Length` (`file.Length`) for the MinIO `StoreAsync` call. `ReadFormAsync` buffers ~64 KB in memory and spills the rest to a transient temp file (auto-deleted by ASP.NET), avoiding large memory use while keeping the known size. Switching to `MultipartReader` would produce an unknown-size stream (size -1) that complicates MinIO uploads. True end-to-end streaming is noted as a future Phase 8c optimization.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

- StoreAsync now compare-and-swaps pending->uploading before the MinIO write and
  only marks ready a still-'uploading' row; reverts to pending on failure. Two
  concurrent POSTs can no longer both fill a single-use link (returns false ->
  409 for the loser).
- Gate the /u/{id} routes (WebService) and /upload command registration +
  handler (Bot/UploadService) on Config.UploadEnabled, so the feature isn't
  advertised when MinIO is unconfigured.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Bot.cs (1)

220-228: 💤 Low value

Consider making the handler registration conditional as well.

The command registration is now correctly gated on Config.UploadEnabled, but line 107 unconditionally adds the handler to the commands dictionary. For consistency, consider wrapping line 107 in the same conditional:

["remind"] = cmd => reminderService!.HandleRemindCommand(cmd),
["status"] = cmd => statusService!.HandleCommand(cmd),
["voicestats"] = cmd => voiceStatsService.HandleCommand(cmd),
["notify"] = cmd => voiceNotifyService!.HandleNotifyCommand(cmd),
["recording"] = cmd => voiceRecorder.HandleRecordingCommand(cmd),

Then after line 106:

if (Config.UploadEnabled)
    commands["upload"] = cmd => uploadService.HandleUploadCommand(cmd);

This isn't strictly necessary since HandleUploadCommand has its own guard (per Services/UploadService.cs), but it would eliminate an unnecessary dictionary entry when uploads are disabled.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Bot.cs` around lines 220 - 228, The commands dictionary currently includes an
unconditional entry for "upload" even when Config.UploadEnabled is false; update
the code that populates the commands dictionary so that the "upload" handler
(the entry assigning commands["upload"] = cmd =>
uploadService.HandleUploadCommand(cmd)) is added only when Config.UploadEnabled
is true (mirror the guild command registration), leaving the other handlers
(e.g., "remind" => reminderService.HandleRemindCommand, "status" =>
statusService.HandleCommand, etc.) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@Bot.cs`:
- Around line 220-228: The commands dictionary currently includes an
unconditional entry for "upload" even when Config.UploadEnabled is false; update
the code that populates the commands dictionary so that the "upload" handler
(the entry assigning commands["upload"] = cmd =>
uploadService.HandleUploadCommand(cmd)) is added only when Config.UploadEnabled
is true (mirror the guild command registration), leaving the other handlers
(e.g., "remind" => reminderService.HandleRemindCommand, "status" =>
statusService.HandleCommand, etc.) unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d62a6436-6bcf-43d2-be55-5dce1e1f6e0c

📥 Commits

Reviewing files that changed from the base of the PR and between d3f1740 and 27010a7.

📒 Files selected for processing (3)
  • Bot.cs
  • Services/UploadService.cs
  • Services/WebService.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • Services/WebService.cs
  • Services/UploadService.cs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Use the field keyword for properties with logic to avoid manual backing fields in C# 14

Use null-conditional assignment ?.= for defensive assignments

Use the extension keyword for grouping extension members (properties, methods, and static members)

Use primary constructors for classes and structs unless complex initialization logic is required

Always use [] for empty or populated collections/spans in C# 14 (e.g., List<int> items = [1, 2, 3];)

Use file-scoped namespaces with namespace MyProject.Models; syntax instead of curly braces

Rely on implicit usings and do not include standard system imports unless unique

Use var keyword only when the type is obvious from the right side of the assignment; use explicit types for method returns or literals

Use expression-bodied members => for simple one-line methods and properties

Prefer ReadOnlySpan<char> for string parsing and slicing

Use the required keyword for properties that must be initialized via object initializers instead of bloating constructors

Use raw string literals """ for multi-line strings or JSON to avoid escaping quotes

Use PascalCase for classes, methods, properties, and public fields

Use camelCase for local variables and method arguments

Avoid underscore prefixes for private fields; use the field keyword or this. prefix if absolutely necessary

Resolve all configuration settings via Config.Load() in this order: process environment variable → local .env file → default; never hardcode site-specific values

Store Discord ulong IDs as long (SQLite INTEGER) and cast at the boundary

Files:

  • Bot.cs
Bot.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Register slash commands idempotently on OnReady by checking existing commands before creating; use /removeallcommands terminal command to force-clear all registered commands

Files:

  • Bot.cs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T16:14:36.965Z
Learning: After completing code changes, run `dotnet build` to test and validate that changes work
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T16:14:36.965Z
Learning: Ensure code is well-structured, follows best practices, and includes appropriate error handling
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T16:14:36.965Z
Learning: Update CLAUDE.md and/or README.md after completing changes to reflect modifications and provide clear documentation
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T16:14:36.965Z
Learning: Store project knowledge in `.claude/memory/` committed to git; read `.claude/memory/MEMORY.md` at the start of every session to load context
🔇 Additional comments (1)
Bot.cs (1)

35-35: LGTM!

@JacobHaig
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@JacobHaig JacobHaig merged commit aa9ea14 into main Jun 2, 2026
1 of 2 checks passed
@JacobHaig JacobHaig deleted the feat/file-relay-endpoints branch June 2, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant