https://github.com/dend/slacktide
https://github.com/dend/slacktide
Last synced: about 2 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/dend/slacktide
- Owner: dend
- License: mit
- Created: 2026-03-19T01:45:39.000Z (3 months ago)
- Default Branch: main
- Last Pushed: 2026-03-19T06:53:13.000Z (3 months ago)
- Last Synced: 2026-03-19T18:54:40.325Z (3 months ago)
- Language: TypeScript
- Size: 318 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
Slacktide
> [!NOTE]
> Want to see it in action first? No backend required - the admin panel ships with a full mock mode:
> ```bash
> npm install
> npm run dev:mock -w packages/admin
> ```
> Then open [http://localhost:5173](http://localhost:5173) to explore the UI with dummy data.
The Slacktide admin dashboard
A self-hosted URL shortener on Cloudflare Workers with an admin dashboard, click analytics, and API key support.
## Table of Contents
- [Architecture](#architecture)
- [Features](#features)
- [Prerequisites](#prerequisites)
- [Setup](#setup)
- [1. Clone and install](#1-clone-and-install)
- [2. Create a GitHub OAuth App](#2-create-a-github-oauth-app)
- [3. Provision infrastructure](#3-provision-infrastructure)
- [4. Run migrations](#4-run-migrations)
- [5. Set secrets](#5-set-secrets-manual-setup-only)
- [6. Seed the first owner](#6-seed-the-first-owner)
- [7. Deploy](#7-deploy)
- [8. Register the OAuth client](#8-register-the-oauth-client)
- [Local Development](#local-development)
- [Worker](#worker)
- [Admin](#admin)
- [Mock mode](#mock-mode-no-backend-needed)
- [OAuth client registration](#oauth-client-registration)
- [API Reference](#api-reference)
- [Authentication](#authentication)
- [Links](#links)
- [Analytics](#analytics)
- [Users](#users)
- [API Keys](#api-keys)
- [Auth](#auth)
- [Public](#public)
- [Admin Dashboard](#admin-dashboard)
- [Environment Variables](#environment-variables)
- [Contributing](#contributing)
- [License](#license)
## Architecture
- **Worker** - Cloudflare Worker (Hono) serving as an OAuth 2.1 resource server and redirect engine
- **D1** - SQLite database for links, clicks, and users
- **KV** - Two namespaces: `LINKS_KV` for redirect caching, `OAUTH_KV` for token storage
- **Admin** - SvelteKit static SPA (Cloudflare Pages) with Chart.js analytics
```mermaid
sequenceDiagram
box Admin SPA (OAuth 2.1 PKCE)
participant Admin
end
box Cloudflare Worker
participant Worker as OAuthProvider
participant API as apiHandler (/api/*)
end
participant GitHub
note over Admin,GitHub: Browser authentication flow
Admin->>Worker: GET /authorize
Worker->>GitHub: Redirect to GitHub OAuth
GitHub-->>Worker: GET /auth/github/callback?code=...
Worker-->>Admin: Redirect with authorization code
Admin->>Worker: POST /oauth/token (code + PKCE verifier)
Worker-->>Admin: Access token + refresh token
Admin->>API: GET /api/* (Bearer )
API-->>Admin: JSON response
note over API: API key authentication flow
participant Client as API Client
Client->>Worker: GET /api/* (Bearer stk_...)
Worker->>Worker: resolveExternalToken: D1 lookup + hash verify
Worker->>API: Forwards with UserProps
API-->>Client: JSON response
note over Worker: Public routes (no auth)
participant Visitor
Visitor->>Worker: GET /:slug
Worker-->>Visitor: 302 redirect
```
## Features
- **Short links** - auto-generated base62 slugs or custom vanity slugs with optional expiration
- **Tags** - organize links with tags, filter by multiple tags in the dashboard
- **Analytics** - per-link click tracking with breakdowns for referrers, countries, devices, browsers, and operating systems
- **OAuth 2.1 API** - Bearer token auth via `@cloudflare/workers-oauth-provider`
- **API keys** - `stk_` prefixed keys for programmatic access (CI/CD, scripts, integrations), managed from the Settings page
- **GitHub authentication** - login with GitHub, no separate account needed
- **RBAC** - `owner` (manage users, links, and API keys) and `admin` (manage own links/analytics/keys) roles
- **User management** - owners add/remove authorized users from the admin panel
- **KV caching** - sub-millisecond redirects with 24h KV TTL
## Prerequisites
- Node.js 20+
- Cloudflare account with Workers, D1, KV, and Pages access
- GitHub OAuth App (one for production, one for local dev)
## Setup
### 1. Clone and install
```bash
git clone
cd slacktide
npm install
cp wrangler.toml.example wrangler.toml # then edit with your values
```
### 2. Create a GitHub OAuth App
Go to **GitHub → Settings → Developer settings → OAuth Apps → New OAuth App**:
| Field | Value |
|---|---|
| Application name | `Slacktide` |
| Homepage URL | `https://your-domain.example.com` |
| Authorization callback URL | `https://your-domain.example.com/auth/github/callback` |
Note the **Client ID** and generate a **Client Secret**.
### 3. Provision infrastructure
#### Option A: Terraform (recommended)
```bash
cd terraform
cp terraform.tfvars.example terraform.tfvars
# Edit terraform.tfvars with your values
terraform init
terraform apply
```
Terraform provisions everything (D1, KV, Worker, Pages). After apply, grab the resource IDs from the `wrangler_toml_snippet` output and paste them into `wrangler.toml`.
#### Option B: Manual (wrangler CLI)
```bash
# D1 database
wrangler d1 create slacktide-links
# KV namespaces
wrangler kv namespace create LINKS_KV
wrangler kv namespace create OAUTH_KV
```
Then copy the example config and fill in your resource IDs:
```bash
cp wrangler.toml.example wrangler.toml
# Edit wrangler.toml with your D1 database ID, KV namespace IDs,
# GITHUB_CLIENT_ID, and ADMIN_ORIGIN
```
### 4. Run migrations
```bash
wrangler d1 execute slacktide-links --remote --file=migrations/0001_create_links.sql
wrangler d1 execute slacktide-links --remote --file=migrations/0002_create_clicks.sql
wrangler d1 execute slacktide-links --remote --file=migrations/0003_create_users.sql
wrangler d1 execute slacktide-links --remote --file=migrations/0004_create_api_keys.sql
wrangler d1 execute slacktide-links --remote --file=migrations/0005_api_keys_restrict_delete.sql
```
### 5. Set secrets (manual setup only)
Skip if you used Terraform - secrets are already configured.
```bash
wrangler secret put GITHUB_CLIENT_SECRET
```
### 6. Seed the first owner
```bash
# Automatically resolves your GitHub profile and inserts you as owner
npm run seed:owner YOUR_GITHUB_USERNAME -- --remote
```
Pulls your GitHub profile and inserts you as the first owner in production D1. Omit `-- --remote` for local dev. Safe to re-run.
### 7. Deploy
```bash
npm run deploy:worker
npm run deploy:admin
```
### 8. Register the OAuth client
After the worker is deployed, register the admin SPA as an OAuth client:
```bash
curl -X POST https://your-domain.example.com/oauth/register \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Slacktide Admin",
"redirect_uris": ["https://admin.your-domain.example.com/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}'
```
This returns a `client_id`. Set it as `VITE_OAUTH_CLIENT_ID` in the admin SPA's environment and redeploy.
## Local Development
### Worker
Create `.dev.vars` in the project root:
```
GITHUB_CLIENT_SECRET=your_dev_github_client_secret
```
Create a **separate** GitHub OAuth App for local dev with callback URL `http://localhost:8787/auth/github/callback` and set its client ID in `wrangler.toml` under `[env.dev.vars]`.
```bash
npm run dev
```
### Admin
```bash
VITE_API_URL=http://localhost:8787 VITE_OAUTH_CLIENT_ID=your_dev_client_id npm run dev:admin
```
### Mock mode (no backend needed)
```bash
npm run dev:mock -w packages/admin
```
Runs the admin SPA with `VITE_MOCK=true` - no worker or database needed. All API calls return fake data, and mutations reset on refresh.
### OAuth client registration
Register a dev OAuth client:
```bash
curl -X POST http://localhost:8787/oauth/register \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Slacktide Admin (dev)",
"redirect_uris": ["http://localhost:5173/callback"],
"token_endpoint_auth_method": "none",
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"]
}'
```
## API Reference
Full spec in [`openapi.yaml`](openapi.yaml) (also served at `/api/openapi.json`). All `/api/*` routes require a Bearer token:
### Authentication
#### OAuth 2.1 (browser)
The admin SPA uses OAuth 2.1 with PKCE via GitHub. See setup steps above.
#### API keys (programmatic)
For CI/CD, scripts, and integrations that can't go through the browser OAuth flow. Create keys from the admin Settings page.
Keys use the format `stk__` (**s**lack**t**ide **k**ey) and work with the same `Authorization: Bearer` header. The worker resolves `stk_` tokens against D1 before falling back to OAuth.
```bash
# Example: list links with an API key
curl -H "Authorization: Bearer stk_aB3kZ9mX_..." https://your-domain.example.com/api/links
```
Secrets are SHA-256 hashed at rest (shown once at creation). Keys inherit the creating user's role and are deleted when the user is removed. Rotate by creating a new key, updating consumers, then revoking the old one.
### Links
| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/api/links?page=&per_page=&search=` | Bearer | List links (paginated, search matches slug, URL, and tags) |
| `POST` | `/api/links` | Bearer | Create link |
| `GET` | `/api/links/:id` | Bearer | Get link by ID |
| `PUT` | `/api/links/:id` | Bearer | Update link |
| `DELETE` | `/api/links/:id` | Bearer | Deactivate link |
### Analytics
| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/api/analytics/overview` | Bearer | Dashboard stats |
| `GET` | `/api/analytics/:slug` | Bearer | Per-link analytics |
### Users
| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/api/users` | Bearer (owner) | List all users |
| `POST` | `/api/users` | Bearer (owner) | Add user by GitHub username |
| `DELETE` | `/api/users/:id` | Bearer (owner) | Remove user |
### API Keys
| Method | Path | Auth | Description |
|---|---|---|---|
| `POST` | `/api/keys` | Bearer | Create API key (returns secret once) |
| `GET` | `/api/keys` | Bearer | List current user's keys (owners: `?user_id=X`) |
| `GET` | `/api/keys/:keyId` | Bearer | Get key metadata (own keys, or any as owner) |
| `DELETE` | `/api/keys/:keyId` | Bearer | Revoke key (own keys, or any as owner) |
### Auth
| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/api/me` | Bearer | Current user profile |
> **Bearer** = OAuth access token or API key (`stk_*`). Both use `Authorization: Bearer `.
>
> **Bearer (owner)** = same, but requires the `owner` role.
### Public
| Method | Path | Auth | Description |
|---|---|---|---|
| `GET` | `/:slug` | None | Redirect to destination URL |
| `GET` | `/health` | None | Health check |
| `GET` | `/.well-known/oauth-authorization-server` | None | OAuth server metadata |
## Admin Dashboard
- **Dashboard** - overview stats (total links, clicks today/this week), top links with click counts, clicks-over-time chart, top countries
- **Links** - create/edit/deactivate/delete links, search by slug/URL/tag, filter by status and tags (multi-select), per-link analytics with charts and breakdown tables
- **Users** (owner only) - add/remove users by GitHub username, assign roles
- **Settings** - create and revoke API keys
## Environment Variables
| Variable | Where | Description |
|---|---|---|
| `ADMIN_ORIGIN` | `wrangler.toml` | Admin SPA origin for CORS |
| `GITHUB_CLIENT_ID` | `wrangler.toml` | GitHub OAuth App client ID |
| `GITHUB_CLIENT_SECRET` | `wrangler secret` | GitHub OAuth App client secret |
| `VITE_API_URL` | Admin env | Worker URL (defaults to same-origin) |
| `VITE_OAUTH_CLIENT_ID` | Admin env | Registered OAuth client ID |
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
## License
MIT - see [LICENSE](LICENSE).