- JavaScript 94.6%
- LSL 2.3%
- PLpgSQL 2.1%
- CSS 0.7%
- Dockerfile 0.3%
| backend | ||
| frontend | ||
| lslScripts | ||
| .env | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| README.md | ||
SLWishes 🎁
A self-hosted Second Life Marketplace wishlist sharing app.
Share your wishlist at: https://slwishes.yourdomain.com/share/kat.morningstar
Stack
- Backend — Node.js 22 + Express (ESM)
- Frontend — Next.js 14 (App Router)
- Database — Your existing PostgreSQL container (
172.29.0.50) - Networking — Joins your existing
nginxDocker network
Setup
1. Create the database
Connect to your existing Postgres and create the database:
CREATE DATABASE slwishes;
Then run the migration:
psql -h 172.29.0.50 -U apenwell -d slwishes -f backend/migrations/001_initial.sql
Or from inside Docker after the backend starts:
docker exec slwishes-backend node src/db/migrate.js
2. Configure environment
cp .env.example .env
Edit .env:
DB_USER=apenwell
DB_PASSWORD=WesterN061092
DB_NAME=slwishes
JWT_SECRET=some_very_long_random_string_change_this
PUBLIC_API_URL=https://slwishes.yourdomain.com/api
3. Build & run
docker compose up -d --build
4. Nginx Proxy Manager
Add two proxy hosts in NPM:
| Domain | Forward to | Port |
|---|---|---|
slwishes.yourdomain.com |
slwishes-frontend |
3000 |
slwishes.yourdomain.com/api/* |
slwishes-backend |
3001 |
Or if using a path-based setup, add a custom location /api/ → http://slwishes-backend:3001.
API Reference
Auth
| Method | Path | Body | Notes |
|---|---|---|---|
POST |
/api/auth/register |
{uuid, username, display_name?, pin} |
Creates avatar + default wishlist, returns session token |
POST |
/api/auth/login |
{identifier, pin} |
identifier = username or UUID |
POST |
/api/auth/logout |
— | Invalidates session |
Public
| Method | Path | Notes |
|---|---|---|
GET |
/api/share/:identifier |
identifier = username or UUID; auto-redirects UUID → username |
POST |
/api/items/:id/gift |
{purchased_by?} — public endpoint, no auth needed |
Authenticated (Bearer token required)
| Method | Path | Notes |
|---|---|---|
GET |
/api/avatar/me |
Your profile |
PATCH |
/api/avatar/me |
Update display_name |
POST |
/api/avatar/me/change-pin |
{current_pin, new_pin} |
PATCH |
/api/wishlist |
{title?, description?, is_public?} |
POST |
/api/items |
Add item |
POST |
/api/items/scrape |
{url} — scrape MP listing and return prefilled data |
PATCH |
/api/items/:id |
Update item |
DELETE |
/api/items/:id |
Remove item |
GET |
/api/webhooks |
List your webhooks |
POST |
/api/webhooks |
{type, url, events[], label?} |
PATCH |
/api/webhooks/:id |
Update webhook |
DELETE |
/api/webhooks/:id |
Delete webhook |
Webhook Events
| Event | Fires when |
|---|---|
item_gifted |
Someone marks an item as gifted |
item_added |
Owner adds a new item |
item_removed |
Owner removes an item |
wishlist_updated |
Wishlist title/description changed |
Discord webhooks send rich embeds. Generic webhooks send:
{ "event": "item_gifted", "payload": { ... }, "timestamp": "..." }
Item Priority
Set priority (0–10) on items. Items with priority > 5 show a ★ High Priority badge on the share page.
Notes on the Scraper
The scraper (POST /api/items/scrape) fetches the SL Marketplace page and extracts name, price, image, and store info. SL Marketplace occasionally changes its HTML structure; if scraping fails, the frontend falls back to manual entry gracefully.
Frontend Pages
| URL | Description |
|---|---|
/ |
Landing page |
/register |
Create account |
/login |
Sign in |
/dashboard |
Manage your items, webhooks, settings |
/share/:identifier |
Public wishlist view (username or UUID) |