managarten/apps/uload/CLAUDE.md
Till JS 0c7a080cf8 feat(uload): Docker setup, CLAUDE.md rewrite, bulk actions, link expiry & passwords
Docker:
- Dockerfile for web (sveltekit-base, port 5029) and server (Bun, port 3041)
- docker-compose.macmini.yml entries for uload-server + uload-web
- Landing page deploy script (Cloudflare Pages)

Documentation:
- Complete CLAUDE.md rewrite reflecting local-first + Hono architecture

Features:
- Bulk select/deselect all/toggle active/delete
- Link expiry date (datetime picker)
- Password-protected links
- Max clicks limit
- Badges for password/expiry/maxClicks on link items
- Advanced options collapsible section in create & edit forms

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 15:14:45 +02:00

3.9 KiB

uLoad — URL Shortener & Link Management

Live: https://ulo.ad

Architecture

uLoad uses a local-first architecture with a lightweight Hono/Bun server for redirects and analytics.

Browser → IndexedDB (Links, Tags, Folders)
              ↕ sync
         mana-sync → PostgreSQL

Browser → /r/:code → Hono Server → PostgreSQL (redirect + click tracking)

Project Structure

apps/uload/
├── apps/
│   ├── web/              # SvelteKit web app (local-first)
│   ├── server/           # Hono/Bun redirect & analytics server
│   └── landing/          # Astro marketing page
├── packages/
│   └── uload-database/   # Shared Drizzle schema
└── package.json

Tech Stack

Layer Technology
Web SvelteKit 2, Svelte 5 (runes), Tailwind CSS 4
Server Hono + Bun
Data Local-first (Dexie.js + mana-sync)
Database PostgreSQL via Drizzle ORM
Auth mana-core-auth (Better Auth + EdDSA JWT)
Landing Astro 5
PWA @vite-pwa/sveltekit
i18n svelte-i18n (DE/EN)

Commands

# Development
pnpm dev:uload:web          # SvelteKit dev server
pnpm dev:uload:server       # Hono/Bun server (port 3070)
pnpm dev:uload:landing      # Landing page
pnpm dev:uload:local        # Web + Sync + Server (no auth)
pnpm dev:uload:full         # Everything incl. auth

# Build & Deploy
pnpm --filter @uload/web build
pnpm --filter @uload/landing build
pnpm deploy:landing:uload   # Deploy landing to Cloudflare Pages

# Type Check
pnpm --filter @uload/web check
pnpm --filter @uload/server type-check
pnpm --filter @manacore/uload-database type-check

Ports

Service Dev Port Prod Port
Web 5173 5029
Server 3070 3041
Landing 4321 Cloudflare Pages

Hono Server Routes

Route Auth Description
GET /health No Health check
GET /r/:code No Redirect + click tracking
GET /public/u/:username No Public user profile + links
GET /api/v1/analytics/:linkId JWT Click stats
GET /api/v1/analytics/:linkId/timeline JWT Clicks over time
GET /api/v1/analytics/:linkId/devices JWT Device breakdown
GET /api/v1/analytics/:linkId/referrers JWT Top referrers
GET /api/v1/analytics/:linkId/countries JWT Country breakdown
POST /api/v1/stripe/checkout JWT Stripe session (stub)
POST /api/v1/stripe/webhook No Stripe webhook (stub)
POST /api/v1/email/send-invitation JWT Team invite (stub)

Local-First Collections

Collection Fields
links shortCode, originalUrl, title, isActive, clickCount, utmSource/Medium/Campaign, folderId
tags name, slug, color, icon, isPublic, usageCount
folders name, color, order
linkTags linkId, tagId

Web App Pages

Route Description
/my/links Link management (CRUD, QR, UTM, bulk)
/my/tags Tag management
/my/analytics/[id] Per-link analytics dashboard
/settings Account & data settings
/pricing Subscription plans (static)
/u/[username] Public user profile
/login Login (shared-auth-ui)
/register Register (shared-auth-ui)

Docker

# Build
./scripts/mac-mini/build-app.sh uload-web
./scripts/mac-mini/build-app.sh uload-server

# Services in docker-compose.macmini.yml:
# - uload-server (port 3041, Bun)
# - uload-web (port 5029, Node)

Key Patterns

  • Svelte 5 Runes: Use $state, $derived, $effect — never $:
  • Local-first: All CRUD via linkCollection.insert/update/delete (IndexedDB)
  • Analytics: Fetched from Hono server, not local (server-only click data)
  • Auth: authStore from @manacore/shared-auth-stores, AuthGate with guest mode
  • Sync: Starts on login via uloadStore.startSync(), stops on logout