init
This commit is contained in:
commit
d69342b2e9
160 changed files with 28681 additions and 0 deletions
273
internal/tenant/sqlite.go
Normal file
273
internal/tenant/sqlite.go
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
package tenant
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func openDB(dataDir, tenantID string, inMemory bool) (*sql.DB, error) {
|
||||
var dsn string
|
||||
if inMemory {
|
||||
// named in-memory DB with shared cache so all 'connections' share the same database
|
||||
dsn = "file:" + tenantID + "?mode=memory&cache=shared&_pragma=busy_timeout(5000)"
|
||||
} else {
|
||||
dbPath := filepath.Join(dataDir, tenantID+".db")
|
||||
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dsn = dbPath + "?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := initSchema(db); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func initSchema(db *sql.DB) error {
|
||||
schema := `
|
||||
CREATE TABLE IF NOT EXISTS posts (
|
||||
id TEXT PRIMARY KEY,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
tags TEXT,
|
||||
cover_image TEXT,
|
||||
content_md TEXT,
|
||||
content_html TEXT,
|
||||
is_published INTEGER DEFAULT 0,
|
||||
members_only INTEGER DEFAULT 0,
|
||||
published_at TEXT,
|
||||
updated_at TEXT,
|
||||
aliases TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
modified_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(published_at DESC) WHERE is_published = 1;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS post_drafts (
|
||||
post_id TEXT PRIMARY KEY REFERENCES posts(id) ON DELETE CASCADE,
|
||||
slug TEXT NOT NULL,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
tags TEXT,
|
||||
cover_image TEXT,
|
||||
members_only INTEGER DEFAULT 0,
|
||||
content_md TEXT,
|
||||
content_html TEXT,
|
||||
modified_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS post_versions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
post_id TEXT NOT NULL REFERENCES posts(id) ON DELETE CASCADE,
|
||||
slug TEXT NOT NULL,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
tags TEXT,
|
||||
cover_image TEXT,
|
||||
content_md TEXT,
|
||||
content_html TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_post_versions_post ON post_versions(post_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS pages (
|
||||
path TEXT PRIMARY KEY,
|
||||
html BLOB,
|
||||
etag TEXT,
|
||||
built_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assets (
|
||||
id TEXT PRIMARY KEY,
|
||||
filename TEXT NOT NULL,
|
||||
r2_key TEXT NOT NULL,
|
||||
content_type TEXT,
|
||||
size INTEGER,
|
||||
width INTEGER,
|
||||
height INTEGER,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS site_settings (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
email TEXT NOT NULL,
|
||||
name TEXT,
|
||||
avatar_url TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
token TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users(id),
|
||||
expires_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_sessions_expires ON sessions(expires_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS comments (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL REFERENCES users(id),
|
||||
post_slug TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
content_html TEXT,
|
||||
parent_id INTEGER REFERENCES comments(id),
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_post ON comments(post_slug);
|
||||
CREATE INDEX IF NOT EXISTS idx_comments_parent ON comments(parent_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS reactions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT REFERENCES users(id),
|
||||
anon_id TEXT,
|
||||
post_slug TEXT NOT NULL,
|
||||
emoji TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(user_id, post_slug, emoji),
|
||||
UNIQUE(anon_id, post_slug, emoji)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_reactions_post ON reactions(post_slug);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS page_views (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
path TEXT NOT NULL,
|
||||
post_slug TEXT,
|
||||
referrer TEXT,
|
||||
user_agent TEXT,
|
||||
visitor_hash TEXT,
|
||||
utm_source TEXT,
|
||||
utm_medium TEXT,
|
||||
utm_campaign TEXT,
|
||||
device_type TEXT,
|
||||
browser TEXT,
|
||||
os TEXT,
|
||||
country TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_page_views_path ON page_views(path);
|
||||
CREATE INDEX IF NOT EXISTS idx_page_views_created ON page_views(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_page_views_visitor ON page_views(visitor_hash);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS daily_analytics (
|
||||
date TEXT PRIMARY KEY,
|
||||
requests INTEGER DEFAULT 0,
|
||||
page_views INTEGER DEFAULT 0,
|
||||
unique_visitors INTEGER DEFAULT 0,
|
||||
bandwidth INTEGER DEFAULT 0,
|
||||
browsers TEXT,
|
||||
os TEXT,
|
||||
devices TEXT,
|
||||
countries TEXT,
|
||||
paths TEXT,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS members (
|
||||
user_id TEXT PRIMARY KEY REFERENCES users(id),
|
||||
email TEXT NOT NULL,
|
||||
name TEXT,
|
||||
tier TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
expires_at TEXT,
|
||||
synced_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS api_keys (
|
||||
key TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
last_used_at TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS components (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
type TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
compiled TEXT,
|
||||
client_directive TEXT DEFAULT 'load',
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS plugins (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
language TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
wasm BLOB,
|
||||
hooks TEXT NOT NULL,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webhooks (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
events TEXT NOT NULL,
|
||||
secret TEXT,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
|
||||
last_triggered_at TEXT,
|
||||
last_status TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
webhook_id TEXT NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
||||
event TEXT NOT NULL,
|
||||
payload TEXT,
|
||||
status TEXT NOT NULL,
|
||||
response_code INTEGER,
|
||||
response_body TEXT,
|
||||
attempts INTEGER DEFAULT 1,
|
||||
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook ON webhook_deliveries(webhook_id, created_at DESC);
|
||||
`
|
||||
|
||||
_, err := db.Exec(schema)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init schema: %w", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS search_index USING fts5(
|
||||
slug UNINDEXED,
|
||||
collection_slug UNINDEXED,
|
||||
title,
|
||||
description,
|
||||
content,
|
||||
type UNINDEXED,
|
||||
url UNINDEXED,
|
||||
date UNINDEXED
|
||||
)
|
||||
`)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init fts5: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue