diff --git a/docs/spec.md b/docs/spec.md index cc36421..c325731 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -1,283 +1,394 @@ -# QR-First URL Shortener SaaS (Designer + Short Links + Tracking) +# TrakQR - QR-First URL Shortener SaaS -Note: These specs are a draft and need review. +> Create branded short links and highly customizable QR codes, then track scans/clicks with actionable analytics. -## 0) Product Definition +## Product Overview -One-liner: Create branded short links and highly customizable QR codes, then track scans/clicks with actionable analytics. - -Primary users: +**Primary users:** - Solo creators / small businesses - Marketing teams in SMB/PME - Agencies managing multiple clients -Core value: Beautiful QR designs + brandable short domains + trustworthy tracking in one place. +**Core value:** Beautiful QR designs + brandable short domains + trustworthy tracking in one place. -## 1) MVP Scope (what ships first) +--- -### 1.1 User Capabilities (MVP) +## 1. MVP Features (Implemented) -Auth & Account -- Sign up / sign in (email + password; optional SSO later) -- Email verification -- Password reset -- Basic account settings +### 1.1 Authentication & Account -Projects / Workspaces -- Default workspace per user -- Create “Projects” to organize links/QRs (e.g., “Restaurant menus”, “Flyers Q1”) +| Feature | Status | Notes | +|---------|--------|-------| +| Email + password signup | ✅ | With auto-created default workspace | +| Email verification | ✅ | Token-based, resend support | +| Login with JWT | ✅ | Rate-limited | +| Password reset | ✅ | Email-based token flow | +| Profile management | ✅ | Update email, change password | +| Delete account | ✅ | With password confirmation | +| SSO | ⏳ | Deferred to post-MVP | -Short Link Creation -- Create short link: https://d.om/abc123 or custom slug …/menu -- Destination URL validation -- Optional UTM builder (preset templates) -- Enable/disable link -- Expiration date (optional) -- Password protection (optional) — may be “Pro” if you want +### 1.2 Workspaces & Projects -QR Code Designer -- Generate QR from a short link (default) or direct URL -- Styling: - - Colors (foreground/background) - - Error correction level (L/M/Q/H) - - Quiet zone padding - - Shape presets (modules/eyes) (start with a few presets) - - Center logo upload (PNG/SVG) with size + margin controls -- Export: - - PNG and SVG - - Size presets (e.g., 256/512/1024/2048) - - Print-ready options (e.g., “high contrast” toggle) +| Feature | Status | Notes | +|---------|--------|-------| +| Default workspace on signup | ✅ | Auto-created | +| Multiple workspaces | ✅ | Based on plan limits | +| Workspace CRUD | ✅ | Create, update, delete | +| Projects for organization | ✅ | Full CRUD with descriptions | +| Workspace switcher UI | ✅ | With create/manage modals | -Tracking & Analytics (MVP) -- Track events: click (short link) and scan (QR) -- Dashboard: - - Total events, uniques, last 24h / 7d / 30d - - Time series - - Top referrers (for clicks) - - Geo (country) and device (desktop/mobile) high-level - - Per-link analytics and per-QR analytics +### 1.3 Short Link Management -Basic Admin -- Subscription status -- Usage quotas (links/QRs/events) +| Feature | Status | Notes | +|---------|--------|-------| +| Create short link | ✅ | Custom or auto-generated slug | +| Destination URL validation | ✅ | URL format validation | +| UTM builder | ✅ | Presets for Google, Facebook, Email, Social | +| Enable/disable link | ✅ | Status field (Active/Disabled) | +| Expiration date | ✅ | Optional datetime | +| Password protection | ✅ | Optional, with POST endpoint for auth | +| Soft delete + restore | ✅ | Trash view with restore | +| Bulk import | ✅ | CSV-style paste with titles | +| URL allowlist/denylist | ⏳ | Deferred - abuse prevention | -## 2) Non-Goals for MVP (explicitly out) +### 1.4 QR Code Designer -- Team roles/permissions (RBAC) beyond “owner” -- A/B routing, smart rules, rotation, geo routing -- Deep campaign automation -- Enterprise SSO, SCIM -- Offline QR scan tracking (impossible without network in most cases) +| Feature | Status | Notes | +|---------|--------|-------| +| Generate from short link | ✅ | Required link association | +| Foreground/background colors | ✅ | Hex color pickers | +| Error correction levels | ✅ | L/M/Q/H options | +| Quiet zone padding | ✅ | Configurable | +| Module shapes | ✅ | Square, Rounded, Dots | +| Eye shapes | ✅ | Square, Rounded, Circle | +| Style presets | ✅ | 6 built-in presets | +| Logo upload | ✅ | PNG/JPG, select from assets or upload new | +| Logo size controls | ⚠️ | Fixed 20% - user controls deferred | +| Live preview | ✅ | Real-time updates | +| Export PNG | ✅ | Configurable size (256-2048px) | +| Export SVG | ✅ | Vector output | +| Print-ready options | ⏳ | High contrast toggle deferred | +| Scan attribution | ✅ | Exports include `?qr={id}` param | -## 3) Plans & Monetization (recommended) +### 1.5 Tracking & Analytics -Free -- 1 workspace -- 25 short links -- 25 QR designs -- 10k events/month -- 1 custom QR logo upload (or allow unlimited but watermark exports) +| Feature | Status | Notes | +|---------|--------|-------| +| Click events | ✅ | From redirect endpoint | +| Scan events | ✅ | When `?qr=` param present | +| Async event logging | ✅ | Non-blocking, fire-and-forget | +| IP hashing | ✅ | SHA256 with daily salt | +| Dedupe (30-min window) | ✅ | Prevents duplicate counts | +| User agent parsing | ✅ | Device type detection | +| GeoIP lookup | ✅ | MaxMind GeoIP2 integration | +| Workspace analytics | ✅ | Totals, time series, breakdowns | +| Per-link analytics | ✅ | Individual link stats | +| Per-QR analytics | ✅ | Individual QR stats | +| Time filters | ✅ | 24h, 7d, 30d | +| Custom date range | ⚠️ | Backend ready, frontend UI needed | +| Referrer breakdown | ✅ | Top referrers list | +| Device breakdown | ✅ | Desktop/Mobile/Tablet | +| Country breakdown | ✅ | With flags and names | +| Monthly IP salt rotation | ⏳ | Deferred - privacy enhancement | +| Event retention config | ⏳ | Deferred - per-plan cleanup | -Pro (individual/SMB) -- Custom domains (1–3) -- Higher limits -- No watermark -- UTM templates -- Expiring links / password links (if Pro) +### 1.6 Domain Management -Business -- Multiple workspaces -- Team seats (later) -- Higher retention and export presets +| Feature | Status | Notes | +|---------|--------|-------| +| Add custom domain | ✅ | Pro/Business plans | +| DNS TXT verification | ✅ | Token-based | +| CNAME setup instructions | ✅ | UI guide | +| Domain status tracking | ✅ | Pending → Verified | +| Delete domain | ✅ | With warning | -## 4) Core Entities (Data Model) +### 1.7 Asset Management -### 4.1 Entities +| Feature | Status | Notes | +|---------|--------|-------| +| Upload assets | ✅ | For QR logos | +| List workspace assets | ✅ | Gallery view | +| Delete assets | ✅ | With cleanup | +| Public asset URL | ✅ | For rendering | +### 1.8 Plans & Billing + +| Feature | Status | Notes | +|---------|--------|-------| +| Plan tiers (Free/Pro/Business) | ✅ | Configured limits | +| Usage tracking | ✅ | Links, QRs, domains, events | +| Plan limits enforcement | ✅ | In create endpoints | +| Stripe checkout | ✅ | Session-based | +| Stripe customer portal | ✅ | Manage subscription | +| Webhook handling | ✅ | Subscription events | +| Billing UI | ✅ | Plan comparison, upgrade flow | + +### 1.9 API Keys + +| Feature | Status | Notes | +|---------|--------|-------| +| Create API key | ✅ | With name and expiry | +| List API keys | ✅ | Shows prefix, last used | +| Delete API key | ✅ | Revoke access | +| API key authentication | ⏳ | Middleware needed | + +--- + +## 2. Plan Limits + +| Feature | Free | Pro | Business | +|---------|------|-----|----------| +| Workspaces | 1 | 5 | Unlimited | +| Links per workspace | 50 | 5,000 | Unlimited | +| QR codes per workspace | 25 | 1,000 | Unlimited | +| Custom domains | 0 | 3 | Unlimited | +| Events per month | 10,000 | 100,000 | Unlimited | +| Custom domains feature | ❌ | ✅ | ✅ | +| Password protection | ❌ | ✅ | ✅ | +| Analytics | ✅ | ✅ | ✅ | + +--- + +## 3. Data Model + +### Core Entities + +``` User -- id, email, password_hash, verified_at, created_at +├── id, email, password_hash +├── is_email_verified, created_at +└── Relations: Workspaces, EmailVerificationTokens, PasswordResetTokens Workspace -- id, owner_user_id, name, plan, created_at +├── id, owner_user_id, name, plan +├── created_at +└── Relations: Projects, ShortLinks, QRCodeDesigns, Domains, Assets, Events, ApiKeys Project -- id, workspace_id, name, created_at +├── id, workspace_id, name, description +└── created_at Domain -- id, workspace_id, hostname, status (pending/verified/active), verification_token, created_at +├── id, workspace_id, hostname +├── status (Pending/Verified), verification_token +└── created_at ShortLink -- id -- workspace_id, project_id (nullable) -- domain_id (nullable; else default platform domain) -- slug -- destination_url -- title (nullable) -- status (active/disabled) -- expires_at (nullable) -- password_hash (nullable) -- created_at, updated_at +├── id, workspace_id, project_id (nullable), domain_id (nullable) +├── slug, destination_url, title +├── status (Active/Disabled), expires_at, password_hash +├── click_count, is_deleted, deleted_at +└── created_at, updated_at QRCodeDesign -- id -- workspace_id, project_id (nullable) -- shortlink_id (nullable; recommended default) -- style_json (colors, shapes, ecc level, etc.) -- logo_asset_id (nullable) -- created_at, updated_at +├── id, workspace_id, project_id (nullable), link_id +├── name, style_json, logo_asset_id (nullable) +└── created_at, updated_at Event -- id (or bigint) -- workspace_id -- shortlink_id -- qrcode_id (nullable but strongly recommended to tag scans) -- type: click | scan -- ts -- ip_hash (privacy-safe) -- user_agent -- referrer -- country_code (nullable) -- device_type (nullable) -- dedupe_key (nullable) -- raw_json (optional for debug, or drop) +├── id, workspace_id, link_id, qr_code_id (nullable) +├── type (Click/Scan), timestamp +├── ip_hash, user_agent, referrer +├── country_code, device_type, dedupe_key +└── (partitioned by month for scale) Asset -- id, workspace_id, type (logo), storage_key, mime, size, created_at +├── id, workspace_id, filename, storage_key +├── content_type, size_bytes +└── created_at -### 4.2 Key Design Choice +ApiKey +├── id, workspace_id, name, key_hash, key_prefix +├── scopes, expires_at, last_used_at, is_active +└── created_at -How do we distinguish “scan” vs “click”? +EmailVerificationToken +├── id, user_id, token, expires_at, used_at +└── created_at -When exporting a QR, embed a URL like: -https://d.om/s/abc123?qr= +PasswordResetToken +├── id, user_id, token, expires_at, used_at +└── created_at +``` -The redirect endpoint records scan when it detects qr=, then redirects to destination, which will also produce a click unless you decide “scan implies click” and record only one. +--- -Recommendation: record one event per redirect request: -- If qr present → type=scan -- Else → type=click +## 4. System Architecture -## 5) System Behavior (Routes & Flows) +### Components -### 5.1 Public Redirect +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Vue 3 SPA │────▶│ ASP.NET Core │────▶│ PostgreSQL │ +│ + Pinia │ │ FastEndpoints │ │ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ External APIs │ + │ - Stripe │ + │ - SMTP │ + │ - MaxMind │ + └─────────────────┘ +``` -GET /{slug} +### Key Design Decisions -Resolve domain + slug → short link +1. **Vertical Slice Architecture**: Features organized in `Features/{Feature}/` folders +2. **FastEndpoints**: Lightweight alternative to MVC controllers +3. **Pinia State Management**: Centralized frontend state with persistence +4. **Non-blocking Event Logging**: Fire-and-forget to not slow redirects +5. **Soft Delete**: Links can be restored from trash +6. **Scan Attribution**: QR exports embed `?qr={id}` for tracking -Validate: -- exists -- active -- not expired -- if password-protected → show password page +### Public Redirect Flow -Log event (scan/click) +``` +GET /{slug}?qr={id} + │ + ▼ +┌─────────────────────────────────────┐ +│ 1. Resolve domain + slug → link │ +│ 2. Validate: exists, active, !expired│ +│ 3. If password → return 401 │ +│ 4. Log event async (scan if ?qr=) │ +│ 5. Return 302 redirect │ +└─────────────────────────────────────┘ +``` -Redirect 301/302 (configurable later; MVP use 302) +--- -### 5.2 QR Export +## 5. API Endpoints -QR code is generated from: +### Authentication +- `POST /auth/register` - Create account +- `POST /auth/login` - Get JWT token +- `POST /auth/forgot` - Request password reset +- `POST /auth/reset` - Reset password with token +- `POST /auth/verify-email` - Verify email +- `POST /auth/resend-verification` - Resend verification email +- `GET /auth/profile` - Get current user +- `PUT /auth/profile` - Update profile +- `POST /auth/change-password` - Change password +- `DELETE /auth/account` - Delete account -Redirect URL including qrcode id: https://{domain}/{slug}?qr={qrcode_id} +### Workspaces & Projects +- `GET/POST /workspaces` - List/Create workspaces +- `GET/PUT/DELETE /workspaces/{id}` - Workspace operations +- `GET/POST /workspaces/{id}/projects` - List/Create projects +- `GET/PUT/DELETE /workspaces/{id}/projects/{pid}` - Project operations -## 6) Functional Requirements (MVP checklist) +### Links +- `GET/POST /workspaces/{id}/links` - List/Create links +- `POST /workspaces/{id}/links/bulk` - Bulk create +- `GET/PUT/DELETE /workspaces/{id}/links/{lid}` - Link operations +- `POST /workspaces/{id}/links/{lid}/restore` - Restore deleted +- `GET /workspaces/{id}/links/{lid}/analytics` - Link analytics -Link Management -- Create, edit, disable, delete (soft delete preferred) -- Slug uniqueness per domain -- Auto-slug generator (base62) -- Destination URL allowlist/denylist (prevent abuse) +### QR Codes +- `GET/POST /workspaces/{id}/qrcodes` - List/Create QR codes +- `GET/PUT/DELETE /workspaces/{id}/qrcodes/{qid}` - QR operations +- `GET /workspaces/{id}/qrcodes/{qid}/preview` - Get preview (data URL) +- `GET /workspaces/{id}/qrcodes/{qid}/export` - Export PNG/SVG +- `GET /workspaces/{id}/qrcodes/{qid}/analytics` - QR analytics -QR Designer -- Live preview -- Save design -- Export SVG/PNG -- Logo upload with validation (size/mime) +### Domains & Assets +- `GET/POST /workspaces/{id}/domains` - List/Add domains +- `DELETE /workspaces/{id}/domains/{did}` - Delete domain +- `POST /workspaces/{id}/domains/{did}/verify` - Verify domain +- `GET/POST /workspaces/{id}/assets` - List/Upload assets +- `DELETE /workspaces/{id}/assets/{aid}` - Delete asset +- `GET /assets/{storageKey}` - Public asset URL -Analytics -- Views for: - - Workspace overview - - Project overview - - Per short link - - Per QR design -- Time filters: 24h / 7d / 30d / custom range -- Unique definition: - - Unique per day per link based on ip_hash + UA hash (privacy-safe and approximate) +### Analytics & Usage +- `GET /workspaces/{id}/analytics` - Workspace analytics +- `GET /usage` - Usage stats and limits -## 7) Non-Functional Requirements +### Billing +- `POST /billing/checkout` - Create Stripe checkout +- `POST /billing/portal` - Create Stripe portal session +- `GET /workspaces/{id}/subscription` - Get subscription +- `POST /billing/webhook` - Stripe webhooks -Performance -- Redirect endpoint P95 < 100ms (excluding DNS/TLS) -- Event write must not block redirect (use async queue if possible) +### API Keys +- `GET/POST /workspaces/{id}/api-keys` - List/Create keys +- `DELETE /workspaces/{id}/api-keys/{kid}` - Delete key -Availability -- Redirect is the critical path; should stay up even if dashboard is down -- Graceful degradation: if analytics store is down, still redirect +### Public +- `GET /{slug}` - Redirect to destination +- `POST /{slug}` - Redirect with password -Security -- Rate limit public endpoints -- Abuse prevention: phishing/malware reporting flow (later), basic filters now -- Domain verification to prevent takeover -- Strict CSP on app pages +--- -Privacy & Compliance (Canada / Quebec friendly baseline) -- Avoid storing raw IP; store hashed IP with rotating salt (e.g., monthly) -- Provide retention configuration per plan (e.g., 30/180/365 days) +## 6. UI Pages -## 8) Architecture (pragmatic MVP) +| Page | Route | Status | +|------|-------|--------| +| Landing | `/` | ✅ | +| Login | `/login` | ✅ | +| Register | `/register` | ✅ | +| Forgot Password | `/forgot-password` | ✅ | +| Reset Password | `/reset-password` | ✅ | +| Verify Email | `/verify-email` | ✅ | +| Dashboard | `/dashboard` | ✅ | +| Links List | `/links` | ✅ | +| Link Detail | `/links/:id` | ✅ | +| QR Codes List | `/qrcodes` | ✅ | +| QR Designer | `/qrcodes/new`, `/qrcodes/:id` | ✅ | +| QR Analytics | `/qrcodes/:id/analytics` | ✅ | +| Analytics | `/analytics` | ✅ | +| Projects | `/projects` | ✅ | +| Domains | `/domains` | ✅ | +| Settings | `/settings` | ✅ | +| Billing | `/billing` | ✅ | -Components -- Web App: dashboard + designer (Vue/React) -- API: CRUD for links/qr/projects/domains, analytics queries -- Redirect Edge: fastest path for /{slug} (can be same API initially) +--- -Storage -- PostgreSQL for core entities -- Analytics: - - MVP: PostgreSQL events table partitioned by month - - Later: ClickHouse/BigQuery for scale +## 7. Security & Performance -Background Jobs -- Domain verification checks -- Event enrichment (geo/device parsing) -- Cleanup & retention tasks +### Implemented +- [x] JWT authentication with expiry +- [x] Rate limiting on auth endpoints (10 req/min) +- [x] Rate limiting on redirect endpoint (100 req/min) +- [x] Password hashing with BCrypt +- [x] IP hashing for privacy +- [x] CORS configuration +- [x] Global exception handling +- [x] Input validation with FluentValidation +- [x] Ownership verification on all endpoints -## 9) API Surface (minimal) +### Deferred +- [ ] Strict CSP headers +- [ ] Monthly rotating IP salt +- [ ] URL allowlist/denylist +- [ ] Abuse reporting flow -- POST /auth/register|login|forgot|reset -- GET/POST /workspaces -- GET/POST /projects -- GET/POST /links -- GET/POST /qrcodes -- POST /domains + verification status -- GET /analytics/overview -- GET /analytics/link/{id} -- GET /analytics/qrcode/{id} +--- -## 10) UI Pages (MVP) +## 8. Remaining Work -- Login / Register / Reset -- Workspace switcher -- Projects list -- Links list + create/edit -- QR designer (create/edit) with preview -- Analytics dashboard (overview + per link + per QR) -- Domains page (add/verify) +### High Priority +1. **API Key Authentication Middleware** - Enable programmatic access +2. **Bulk Create Plan Limits** - Check limits in bulk endpoint +3. **Custom Date Range UI** - Date picker for analytics -## 11) Pricing/Quotas Enforcement +### Medium Priority +4. **Background Jobs** - Domain verification polling, event cleanup +5. **Logo Size Controls** - User-adjustable logo size/margin +6. **Additional Tests** - Auth, billing, API key endpoints -Enforce at API level: -- max links, max QR codes, max events/month, max custom domains +### Lower Priority +7. **Print-Ready QR** - High contrast mode +8. **Analytics Export** - CSV/JSON download +9. **Strict CSP** - Security headers +10. **IP Salt Rotation** - Monthly rotation for privacy -Stripe integration later; MVP can be “manual Pro” toggle or Stripe from day 1 if you want. +--- -## 12) Implementation Notes (key decisions) +## 9. Non-Goals (Post-MVP) -- Use one redirect URL as canonical; QR adds ?qr= for attribution. -- Event logging should be non-blocking: - - MVP: write to DB async (background queue) or “fire-and-forget” with retry -- Plan for domain verification: - - Require DNS TXT record or CNAME to verify ownership -- Short link collision: - - Slug uniqueness per domain enforced in DB +- Team roles/permissions (RBAC) +- A/B routing, smart rules, geo routing +- Deep campaign automation +- Enterprise SSO, SCIM +- Offline QR scan tracking