Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Trade-offs:
Decision: Support both canonical and compatibility endpoints.

Implemented compatibility includes:
- URLs: `POST /shorten`, `POST /urls`, `GET /urls`, `GET /urls/<id>`, `PUT/PATCH /urls/<id>`, `DELETE /urls/<id>`, `GET /<short_code>`, `GET /r/<short_code>`.
- URLs: `POST /shorten`, `POST /urls`, `GET /urls`, `GET /urls/{id}`, `PUT/PATCH /urls/{id}`, `DELETE /urls/{id}`, `GET /{short_code}`, `GET /r/{short_code}`.
- Users: CRUD endpoints plus `POST /users/bulk`.
- Events: `GET /events`, `POST /events` with filtering support.

Expand Down
15 changes: 9 additions & 6 deletions DEPLOYMENT_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- [x] Link Health: APScheduler worker, 5min interval, HEAD checks

### Phase 4: API Routes ✅
- [x] URLs: POST /shorten, GET /<code>, PATCH/DELETE /urls/<id>, GET /urls
- [x] URLs: POST /shorten, GET /{short_code}, PATCH/DELETE /urls/{id}, GET /urls
- [x] Health: GET /health (DB+Redis status), GET /metrics (Prometheus)

### Phase 5: Testing ✅
Expand Down Expand Up @@ -72,9 +72,9 @@

### ✅ API Contracts (Akshay's Dependencies)
- [x] POST /shorten → 201 {"id":1, "short_code":"aB3xYz"}
- [x] GET /<code> → 302 redirect (or 404/410)
- [x] PATCH /urls/<id> → 200 {"message":"updated"}
- [x] DELETE /urls/<id> → 200 {"message":"deleted"}
- [x] GET /{short_code} → 302 redirect (or 404/410)
- [x] PATCH /urls/{id} → 200 {"message":"updated"}
- [x] DELETE /urls/{id} → 200 {"message":"deleted"}
- [x] GET /urls → 200 [array]
- [x] GET /health → 200/503 with component status
- [x] GET /metrics → Prometheus text format
Expand Down Expand Up @@ -115,12 +115,15 @@ curl -X POST http://localhost:5000/shorten \
-H "Content-Type: application/json" \
-d '{"original_url":"https://example.com"}'

# Set SHORT_CODE from step 7 response (example: abc123)
SHORT_CODE="abc123"

# 8. Test redirect (use short_code from step 7)
curl -I http://localhost:5000/<short_code>
curl -I "http://localhost:5000/${SHORT_CODE}"

# 9. Test 410 edge case (delete URL first)
curl -X DELETE http://localhost:5000/urls/1
curl http://localhost:5000/<short_code> # Should return 410
curl "http://localhost:5000/${SHORT_CODE}" # Should return 410

# 10. Test 409 edge case (duplicate short_code)
curl -X POST http://localhost:5000/shorten \
Expand Down
30 changes: 19 additions & 11 deletions QUICKREF.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,48 +31,56 @@ curl -s http://localhost/metrics | grep -E "ghostlink_rollbacks_total|ghostlink_

## 📡 API Quick Reference

Template style is consistent with `docs/API_REFERENCE.md`:

- Request Example
- Success Response
- Error Responses

```bash
# Create short URL
curl -X POST http://localhost:5000/shorten \
curl -X POST http://localhost/shorten \
-H "Content-Type: application/json" \
-d '{"original_url":"https://example.com"}'
# → {"id":1, "short_code":"abc123"}

# Redirect
curl -I http://localhost:5000/abc123
curl -I http://localhost/abc123
# → 302 Location: https://example.com

# List URLs
curl http://localhost:5000/urls
curl http://localhost/urls

# Update URL
curl -X PATCH http://localhost:5000/urls/1 \
curl -X PATCH http://localhost/urls/1 \
-H "Content-Type: application/json" \
-d '{"title":"New Title"}'

# Delete URL (soft delete)
curl -X DELETE http://localhost:5000/urls/1
curl -X DELETE http://localhost/urls/1

# Health Check
curl http://localhost:5000/health
# → {"status":"ok","db":"ok","redis":"ok"}
curl http://localhost/health
# → {"status":"ok","version":"v1",...,"db":"ok","redis":"ok"}

# Metrics
curl http://localhost:5000/metrics
curl http://localhost/metrics
```

## 🎯 Status Codes

| Code | Meaning | When |
|------|---------|------|
| 200 | OK | Successful GET/PATCH/DELETE |
| 200 | OK | Successful read/update/delete and health/metrics routes |
| 201 | Created | URL shortened successfully |
| 302 | Redirect | Short code found, redirecting |
| 400 | Bad Request | Missing body or required fields |
| 400 | Bad Request | Missing body, malformed JSON, or invalid request fields |
| 403 | Forbidden | Ownership mismatch on protected update/delete operations |
| 404 | Not Found | Short code doesn't exist |
| 409 | Conflict | Short code already taken |
| 410 | Gone | Link is inactive (soft deleted) |
| 410 | Gone | Link is inactive or quarantined |
| 422 | Unprocessable | Invalid URL format |
| 500 | Server Error | Short code generation failure |
| 503 | Degraded | Database connection error |

## 🛡️ Risk Scoring
Expand Down
25 changes: 16 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ GhostLink shortens URLs, tracks redirect events, scores link risk, and monitors
## What it does

- Shorten URLs with auto-generated or custom short codes
- Redirect users via `GET /<short_code>`
- Redirect users via `GET /{short_code}`
- Track click events with referrer and user attribution
- Score link risk based on five signals (dead destination, ghost probes, suspicious clients, canary failures, threat patterns)
- Quarantine high-risk short codes via Nginx without taking the app down
Expand Down Expand Up @@ -115,30 +115,37 @@ Truthy values accepted for all flags: `true`, `1`, `yes`, `on` (case-insensitive

## API Endpoints

Endpoint request and response templates use the same style in [docs/API_REFERENCE.md](docs/API_REFERENCE.md): Request Example, Success Response, and Error Responses.

### Health
| Method | Path | Description |
|---|---|---|
| `GET` | `/health` | Returns DB and Redis status |
| `GET` | `/health` | Returns DB/Redis status plus release metadata and feature flags |
| `GET` | `/metrics` | Prometheus metrics |
| `GET` | `/health-demo`, `/promo-demo`, `/checkout-demo`, `/dashboard-demo`, `/support-demo` | Synthetic canary health routes |

### URLs
| Method | Path | Description |
|---|---|---|
| `POST` | `/shorten` | Create short URL with compact response payload |
| `POST` | `/urls` | Create a short URL |
| `GET` | `/urls` | List all URLs (supports `?user_id=1&is_active=true`) |
| `GET` | `/urls/<id>` | Get a URL by ID |
| `PUT` | `/urls/<id>` | Update title or is_active |
| `DELETE` | `/urls/<id>` | Soft delete a URL |
| `GET` | `/<short_code>` | Redirect to original URL |
| `GET` | `/urls/{id}` | Get a URL by ID |
| `PATCH`, `PUT` | `/urls/{id}` | Update mutable URL fields |
| `DELETE` | `/urls/{id}` | Soft delete a URL |
| `GET` | `/urls/{id}/risk` | Get risk score details for URL |
| `GET` | `/{short_code}` | Redirect to original URL |
| `GET` | `/r/{short_code}` | Redirect alias |
| `GET` | `/urls/{short_code}` | Redirect alias |

### Users
| Method | Path | Description |
|---|---|---|
| `POST` | `/users` | Create a user |
| `GET` | `/users` | List all users (supports `?page=1&per_page=10`) |
| `GET` | `/users/<id>` | Get a user by ID |
| `PUT` | `/users/<id>` | Update a user |
| `DELETE` | `/users/<id>` | Delete a user |
| `GET` | `/users/{id}` | Get a user by ID |
| `PATCH`, `PUT` | `/users/{id}` | Update a user |
| `DELETE` | `/users/{id}` | Delete a user |
| `POST` | `/users/bulk` | Bulk import from CSV |

### Events
Expand Down
132 changes: 35 additions & 97 deletions README_GHOSTLINK.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,122 +49,60 @@ Exposes at `GET /metrics` for Prometheus scraping:

## API Endpoints

For a concise endpoint matrix with curl examples and status-code behavior, see `docs/API_REFERENCE.md`.
Endpoint wording style is standardized across docs:

### POST /shorten
Create a shortened URL.
- `Request Example`
- `Success Response`
- `Error Responses`

**Request:**
```json
{
"original_url": "https://example.com",
"short_code": "custom1", // optional
"title": "Example Site", // optional
"user_id": 1 // optional
}
```
Canonical request/response templates and complete error details live in `docs/API_REFERENCE.md`.

**Response (201):**
```json
{
"id": 1,
"short_code": "abc123"
}
```

**Errors:**
- 400: Missing request body / Missing original_url
- 409: Short code already exists
- 422: Invalid URL format

### GET /<short_code>
Redirect to the original URL.
### Endpoint Matrix

**Response:**
- 302: Redirect to original URL
- 404: Short code not found
- 410: Link inactive (soft deleted)
| Method | Path | Success | Common Errors | Notes |
|---|---|---:|---|---|
| `POST` | `/shorten` | `201` | `400`, `409`, `422`, `500` | Create short URL with compact response |
| `POST` | `/urls` | `201` | `400`, `409`, `422`, `500` | Create short URL with full URL record (`user_id` required) |
| `GET` | `/{short_code}` | `302` | `404`, `410` | Primary redirect route |
| `GET` | `/r/{short_code}` | `302` | `404`, `410` | Redirect alias |
| `GET` | `/urls/{short_code}` | `302` | `404`, `410` | Redirect alias |
| `GET` | `/urls` | `200` | - | Supports `user_id` and `is_active` filtering |
| `GET` | `/urls/{id}` | `200` | `404` | Fetch URL by ID |
| `PATCH`, `PUT` | `/urls/{id}` | `200` | `400`, `403`, `404`, `422` | Update mutable URL fields |
| `DELETE` | `/urls/{id}` | `200` | `400`, `403`, `404` | Soft delete URL |
| `GET` | `/urls/{id}/risk` | `200` | `404` | Risk score and signal details |
| `GET` | `/health` | `200` | `503` | Includes release metadata and feature flags |
| `GET` | `/metrics` | `200` | - | Prometheus exposition format |
| `GET` | `/health-demo`, `/promo-demo`, `/checkout-demo`, `/dashboard-demo`, `/support-demo` | `200` | - | Synthetic canary routes |

### GET /urls
List all URLs (optionally filter by user_id).

**Query params:**
- `user_id` (optional): Filter by user

**Response (200):**
```json
[
{
"id": 1,
"short_code": "abc123",
"original_url": "https://example.com",
"title": "Example",
"is_active": true,
"created_at": "2026-04-04T12:00:00",
"updated_at": "2026-04-04T12:00:00"
}
]
```
### Response Template Example (POST /shorten)

### PATCH /urls/<id>
Update a URL.
Request Example:

**Request:**
```json
{
"title": "New Title", // optional
"original_url": "https://...", // optional
"is_active": false // optional
"original_url": "https://example.com",
"short_code": "custom1",
"title": "Example Site",
"user_id": 1
}
```

**Response (200):**
```json
{"message": "updated"}
```

**Errors:**
- 400: Missing request body
- 404: URL not found
- 422: Invalid URL format

### DELETE /urls/<id>
Soft delete a URL (sets is_active=False).
Success Response (`201`):

**Response (200):**
```json
{"message": "deleted"}
```

**Errors:**
- 404: URL not found

### GET /health
Health check with dependency status.

**Response (200):**
```json
{
"status": "ok",
"db": "ok",
"redis": "ok"
}
```

**Response (503 - Degraded):**
```json
{
"status": "degraded",
"db": "error",
"redis": "ok"
"id": 1,
"short_code": "abc123"
}
```

### GET /metrics
Prometheus metrics in text format.
Error Responses:

### GET /urls/<id>/risk
Return computed risk score, tier, and active signals for a URL.
- `400` missing body, malformed JSON, or invalid field type
- `409` short code already exists
- `422` invalid URL format
- `500` short code generation failure

## Tech Stack

Expand Down
17 changes: 8 additions & 9 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3.9"

services:
nginx:
image: nginx:1.27-alpine
Expand Down Expand Up @@ -288,20 +286,21 @@ services:
- ghostlink_net

grafana:
image: grafana/grafana:11.1.4
image: grafana/grafana:latest
container_name: ghostlink-grafana
environment:
GF_SECURITY_ADMIN_USER: admin
GF_SECURITY_ADMIN_PASSWORD: ghostlink
GF_USERS_ALLOW_SIGN_UP: "false"
ports:
- "3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana-data:/var/lib/grafana
- ./grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
- ./grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards
- ./grafana/dashboards:/var/lib/grafana/dashboards
depends_on:
prometheus:
condition: service_healthy
- prometheus
restart: always
networks:
- ghostlink_net
Expand All @@ -312,4 +311,4 @@ networks:

volumes:
postgres_data:
grafana_data:
grafana-data:
Loading
Loading