mirror of
git://f0xx.org/android_cast
synced 2026-07-29 03:57:50 +03:00
url shortener addons
This commit is contained in:
41
backend/url-shortener/DEPLOY.md
Normal file
41
backend/url-shortener/DEPLOY.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# URL shortener — deploy (FE → BE)
|
||||
|
||||
**Topology:** same as [INFRA.md](../../docs/INFRA.md) — TLS on Gentoo FE, app on Alpine BE `:80`.
|
||||
|
||||
## Prerequisites (confirmed)
|
||||
|
||||
| Item | Status |
|
||||
|------|--------|
|
||||
| DNS `s.f0xx.org` → FE | PO |
|
||||
| FE TLS cert | `/etc/letsencrypt/live/s.f0xx.org/` |
|
||||
| Git remote | `git://f0xx.org/androicast_project/url-shortener` |
|
||||
| MariaDB schema `url_shortener` | `sql/schema.mariadb.sql` |
|
||||
|
||||
## FE (Gentoo)
|
||||
|
||||
Add `deploy/nginx.fe-s.f0xx.org.snippet` inside a new `server { listen 443 ssl; server_name s.f0xx.org; }` block
|
||||
(mirror `apps.f0xx.org` TLS paths; cert paths above).
|
||||
|
||||
```bash
|
||||
nginx -t && rc-service nginx reload
|
||||
```
|
||||
|
||||
## BE (Alpine)
|
||||
|
||||
1. Sync this tree to docroot (suggested): `/var/www/localhost/htdocs/apps/s/`
|
||||
2. Include `deploy/nginx.be-url-shortener.fragment` in `apps.conf` **or** dedicated `server_name` on BE
|
||||
3. Apply SQL migration
|
||||
4. `rc-service php-fpm81 restart` if needed
|
||||
|
||||
```bash
|
||||
nginx -t && rc-service nginx reload
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
curl -sSI https://s.f0xx.org/api/v1/health
|
||||
./scripts/smoke_shorten.sh
|
||||
```
|
||||
|
||||
Not added to `validate_be_services.sh` until first production deploy (SPEC).
|
||||
42
backend/url-shortener/README.md
Normal file
42
backend/url-shortener/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# URL shortener (`s.f0xx.org`)
|
||||
|
||||
Standalone opt-in service — **not** part of `examples/crash_reporter/backend/`.
|
||||
|
||||
| Doc | Path |
|
||||
|-----|------|
|
||||
| SPEC | [docs/specs/20100611_3_url_shortener.md](../../docs/specs/20100611_3_url_shortener.md) |
|
||||
| DR | [docs/DRs/20100611_3_url_shortener.md](../../docs/DRs/20100611_3_url_shortener.md) |
|
||||
| Deploy | [DEPLOY.md](DEPLOY.md) |
|
||||
|
||||
**Status:** scaffold (OpenAPI + SQL + nginx fragments + smoke scripts). PHP handlers TODO.
|
||||
|
||||
**Git remote (PO confirmed):** `git://f0xx.org/androicast_project/url-shortener`
|
||||
**Public URL:** `https://s.f0xx.org`
|
||||
**FE TLS (PO confirmed):** `/etc/letsencrypt/live/s.f0xx.org/{fullchain.pem,privkey.pem}`
|
||||
|
||||
## Quick smoke (when deployed)
|
||||
|
||||
```bash
|
||||
export BASE=https://s.f0xx.org
|
||||
export BEARER=demo-bearer-replace-me
|
||||
./scripts/demo_curl.sh
|
||||
```
|
||||
|
||||
## Local dev (future)
|
||||
|
||||
```bash
|
||||
# MariaDB: apply sql/schema.mariadb.sql
|
||||
# php -S 127.0.0.1:8091 -t public
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
```text
|
||||
backend/url-shortener/
|
||||
openapi.yaml # API contract (source of truth with SPEC §6)
|
||||
deploy/ # FE + BE nginx fragments
|
||||
sql/schema.mariadb.sql
|
||||
scripts/demo_curl.sh
|
||||
scripts/smoke_shorten.sh
|
||||
public/index.php # stub router (health only until impl)
|
||||
```
|
||||
29
backend/url-shortener/deploy/nginx.be-url-shortener.fragment
Normal file
29
backend/url-shortener/deploy/nginx.be-url-shortener.fragment
Normal file
@@ -0,0 +1,29 @@
|
||||
# BE Alpine — url-shortener vhost fragment
|
||||
# Include from /etc/nginx/conf.d/apps.conf or separate file.
|
||||
#
|
||||
# Docroot: /var/www/localhost/htdocs/apps/s/public
|
||||
# PHP-FPM: same socket as crash reporter unless load warrants a pool.
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name s.f0xx.org;
|
||||
|
||||
root /var/www/localhost/htdocs/apps/s/public;
|
||||
index index.php;
|
||||
|
||||
# API + QR
|
||||
location ^~ /api/ {
|
||||
try_files $uri /index.php?$query_string;
|
||||
}
|
||||
|
||||
# Short slug redirect (12–16 hex chars)
|
||||
location ~ ^/[a-f0-9]{6,16}$ {
|
||||
try_files $uri /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
include fastcgi_params;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_pass unix:/run/php-fpm81.sock;
|
||||
}
|
||||
}
|
||||
30
backend/url-shortener/deploy/nginx.fe-s.f0xx.org.snippet
Normal file
30
backend/url-shortener/deploy/nginx.fe-s.f0xx.org.snippet
Normal file
@@ -0,0 +1,30 @@
|
||||
# FE Gentoo — server block for s.f0xx.org (URL shortener)
|
||||
# File: tmp/FE_gentoo/etc/nginx/nginx.conf OR conf.d/s.f0xx.org.conf
|
||||
#
|
||||
# TLS cert (confirmed on FE):
|
||||
# ssl_certificate /etc/letsencrypt/live/s.f0xx.org/fullchain.pem;
|
||||
# ssl_certificate_key /etc/letsencrypt/live/s.f0xx.org/privkey.pem;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name s.f0xx.org;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name s.f0xx.org;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/s.f0xx.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/s.f0xx.org/privkey.pem;
|
||||
|
||||
# All paths → BE :80 (PHP-FPM url-shortener vhost)
|
||||
location / {
|
||||
proxy_pass http://artc0.intra.raptor.org:80;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
253
backend/url-shortener/openapi.yaml
Normal file
253
backend/url-shortener/openapi.yaml
Normal file
@@ -0,0 +1,253 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Android Cast URL Shortener
|
||||
description: |
|
||||
Standalone service at https://s.f0xx.org — SPEC docs/specs/20100611_3_url_shortener.md
|
||||
version: 0.1.0
|
||||
servers:
|
||||
- url: https://s.f0xx.org
|
||||
description: Production (FE TLS → BE :80)
|
||||
|
||||
paths:
|
||||
/api/v1/health:
|
||||
get:
|
||||
summary: Service health
|
||||
operationId: health
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
service:
|
||||
type: string
|
||||
|
||||
/api/v1/shorten:
|
||||
get:
|
||||
summary: Shorten URL (query params)
|
||||
description: Prefer POST for long URLs or sensitive query strings.
|
||||
operationId: shortenGet
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/url'
|
||||
- $ref: '#/components/parameters/bearer'
|
||||
- $ref: '#/components/parameters/signer'
|
||||
- $ref: '#/components/parameters/ttl'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/ShortenSuccess'
|
||||
'400':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'401':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'403':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'409':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'429':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
post:
|
||||
summary: Shorten URL (JSON body) — preferred
|
||||
operationId: shortenPost
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ShortenRequest'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/components/responses/ShortenSuccess'
|
||||
'400':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'401':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'403':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'409':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
'429':
|
||||
$ref: '#/components/responses/ShortenError'
|
||||
|
||||
/api/v1/qr/{slug}.png:
|
||||
get:
|
||||
summary: QR code PNG for short URL
|
||||
operationId: qrPng
|
||||
parameters:
|
||||
- name: slug
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^[a-f0-9]{6,16}$'
|
||||
- name: size
|
||||
in: query
|
||||
schema:
|
||||
type: integer
|
||||
default: 256
|
||||
minimum: 128
|
||||
maximum: 512
|
||||
responses:
|
||||
'200':
|
||||
description: PNG image
|
||||
content:
|
||||
image/png:
|
||||
schema:
|
||||
type: string
|
||||
format: binary
|
||||
'404':
|
||||
description: Unknown slug
|
||||
'410':
|
||||
description: Expired slug
|
||||
|
||||
/{slug}:
|
||||
get:
|
||||
summary: Redirect to original URL
|
||||
operationId: redirect
|
||||
parameters:
|
||||
- name: slug
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
pattern: '^[a-f0-9]{6,16}$'
|
||||
responses:
|
||||
'302':
|
||||
description: Found — Location is original URL
|
||||
headers:
|
||||
Location:
|
||||
schema:
|
||||
type: string
|
||||
format: uri
|
||||
'404':
|
||||
description: Unknown slug
|
||||
'410':
|
||||
description: Expired slug
|
||||
head:
|
||||
summary: Redirect probe (no body)
|
||||
operationId: redirectHead
|
||||
parameters:
|
||||
- name: slug
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'302':
|
||||
description: Found
|
||||
'404':
|
||||
description: Unknown
|
||||
'410':
|
||||
description: Expired
|
||||
|
||||
components:
|
||||
parameters:
|
||||
url:
|
||||
name: url
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
format: uri
|
||||
maxLength: 2048
|
||||
bearer:
|
||||
name: bearer
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
minLength: 8
|
||||
signer:
|
||||
name: signer
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
ttl:
|
||||
name: ttl
|
||||
in: query
|
||||
required: false
|
||||
description: Seconds until expiry; 0 = permanent (signer required)
|
||||
schema:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 86400
|
||||
|
||||
schemas:
|
||||
ShortenRequest:
|
||||
type: object
|
||||
required: [url, bearer]
|
||||
properties:
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
maxLength: 2048
|
||||
bearer:
|
||||
type: string
|
||||
signer:
|
||||
type: string
|
||||
ttl:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 86400
|
||||
ShortenSuccess:
|
||||
type: object
|
||||
required: [code, url, u, bearer, ttl]
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
enum: ['0']
|
||||
url:
|
||||
type: string
|
||||
format: uri
|
||||
u:
|
||||
type: string
|
||||
format: uri
|
||||
description: Short URL
|
||||
bearer:
|
||||
type: string
|
||||
ttl:
|
||||
type: string
|
||||
description: Remaining seconds as string; "0" if permanent
|
||||
qr:
|
||||
type: string
|
||||
format: uri
|
||||
description: Optional v1.1 — URL to QR PNG
|
||||
ShortenError:
|
||||
type: object
|
||||
required: [code, desc]
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
enum:
|
||||
- ACCESS_DENIED
|
||||
- MISSING_BEARER
|
||||
- INVALID_URL
|
||||
- TTL_EXPIRED
|
||||
- SIGNER_REQUIRED
|
||||
- RATE_LIMITED
|
||||
- INTERNAL
|
||||
- NOT_IMPLEMENTED
|
||||
url:
|
||||
type: string
|
||||
bearer:
|
||||
type: string
|
||||
desc:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
ShortenSuccess:
|
||||
description: Short URL created or cached
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ShortenSuccess'
|
||||
ShortenError:
|
||||
description: Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ShortenError'
|
||||
27
backend/url-shortener/public/index.php
Normal file
27
backend/url-shortener/public/index.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* URL shortener front controller (stub).
|
||||
* Routes: /api/v1/health, /api/v1/shorten, /api/v1/qr/{slug}.png, /{slug}
|
||||
* Full implementation tracked in docs/specs/20100611_3_url_shortener.md
|
||||
*/
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
|
||||
|
||||
if ($uri === '/api/v1/health') {
|
||||
echo json_encode([
|
||||
'status' => 'stub',
|
||||
'service' => 'url-shortener',
|
||||
'message' => 'PHP handlers not deployed yet — see backend/url-shortener/README.md',
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(501);
|
||||
echo json_encode([
|
||||
'code' => 'NOT_IMPLEMENTED',
|
||||
'desc' => 'url-shortener API stub — deploy implementation from git://f0xx.org/androicast_project/url-shortener',
|
||||
], JSON_UNESCAPED_SLASHES);
|
||||
55
backend/url-shortener/scripts/demo_curl.sh
Executable file
55
backend/url-shortener/scripts/demo_curl.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env bash
|
||||
# End-to-end curl cookbook — SPEC appendix A.
|
||||
# Usage:
|
||||
# export BASE=https://s.f0xx.org
|
||||
# export BEARER=your-trusted-token
|
||||
# export SIGNER=optional-for-ttl0
|
||||
# ./scripts/demo_curl.sh
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${BASE:-https://s.f0xx.org}"
|
||||
BEARER="${BEARER:?set BEARER to a trusted API token}"
|
||||
SIGNER="${SIGNER:-}"
|
||||
TARGET="${TARGET:-https://defender.net/very/long/link?demo=1}"
|
||||
TTL="${TTL:-3600}"
|
||||
|
||||
echo "==> 1) Health"
|
||||
curl -fsS "$BASE/api/v1/health" | head -c 200
|
||||
echo
|
||||
|
||||
echo "==> 2) Shorten (POST preferred)"
|
||||
SHORT_JSON="$(curl -fsS -X POST "$BASE/api/v1/shorten" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$(jq -nc --arg url "$TARGET" --arg bearer "$BEARER" --argjson ttl "$TTL" \
|
||||
'{url:$url,bearer:$bearer,ttl:$ttl}')")"
|
||||
echo "$SHORT_JSON" | jq .
|
||||
|
||||
SHORT="$(echo "$SHORT_JSON" | jq -r '.u // empty')"
|
||||
if [[ -z "$SHORT" ]]; then
|
||||
echo "shorten failed (no .u in response)" >&2
|
||||
exit 1
|
||||
fi
|
||||
SLUG="${SHORT##*/}"
|
||||
|
||||
echo "==> 3) Shorten cache hit (same bearer + url)"
|
||||
curl -fsS -X POST "$BASE/api/v1/shorten" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$(jq -nc --arg url "$TARGET" --arg bearer "$BEARER" --argjson ttl "$TTL" \
|
||||
'{url:$url,bearer:$bearer,ttl:$ttl}')" | jq .
|
||||
|
||||
echo "==> 4) Redirect (HEAD — no body)"
|
||||
curl -sSI "$SHORT" | grep -E '^(HTTP|Location:)'
|
||||
|
||||
echo "==> 5) QR PNG"
|
||||
curl -fsS -o "/tmp/url-shortener-qr-${SLUG}.png" "$BASE/api/v1/qr/${SLUG}.png?size=256"
|
||||
file "/tmp/url-shortener-qr-${SLUG}.png"
|
||||
|
||||
if [[ -n "$SIGNER" ]]; then
|
||||
echo "==> 6) Permanent link (ttl=0, signer required)"
|
||||
curl -fsS -X POST "$BASE/api/v1/shorten" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "$(jq -nc --arg url "$TARGET&perm=1" --arg bearer "$BEARER" --arg signer "$SIGNER" \
|
||||
'{url:$url,bearer:$bearer,signer:$signer,ttl:0}')" | jq .
|
||||
fi
|
||||
|
||||
echo "==> Done"
|
||||
29
backend/url-shortener/scripts/smoke_shorten.sh
Executable file
29
backend/url-shortener/scripts/smoke_shorten.sh
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Minimal deploy smoke — exits non-zero on failure.
|
||||
set -euo pipefail
|
||||
|
||||
BASE="${BASE:-https://s.f0xx.org}"
|
||||
BEARER="${BEARER:-}"
|
||||
|
||||
echo "==> health $BASE/api/v1/health"
|
||||
code="$(curl -sS -o /tmp/url-shortener-health.json -w '%{http_code}' "$BASE/api/v1/health" || true)"
|
||||
if [[ "$code" != "200" ]]; then
|
||||
echo "FAIL health HTTP $code" >&2
|
||||
cat /tmp/url-shortener-health.json 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
cat /tmp/url-shortener-health.json
|
||||
echo
|
||||
|
||||
if [[ -z "$BEARER" ]]; then
|
||||
echo "SKIP shorten (set BEARER to run full smoke)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "==> shorten POST"
|
||||
curl -fsS -X POST "$BASE/api/v1/shorten" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"url":"https://example.com/smoke","bearer":"'"$BEARER"'","ttl":300}' \
|
||||
| jq -e '.code == "0" and (.u | length) > 0' >/dev/null
|
||||
|
||||
echo "OK"
|
||||
62
backend/url-shortener/sql/schema.mariadb.sql
Normal file
62
backend/url-shortener/sql/schema.mariadb.sql
Normal file
@@ -0,0 +1,62 @@
|
||||
-- URL shortener — MariaDB schema (SPEC docs/specs/20100611_3_url_shortener.md §9)
|
||||
CREATE DATABASE IF NOT EXISTS url_shortener
|
||||
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
USE url_shortener;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bearer_tokens (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
token_hash CHAR(64) NOT NULL,
|
||||
label VARCHAR(128) NOT NULL DEFAULT '',
|
||||
trusted TINYINT(1) NOT NULL DEFAULT 0,
|
||||
can_temporary TINYINT(1) NOT NULL DEFAULT 1,
|
||||
can_permanent TINYINT(1) NOT NULL DEFAULT 0,
|
||||
rate_limit_per_hour INT UNSIGNED NOT NULL DEFAULT 1000,
|
||||
revoked_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_bearer_token_hash (token_hash)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS signer_keys (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
key_hash CHAR(64) NOT NULL,
|
||||
label VARCHAR(128) NOT NULL DEFAULT '',
|
||||
can_permanent TINYINT(1) NOT NULL DEFAULT 1,
|
||||
revoked_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_signer_key_hash (key_hash)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS links (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
bearer_id INT UNSIGNED NOT NULL,
|
||||
signer_id INT UNSIGNED NULL,
|
||||
slug VARCHAR(16) NOT NULL,
|
||||
origin TEXT NOT NULL,
|
||||
origin_hash CHAR(64) NOT NULL,
|
||||
ttl_seconds INT UNSIGNED NOT NULL DEFAULT 86400,
|
||||
expires_at DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
accessed_at DATETIME NULL,
|
||||
access_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_slug (slug),
|
||||
KEY idx_bearer_origin (bearer_id, origin_hash),
|
||||
CONSTRAINT fk_links_bearer FOREIGN KEY (bearer_id) REFERENCES bearer_tokens (id),
|
||||
CONSTRAINT fk_links_signer FOREIGN KEY (signer_id) REFERENCES signer_keys (id)
|
||||
) ENGINE=InnoDB;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
event VARCHAR(32) NOT NULL,
|
||||
bearer_id INT UNSIGNED NULL,
|
||||
slug VARCHAR(16) NULL,
|
||||
ip VARCHAR(45) NOT NULL DEFAULT '',
|
||||
detail TEXT,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (id),
|
||||
KEY idx_audit_created (created_at)
|
||||
) ENGINE=InnoDB;
|
||||
@@ -92,7 +92,7 @@ endobj
|
||||
endobj
|
||||
13 0 obj
|
||||
<<
|
||||
/Author (Android Cast project) /CreationDate (D:20260521190501+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260521190501+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Author (Android Cast project) /CreationDate (D:20260611185343+02'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260611185343+02'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||
/Subject (\(unspecified\)) /Title (Android Cast \204 Next-gen egress review) /Trapped /False
|
||||
>>
|
||||
endobj
|
||||
@@ -153,7 +153,7 @@ xref
|
||||
trailer
|
||||
<<
|
||||
/ID
|
||||
[<69103a44ba79ad224f4c4d9612cf06ea><69103a44ba79ad224f4c4d9612cf06ea>]
|
||||
[<7980b3b39b2662662624213f08f5e4c7><7980b3b39b2662662624213f08f5e4c7>]
|
||||
% ReportLab generated PDF document -- digest (opensource)
|
||||
|
||||
/Info 13 0 R
|
||||
|
||||
Reference in New Issue
Block a user