diff --git a/NOTICES.md b/NOTICES.md new file mode 100644 index 0000000..377112f --- /dev/null +++ b/NOTICES.md @@ -0,0 +1,82 @@ +# QRknit — Third-Party Notices + +QRknit is proprietary software. It is built using open-source components, each of which retains its original copyright and licence. The notices below satisfy the attribution requirements of those licences. + +--- + +## Flask + +Copyright © 2010 Pallets +https://flask.palletsprojects.com +Licence: BSD-3-Clause + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + +--- + +## Werkzeug + +Copyright © 2007 Pallets +https://werkzeug.palletsprojects.com +Licence: BSD-3-Clause + +Same terms as Flask above. + +--- + +## qrcode + +Copyright © 2011 Lincoln Loop +https://github.com/lincolnloop/python-qrcode +Licence: MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. + +--- + +## Pillow + +Copyright © 1995–2011 Fredrik Lundh and contributors +Copyright © 2010–present Alex Clark and contributors +https://python-pillow.org +Licence: HPND (Historical Permission Notice and Disclaimer) + +Permission to use, copy, modify, and distribute this software and its documentation for any purpose and without fee is hereby granted, provided that the above copyright notice appear in all copies and that the copyright notice and this permission notice appear in supporting documentation, and that the name of the author not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. + +--- + +## Gunicorn + +Copyright © 2009 Benoit Chesneau +Copyright © 2009 Paul J. Davis +https://gunicorn.org +Licence: MIT + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. + +--- + +## Google Fonts — Orbitron, Barlow, Share Tech Mono + +Orbitron — Copyright © 2010 Matt McInerney +Barlow — Copyright © 2017 Jeremy Tribby +Share Tech Mono — Copyright © 2012 Carrois Apostrophe + +https://fonts.google.com +Licence: SIL Open Font Licence 1.1 (OFL-1.1) + +These fonts are used in the QRknit web interface and are served via the Google Fonts API. The OFL-1.1 licence allows free use, study, modification, and redistribution subject to the condition that the fonts are not sold on their own. Full licence text: https://openfontlicense.org diff --git a/README.md b/README.md index 0bc3ccf..6dd71c2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,130 @@ -# qrknit +# 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=` | +| 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. diff --git a/landing-os.html b/landing-os.html new file mode 100644 index 0000000..b22d823 --- /dev/null +++ b/landing-os.html @@ -0,0 +1,862 @@ + + + + + + +QRknit Private Instance — Self-Hosted URL Shortener & QR Generator + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+ + + + +
+ Open App → +
+
+ + + +
+
Private Instance · Pro & Team plans
+

Short links.
Sharp QR codes.
Your infrastructure.

+

+ All the power of QRknit running on your own hardware, under your full control. Your data stays on your server — no sharing, no usage caps from noisy neighbours. +

+ +
+ Single Docker container · Included with Pro & Team plans +
+
+ + + +
+
+ +
Everything in one container
+
+ +
+
+ +
+

Link Shortening

+

Custom codes, automatic expiry dates, tag organisation, bulk import and export. Search and filter your entire link library in milliseconds.

+ Custom codes · Tags · Expiry · Bulk CSV +
+ +
+
+ +
+

QR Code Generator

+

Five dot styles, full color control, logo overlay support, and downloadable PNGs — right in the browser. Generate for any URL, not just your short links.

+ 5 styles · Logo overlay · Custom colors +
+ +
+
+ +
+

Click Analytics

+

Daily bar charts, hourly 7×24 activity heatmap, geographic country breakdown, referrer and device tracking — all per link, with raw CSV export.

+ Heatmap · Geo · Referrers · CSV export +
+ +
+
+ +
+

Multi-user Support

+

Admin account management, per-user isolation so each user only sees their own links, and owner attribution so admins can quickly identify and act on abuse.

+ Admin panel · User isolation · Owner badges +
+ +
+
+ +
+

Docker Native

+

One container, one volume, one command. SQLite keeps it simple — no Postgres, no Redis, no message queue. Works on Unraid, Synology, or any Docker host.

+ Single container · SQLite · Zero infra deps +
+ +
+
+ +
+

Four Color Themes

+

Switch between Cyan, Gold, Green, and Red accent palettes. Preference is stored in localStorage and applies instantly — no page reload, no account needed.

+ Cyan · Gold · Green · Red +
+ +
+
+
+ +
+ + +
+
+
+
+ +

Know exactly who clicked, when, and from where

+

Most link shorteners give you a click count. QRknit gives you the full picture — every event captured and broken down so you can make real decisions.

+
    +
  • Daily click bar chart per link (7d · 30d · 90d windows)
  • +
  • Hourly 7×24 activity heatmap — spot your peak times at a glance
  • +
  • Geographic country breakdown via Cloudflare headers
  • +
  • Referrer, device type, and browser breakdown
  • +
  • 30-day dashboard-wide click chart across all links
  • +
  • Raw click-event CSV export — every timestamp, referrer, country
  • +
+ See it in action → +
+ + +
+
CLICK ANALYTICS — LAST 30 DAYS
+
+
HOURLY ACTIVITY
+
+
+
TOTAL CLICKS
+
PEAK DAY
+
COUNTRIES
+
+
+
+
+
+ +
+ + +
+
+ +
Your data never leaves your server
+
+
+
0
+
External infra deps
+

No Redis, no Postgres, no message queue. A single SQLite file holds everything. Back it up with one cp.

+
+
+
1
+
Docker container
+

The entire app — Python, Flask, frontend, and all assets — ships as a single image. Map one port, mount one volume, done.

+
+
+
+
Links you can create
+

Your plan limits apply, but your instance is yours alone — no shared infrastructure, no noisy neighbours, no cross-tenant data.

+
+
+
+
+ +
+ + +
+
+
+
+ +

One command to deploy

+

No CI pipeline, no cloud credentials, no YAML sprawl. Access the private repository with your plan credentials, build the image, run the container.

+
    +
  1. Clone the private repo using the credentials from your account
  2. +
  3. Set BASE_URL, SECRET_KEY, and ADMIN_PASSWORD
  4. +
  5. Point a domain at your server and enable HTTPS
  6. +
  7. Log in at /app and start shortening
  8. +
+
+
+
docker run -d \
+  --name qrknit \
+  --restart unless-stopped \
+  -p 5000:5000 \
+  -v qrknit-data:/app/data \
+  -e BASE_URL=https://yourdomain.com \
+  -e SECRET_KEY=your-generated-key \
+  -e ADMIN_PASSWORD=your-password \
+  qrknit:latest
+
+
+
+
+ +
+ + +
+
+ +
Private instance is included with Pro and Team plans.
+
+ + + +
+
Coming soon
+
Team
+
$79 / month
+

Advanced features and full API access for teams that need more control.

+
    +
  • Unlimited links & clicks
  • +
  • Everything in Pro
  • +
  • Up to 3 custom short domains
  • +
  • Private instance
  • +
  • Unlimited team members
  • +
  • Full REST API + API key auth
  • +
  • UTM parameter builder
  • +
  • Password-protected links
  • +
  • Dedicated account support
  • +
+ +
+ +
+
+
+ +
+ + +
+ +

Your links are waiting.

+

Open the app and start shortening URLs, generating QR codes, and tracking clicks — right now.

+ +
Included with Pro & Team plans · Full data sovereignty · Your domain
+
+ + + + + + + +
+
+ +
+

Get in Touch

+

Send us a message and we'll get back to you.

+
+ + +
+
+ + +
+
+ + +
+ +
+ + +
+
+
+
+

Message sent!

+

Thanks for reaching out. We'll be in touch soon.

+
+
+
+ + + + diff --git a/qk-ico.png b/qk-ico.png new file mode 100644 index 0000000..ac9b92d Binary files /dev/null and b/qk-ico.png differ diff --git a/qrknit.png b/qrknit.png new file mode 100644 index 0000000..2311264 Binary files /dev/null and b/qrknit.png differ