Skip to content

Phase 8a-2: /upload command + uploads table + link minting#15

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

Phase 8a-2: /upload command + uploads table + link minting#15
JacobHaig merged 2 commits into
mainfrom
feat/file-relay-upload-command

Conversation

@JacobHaig
Copy link
Copy Markdown
Member

Phase 8a-2 — /upload command + link minting

Second sub-phase of the file relay (plan §9). Mints the unguessable links; the web upload/download endpoints come in 8a-3.

Changes

  • Database.cs — new uploads table (id, owner_user_id, owner_username, filename, content_type, size_bytes, status, created_at, uploaded_at, expires_at) + expiry index.
  • Services/UploadService.cs/upload handler: generates a 128-bit base64url id (CSPRNG), inserts a pending row tagged with the requesting user, replies ephemerally with WISBOT_PUBLIC_BASE_URL/u/{id}.
  • Bot.cs — register the /upload guild command + route it.
  • Config.csWISBOT_PUBLIC_BASE_URL, WISBOT_UPLOAD_MAX_BYTES (default 500 MB), WISBOT_UPLOAD_RETENTION_DAYS (default 30).
  • .env.example + CLAUDE.md updated.

Notes

  • The minted link isn't functional until 8a-3 (the GET/POST /u/{id} endpoints + MinIO). That's fine — the feature only goes live at Phase 8c (agent-cloud MinIO + Caddy), after all sub-phases land.
  • Access model is trust-the-link (the id is the credential); the file is labeled with the Discord user who ran /upload.

Verification

  • dotnet build — succeeds, 0 warnings, 0 errors.
  • docker-build CI validates the image.

Next: 8a-3 — MinIO client + streamed upload/download (≤500 MB, attachment downloads), then 8b retention loop.

Phase 8a-2 of the file relay. Mints upload links; the web endpoints (8a-3)
make them functional.

- Database: new uploads table (id, owner, filename, content_type, size, status,
  created_at, uploaded_at, expires_at) + expiry index.
- UploadService: /upload handler generates a 128-bit base64url id, inserts a
  pending row, replies ephemerally with PUBLIC_BASE_URL/u/{id}.
- Bot: register /upload guild command + route it.
- Config: WISBOT_PUBLIC_BASE_URL, WISBOT_UPLOAD_MAX_BYTES (500MB),
  WISBOT_UPLOAD_RETENTION_DAYS (30).
- .env.example + CLAUDE.md updated.

Next (8a-3): MinIO client + streamed upload/download endpoints in WebService.
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

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

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0cdcbf6d-f0c0-4af7-a148-8c2fd2569ddb

📥 Commits

Reviewing files that changed from the base of the PR and between b88e884 and 6be3d9b.

📒 Files selected for processing (2)
  • Config.cs
  • Services/UploadService.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • Config.cs
  • Services/UploadService.cs

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added /upload slash command that mints secure, private upload links and returns an ephemeral message with the upload URL and max file size.
    • Upload links expire based on a configurable retention period.
  • Configuration

    • New environment settings to configure public base URL, per-file upload size limit, and upload retention days.
  • Documentation

    • Updated docs to describe the file relay upload feature.

Walkthrough

Adds file-relay support: new env config, uploads DB table, UploadService that mints secure upload links and persists pending uploads, and wiring for a /upload slash command. .env.example and CLAUDE.md updated.

Changes

File upload feature with /upload slash command

Layer / File(s) Summary
Configuration for upload settings
Config.cs, .env.example
Config class introduces PublicBaseUrl, UploadMaxBytes, and UploadRetentionDays properties, populated from environment variables with validation; .env.example documents the new feature settings.
Database uploads table schema
Database.cs
Database.Initialize() creates an uploads table with owner, filename, status, created_at, expires_at, and an index on expires_at for efficient record cleanup.
UploadService command implementation
Services/UploadService.cs
UploadService handles /upload slash commands by generating cryptographically secure token IDs, inserting pending upload records into the database (created_at/expires_at), constructing public upload URLs using Config.PublicBaseUrl, and logging the minting event.
Bot command handler wiring
Bot.cs
Bot class injects the UploadService, adds the handler to the command dispatch map, and registers the /upload guild slash command with description.
Project documentation update
CLAUDE.md
Documents the new UploadService.cs entry and Phase 8 file relay behavior for generating and validating upload links.

Sequence Diagram(s)

sequenceDiagram
  participant User as DiscordUser
  participant Bot as Bot
  participant Upload as UploadService
  participant DB as SQLiteDB
  participant Terminal as Terminal

  User->>Bot: /upload slash command
  Bot->>Upload: Invoke HandleUploadCommand(command)
  Upload->>Upload: GenerateId() (CSPRNG token)
  Upload->>DB: INSERT uploads (owner, status='pending', created_at, expires_at)
  Upload->>Terminal: Log "minted an upload link"
  Upload->>Bot: Ephemeral response with Config.PublicBaseUrl/<token> and max size info
  Bot->>User: Ephemeral reply (upload link)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • uhstray-io/WisBot#9: Overlaps at Config.Load() environment variable parsing; the file relay feature depends on the same config-loading infrastructure.

Poem

🐰 I mint a hop-sized, secret key tonight,
A little link to carry files in flight,
SQLite burrows hold the pending seed,
Terminal hums — the token's freed! 📤✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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 captures the main change: implementing the /upload command and link minting logic as part of Phase 8a-2 of the file relay feature.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, clearly explaining Phase 8a-2 implementation details, the specific files modified, and the rationale for the 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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/file-relay-upload-command

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 `@Config.cs`:
- Line 91: The current Config.cs line assigns any non-empty
WISBOT_PUBLIC_BASE_URL to PublicBaseUrl, which can produce broken relative
links; update the Get("WISBOT_PUBLIC_BASE_URL") handling to validate the value
with Uri.TryCreate and require Uri.IsAbsoluteUri (optionally normalize by
trimming trailing slashes) and if validation fails throw a clear
ConfigurationException (or otherwise stop startup) instead of silently accepting
it; reference the Get method, the "WISBOT_PUBLIC_BASE_URL" key, and the
PublicBaseUrl property when making this change.

In `@Services/UploadService.cs`:
- Line 28: The Log call in UploadService (await Log($"{command.User.Username}
minted upload link {id}");) is writing the bearer upload token/id to logs;
remove the token from logs and either log a non-sensitive indicator or redact
the id (e.g., log only that a link was created or log a truncated/hashed id), so
update the await Log(...) invocation in the method that handles minting (the
code using command.User.Username and id) to omit or redact the raw id and
instead log safe metadata (user, action, timestamp) or a one-way hash if needed
for correlation.
- Around line 47-54: The INSERT currently omits expires_at so pending uploads
never expire; update cmd.CommandText (the INSERT in UploadService.cs) to include
the expires_at column and add a parameter (e.g. "$expires"), and set that
parameter when calling cmd.Parameters.AddWithValue. Compute the value by taking
DateTime.UtcNow and adding the configured retention days (from
WISBOT_UPLOAD_RETENTION_DAYS or the service's retention constant) via
DateTime.UtcNow.AddDays(retentionDays) and pass it formatted consistently (e.g.
ISO/O) or as a DateTime depending on DB binding so minted rows persist the
advertised expiry.
🪄 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: 58ae62c7-94e1-4b16-903c-063a4649d4bc

📥 Commits

Reviewing files that changed from the base of the PR and between 3f09afc and b88e884.

📒 Files selected for processing (6)
  • .env.example
  • Bot.cs
  • CLAUDE.md
  • Config.cs
  • Database.cs
  • Services/UploadService.cs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.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 in C# 14

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

Always use [] for empty or populated collections/spans instead of new List<T>() syntax

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 only when the type is obvious from the right side; 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 operations

Use the required keyword for properties that must be initialized via object initializers

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 field keyword or this. prefix if necessary)

Use Task.Run() fire-and-forget with immediate RespondAsync() followed by FollowupAsync() for long operations to avoid 3-second Discord interaction timeout

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

All configuration must resolve via Config.Load() in order: process environment variable → local .env file → default; never hardcode site-specific values

Use ISO 8601 strings ("O" format) for DateTime storage in the SQLite database

Use Discord.Net 3.19.0-beta.1 (beta version required for voice audio stream features like IAudioClient.GetStreams())

Files:

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

📄 CodeRabbit inference engine (CLAUDE.md)

Use SQLite via Microsoft.Data.Sqlite; store Discord ulong IDs as long (SQLite INTEGER) and cast at boundaries

Files:

  • Database.cs
Services/**/*.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Manual constructor dependency injection: inject Terminal into all services and DiscordSocketClient into services that need Discord APIs after startup

Each service lives in its own file under Services/ with Bot.cs owning instances, wiring events, and handling slash command routing

Files:

  • Services/UploadService.cs
Bot.cs

📄 CodeRabbit inference engine (CLAUDE.md)

Slash commands must be registered idempotently on OnReady() (check existing before creating); use /removeallcommands terminal command to force-clear

Enable GuildMembers privileged intent for UserJoined events (must be enabled in Discord Developer Portal)

Files:

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

Timestamp: 2026-06-02T14:29:22.592Z
Learning: Run `dotnet build` to validate changes before completing tasks
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T14:29:22.592Z
Learning: Include appropriate error handling and ensure code is well-structured following best practices
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T14:29:22.592Z
Learning: Read `.claude/memory/MEMORY.md` at the start of every session and use `/repo-memory` to save or retrieve project memories
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T14:29:22.592Z
Learning: Use manual constructor dependency injection without a DI container; follow the pattern established in this project
Learnt from: CR
Repo: uhstray-io/WisBot

Timestamp: 2026-06-02T14:29:22.592Z
Learning: Document changes in CLAUDE.md and/or README.md to reflect architectural changes and provide clear documentation for future reference
🔇 Additional comments (6)
CLAUDE.md (1)

42-42: LGTM!

Config.cs (1)

31-35: LGTM!

.env.example (1)

25-32: LGTM!

Database.cs (1)

80-92: LGTM!

Services/UploadService.cs (1)

33-37: LGTM!

Bot.cs (1)

15-15: LGTM!

Also applies to: 107-107, 220-228

Comment thread Config.cs Outdated
Comment thread Services/UploadService.cs Outdated
Comment thread Services/UploadService.cs Outdated
…bit)

- Config: validate WISBOT_PUBLIC_BASE_URL is an absolute http(s) URL (fail fast)
  and normalize trailing slash, so a typo can't mint broken links.
- UploadService: don't log the link id (it's the bearer credential).
- UploadService: set expires_at = created_at + retention on the pending insert,
  so minted links age out and retention is enforced (refreshed on upload in 8a-3).
@JacobHaig JacobHaig merged commit 0f892ee into main Jun 2, 2026
2 checks passed
@JacobHaig JacobHaig deleted the feat/file-relay-upload-command branch June 2, 2026 15:31
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