# 🥷 WishNinja A self-hosted **gift wishlist manager** for a closed group (family/friends). Create wishlists, add items with links, prices and images, and let others **claim gifts so the owner never sees what's been claimed** — preserving the surprise while preventing duplicate gifts. Built with **ASP.NET Core Blazor (.NET 10)**, **EF Core + SQLite**, and **ASP.NET Core Identity**. Ships as a single Docker image; all state lives in one mounted volume. ## Features - **Wishlists & items** — title/description, per-item link, price, priority, quantity and image. - **Three ways to add an image** — upload a file, fetch from an image URL, or just `Ctrl`+`V` a copied image. **All images are stored locally** on the data volume; pasted URLs are downloaded server-side and saved as files, so a wishlist never breaks when the source URL goes away. - **Owner-hidden claims** — when someone claims/reserves an item, the list owner can never see it. Other viewers see claims (and remaining quantity) to avoid double-gifting. Enforced at the query layer, not just hidden in the UI. - **Sharing** — a list is visible to all members or only to specific people you choose. - **Invite-only accounts** — no open registration. Admins invite by email; the recipient follows a single-use link to set their display name + password. Password resets via email. - **Admin area** — manage users (promote/disable) and invitations. ## Quick start (Docker Compose) ```bash docker compose up --build ``` Then open , and sign in with the bootstrap admin credentials from `docker-compose.yml` (`SEED_ADMIN_EMAIL` / `SEED_ADMIN_PASSWORD`). Change these before first run. > If SMTP isn't configured, emails aren't sent — but invite links are still shown directly in the > admin **Invitations** page, so you can onboard users without an email server. ## Configuration All settings are environment variables (double-underscore maps to nested config). | Variable | Purpose | Default | |---|---|---| | `WishNinja__DataPath` | Directory for the SQLite db, uploads and keys | `/data` (in container) | | `WishNinja__BaseUrl` | Public URL, used in email links | `https://localhost:7777` | | `SEED_ADMIN_EMAIL` | First-run bootstrap admin email | — | | `SEED_ADMIN_PASSWORD` | First-run bootstrap admin password | — | | `WishNinja__Smtp__Host` | SMTP server (empty ⇒ emails logged, not sent) | empty | | `WishNinja__Smtp__Port` | SMTP port | `587` | | `WishNinja__Smtp__UseStartTls` | Use STARTTLS | `true` | | `WishNinja__Smtp__User` / `__Password` | SMTP credentials | empty | | `WishNinja__Smtp__FromAddress` / `__FromName` | Sender identity | — | | `WishNinja__Uploads__MaxBytes` | Max upload size in bytes | `5242880` (5 MB) | | `WishNinja__Invites__ExpiryHours` | Invite link lifetime | `168` (7 days) | | `ConnectionStrings__DefaultConnection` | Override the SQLite connection string | derived from `DataPath` | The container listens on **port 8080** over plain HTTP — terminate TLS at your reverse proxy. ## Running on Unraid 1. **Build & publish the image.** Push to your Gitea repo; the included [`.gitea/workflows/build.yml`](.gitea/workflows/build.yml) runs the tests and pushes `your-gitea-host/youruser/wishninja:latest` to Gitea's built-in container registry. (Or build locally and push manually.) 2. **Add the container** (Docker tab → *Add Container*, or import [`unraid-template.xml`](unraid-template.xml)): - **Repository:** `your-gitea-host/youruser/wishninja:latest` - **Port:** host `8080` → container `8080` - **Path:** host `/mnt/user/appdata/wishninja` → container `/data` - **Env vars:** at minimum `SEED_ADMIN_EMAIL`, `SEED_ADMIN_PASSWORD`, `WishNinja__BaseUrl`, and SMTP settings. 3. **Reverse proxy (SWAG / Nginx Proxy Manager).** Blazor's interactive server mode uses **SignalR over WebSockets**, so the proxy **must have WebSocket support enabled** (NPM: the "Websockets Support" toggle; SWAG: included in the default proxy-conf samples). Everything (database, uploaded images, data-protection keys) lives under `/data`, so backing up `/mnt/user/appdata/wishninja` backs up the whole app, and container updates preserve all state. ## Development ```bash # Run locally (uses ./Data for the SQLite db by default) cd src/WishNinja SEED_ADMIN_EMAIL=admin@local SEED_ADMIN_PASSWORD='Admin!2345' dotnet run # Run the tests dotnet test ``` EF Core migrations are applied automatically on startup. To add a migration after changing entities: ```bash dotnet ef migrations add --project src/WishNinja/WishNinja.csproj --output-dir Data/Migrations ``` ## Project layout ``` src/WishNinja/ ASP.NET Core Blazor Web App Data/ EF Core context, entities, migrations Services/ EmailSender, InviteService, ImageService, WishlistService, seeding Components/ Razor components (Pages, Account, Wishlists, Layout) wwwroot/js/ clipboard-paste interop tests/WishNinja.Tests/ xUnit tests (claim privacy, invite lifecycle, image validation) Dockerfile multi-stage build → aspnet:10.0 runtime docker-compose.yml local + Unraid reference .gitea/workflows/ CI: test + build/push image ```