# QRknit — URL Shortener & QR Code Generator

A managed URL shortener with QR code generation, deep click analytics, and tag-based link organization.

- **Multi-user** — admin account seeded from env vars; admin can create/delete/manage accounts via the UI
- **Per-user isolation** — each user sees only their own links and tags; admin has its own isolated workspace separate from all other accounts
- **Organizations** — group users into orgs that share link/tag visibility and pool usage quotas under a shared plan
- **Plan tiers** — Admin / Free / Starter / Pro / Team with enforced limits on active links, monthly clicks, and analytics history window; org members inherit their org's plan
- **Scoped tags** — tags are owned by the user who creates them; org members share a tag namespace, solo users and admins each have their own isolated namespace
- **Deep analytics** — daily charts, referrer/device/browser/country breakdowns, hourly 7×24 heatmap, raw click CSV export
- **Private instance** — Pro and Team plans include a dedicated private deployment on your own infrastructure

---

## 🗺 Feature Map

### Shipped

| Feature area | What's included |
|---|---|
| **Short links** | Random & custom codes, link expiry, pinned links, one-click copy, auto-fetch page title |
| **QR codes** | QR generation for any link or URL, logo overlay, dot-shape presets, inline thumbnail, copy to clipboard |
| **Analytics** | Per-link daily click charts, referrer / device / browser / country breakdowns, hourly 7×24 heatmap, raw click CSV export |
| **Tags** | Tag links for filtering; tags are scoped per user — org members share a namespace, solo users and admins each have their own |
| **Bulk tools** | Bulk delete, bulk tag, bulk expire; CSV import & export |
| **Auth** | Session-cookie login, 30-day HttpOnly cookie, works behind Cloudflare and Nginx Proxy Manager |
| **Multi-user** | Per-user link isolation, admin user-management panel, username + password login |
| **Organizations** | Group users into orgs that share link/tag visibility and pool usage quotas under a shared plan |
| **Plan tiers** | Free / Starter / Pro / Team plans with enforced limits on active links, monthly clicks, and analytics history; org plan overrides member plans |
| **Admin tier** | Admin accounts sit above all plans with unlimited quotas and an isolated workspace; can filter into any user's links on demand |
| **Landing & portal** | Marketing landing page, SaaS pricing page, contact form, admin inbox with unread badge |
| **Deployment** | `APP_NAME` / `BASE_URL` env vars, four colour themes, single Docker container, Unraid-ready |

### Upcoming

| Feature area | What's planned |
|---|---|
| **Billing** | Stripe checkout & subscriptions, upgrade/downgrade UI, self-serve account portal |
| **Custom domains** | CNAME-based short domains, SSL provisioning, domain verification, Team tier multi-domain support |
| **Power features** | API key auth, password-protected links, UTM parameter builder, custom 404 & expired-link pages |
| **Link organisation** | Folders/groups, duplicate link, dead-link detection, per-link redirect type (301 vs 302) |

---

> **Installing?** See [INSTALL.md](INSTALL.md) for Docker, Docker Compose, and Unraid setup instructions, environment variables, and update steps.

---

## 🔌 API Reference

All write endpoints require an active session (log in via the web UI or `POST /api/auth/login`).

### Auth

| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | `/api/auth/login` | — | Login — `{"username": "...", "password": "..."}`, sets session cookie; returns `plan`, `plan_limits`, `org_id` |
| POST | `/api/auth/logout` | ✓ | Clear session |
| GET | `/api/auth/me` | ✓ | Returns `{"authenticated": true, "username": "...", "is_admin": bool, "org_id": int\|null, "plan": "...", "plan_limits": {…}}` |

### Links

| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | `/api/shorten` | ✓ | Create a short link |
| GET | `/api/links` | ✓ | List links — supports `?q=`, `?tag=`, `?page=`, `?per_page=`; org members see all links within their org; admin sees own links only, optionally filtered with `?user=<username>` |
| GET | `/api/links/:code` | ✓ | Link detail — includes `created_by` username |
| PATCH | `/api/links/:code` | ✓ | Edit link — `url`, `title`, `expires_at`, `tags`, `is_pinned` |
| DELETE | `/api/links/:code` | ✓ | Delete link |
| GET | `/api/links/:code/analytics` | ✓ | Click analytics — supports `?days=7\|30\|90` (capped to plan's max window); returns `daily`, `referrers`, `devices`, `browsers`, `countries`, `heatmap` (7×24 array), and `max_days` |
| GET | `/api/links/:code/clicks/export` | ✓ | Download raw click events as CSV — columns: `timestamp`, `referrer`, `device`, `browser`, `country` |

### Utilities

| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | `/api/stats` | ✓ | Total links, total clicks, clicks/7d, top 5 links, and a 30-day `daily` click array for the dashboard chart |
| GET | `/api/plan` | ✓ | Current user's effective plan, limits, and live usage — `{plan, limits: {max_links, monthly_clicks, analytics_days}, usage: {active_links, monthly_clicks}}`; org members see pooled usage across all org members; admin plan is always `"admin"` (unlimited) |
| GET | `/api/tags` | ✓ | Tags scoped to the requesting user — own tags only for solo users and admins; all org member tags for org members |
| GET | `/api/fetch-title` | ✓ | Server-side page title fetch — `?url=`. Returns `{"title":"…"}` |
| GET | `/api/qr/:code` | — | QR PNG for a short link |
| GET | `/api/qr/custom` | — | QR PNG for any URL — `?url=`, `?fg=`, `?bg=`, `?size=`, `?style=` |
| POST | `/api/qr/custom` | — | QR PNG with logo overlay — `{url, fg, bg, size, style, logo}` (logo as base64) |
| POST | `/api/links/bulk` | ✓ | Bulk operations — `{action: "delete"\|"tag"\|"expire", codes: […]}` |
| GET | `/api/links/export` | ✓ | Download all links as CSV |
| POST | `/api/links/import` | ✓ | Import links from CSV — `{csv: "…"}` |
| GET | `/api/health` | — | Health check — `{"status":"ok"}` |
| GET | `/:code` | — | Redirect to destination URL |

### Admin

| Method | Endpoint | Auth | Description |
|---|---|---|---|
| GET | `/api/admin/users` | Admin | List all users with link counts, plan, org name, and org plan |
| POST | `/api/admin/users` | Admin | Create user — `{username, password, is_admin, plan}` |
| PATCH | `/api/admin/users/:id` | Admin | Update plan, admin status, and/or org — `{plan?, is_admin?, org_id?}` (set `org_id: null` to remove from org) |
| DELETE | `/api/admin/users/:id` | Admin | Delete user (cannot delete self) |
| PATCH | `/api/admin/users/:id/password` | Admin | Change user password — `{password}` |
| GET | `/api/admin/organizations` | Admin | List all organizations with member counts and plan |
| POST | `/api/admin/organizations` | Admin | Create organization — `{name, plan}` |
| PATCH | `/api/admin/organizations/:id` | Admin | Update organization name and/or plan — `{name?, plan?}` |
| DELETE | `/api/admin/organizations/:id` | Admin | Delete organization — members are unassigned but not deleted |
| GET | `/api/admin/messages` | Admin | List all contact/portal messages, newest first |
| DELETE | `/api/admin/messages/:id` | Admin | Delete a message |
| PATCH | `/api/admin/messages/:id/read` | Admin | Mark a message as read |

### Contact

| Method | Endpoint | Auth | Description |
|---|---|---|---|
| POST | `/api/contact` | — | Submit a contact message — `{name, email, subject, body}` |

---

## Third-party attributions

QRknit is built on open-source components. See [NOTICES.md](NOTICES.md) for full licence texts.

| Library | Licence | Used for |
|---|---|---|
| [Flask](https://flask.palletsprojects.com) | BSD-3-Clause | Web framework & routing |
| [Werkzeug](https://werkzeug.palletsprojects.com) | BSD-3-Clause | Password hashing, WSGI utilities |
| [qrcode](https://github.com/lincolnloop/python-qrcode) | MIT | QR code generation |
| [Pillow](https://python-pillow.org) | HPND | Image processing & QR rendering |
| [Gunicorn](https://gunicorn.org) | MIT | Production WSGI server |
| [Google Fonts](https://fonts.google.com) — Orbitron, Barlow, Share Tech Mono | OFL-1.1 | UI typography |

---

© 2025 QRknit. All rights reserved.
