Add a Gmail App Password walkthrough to the README and note in CLAUDE.md that production email is configured, including the Unraid-template persistence caveat. Co-Authored-By: Claude Opus 4.8 <[email protected]>
134 lines
6.3 KiB
Markdown
134 lines
6.3 KiB
Markdown
# 🥷 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 <http://localhost:8080>, 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.
|
|
|
|
### Email (Gmail example)
|
|
|
|
Invites and password resets need SMTP. Gmail works via an **App Password** (not your account
|
|
password):
|
|
|
|
1. Enable **2-Step Verification** on the Google account, then create an App Password at
|
|
<https://myaccount.google.com/apppasswords>. You get a 16-character password — use it **without
|
|
spaces**.
|
|
2. Set the SMTP variables:
|
|
|
|
| Variable | Value |
|
|
|---|---|
|
|
| `WishNinja__Smtp__Host` | `smtp.gmail.com` |
|
|
| `WishNinja__Smtp__Port` | `587` |
|
|
| `WishNinja__Smtp__UseStartTls` | `true` |
|
|
| `WishNinja__Smtp__User` | your full Gmail address |
|
|
| `WishNinja__Smtp__Password` | the 16-char app password (no spaces) |
|
|
| `WishNinja__Smtp__FromAddress` | same Gmail address (Gmail requires From = authenticated user) |
|
|
| `WishNinja__Smtp__FromName` | e.g. `WishNinja` |
|
|
|
|
Other providers work the same way — just swap Host/Port/User/Password. Leave `Smtp__Host` empty to
|
|
disable email entirely (invite links still appear in the admin UI).
|
|
|
|
> **Unraid note:** set these as container variables via the template so they persist across
|
|
> *Force Update* redeploys — variables added only to a running container are lost on recreate.
|
|
|
|
## 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 <Name> --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
|
|
```
|