8cea7dee20
- SiteForge Enterprise V8.10.0-dev.1 upstream source (210 tests) - Fix: v8_workflows._mapped_input now reads both 类型/分类 columns so WXR imports keep post/product types instead of collapsing to page - Docs: trade-pro theme port spec (R3) and implementation plan Co-Authored-By: Claude Code <noreply@anthropic.com>
102 lines
4.3 KiB
Python
102 lines
4.3 KiB
Python
"""Local SQLite data store for SiteForge MVP. One database, hard site_id isolation."""
|
|
import os
|
|
import sqlite3
|
|
import json
|
|
from pathlib import Path
|
|
from datetime import datetime, timezone
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
DATA = Path(os.getenv('SITEFORGE_DATA_DIR', str(ROOT / 'storage'))).resolve()
|
|
DB_FILE = Path(os.getenv('SITEFORGE_DB_FILE',str(DATA / 'siteforge.sqlite3'))).resolve()
|
|
DATABASE_URL = os.getenv('SITEFORGE_DATABASE_URL','')
|
|
POSTGRES = DATABASE_URL.startswith(('postgresql://','postgres://'))
|
|
|
|
def now():
|
|
return datetime.now(timezone.utc).isoformat(timespec='seconds')
|
|
|
|
def connect():
|
|
if POSTGRES:
|
|
from . import pg_compat
|
|
return pg_compat.connect(DATABASE_URL)
|
|
DATA.mkdir(parents=True, exist_ok=True)
|
|
c = sqlite3.connect(DB_FILE)
|
|
c.row_factory = sqlite3.Row
|
|
c.execute('PRAGMA foreign_keys = ON')
|
|
c.execute('PRAGMA busy_timeout=5000')
|
|
return c
|
|
|
|
def init():
|
|
with connect() as c:
|
|
c.executescript('''
|
|
PRAGMA journal_mode=WAL;
|
|
CREATE TABLE IF NOT EXISTS sites (
|
|
id TEXT PRIMARY KEY, name TEXT NOT NULL, domain TEXT NOT NULL DEFAULT '',
|
|
tagline TEXT NOT NULL DEFAULT '', theme TEXT NOT NULL DEFAULT 'corporate',
|
|
accent TEXT NOT NULL DEFAULT '#2563eb', logo TEXT NOT NULL DEFAULT '',
|
|
mode TEXT NOT NULL DEFAULT 'new',
|
|
settings_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL, updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS entries (
|
|
id TEXT PRIMARY KEY, site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
|
type TEXT NOT NULL, title TEXT NOT NULL, route TEXT NOT NULL, slug TEXT NOT NULL,
|
|
summary TEXT NOT NULL DEFAULT '', body TEXT NOT NULL DEFAULT '',
|
|
cover TEXT NOT NULL DEFAULT '', seo_title TEXT NOT NULL DEFAULT '',
|
|
seo_description TEXT NOT NULL DEFAULT '', canonical TEXT NOT NULL DEFAULT '',
|
|
robots TEXT NOT NULL DEFAULT 'index,follow',
|
|
meta_json TEXT NOT NULL DEFAULT '{}', status TEXT NOT NULL DEFAULT 'published',
|
|
source TEXT NOT NULL DEFAULT 'manual', created_at TEXT NOT NULL, updated_at TEXT NOT NULL,
|
|
UNIQUE(site_id, route)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_entries_site ON entries(site_id, type, status);
|
|
CREATE TABLE IF NOT EXISTS redirects (
|
|
id TEXT PRIMARY KEY, site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
|
old_path TEXT NOT NULL, new_path TEXT NOT NULL, status_code INTEGER NOT NULL DEFAULT 301,
|
|
UNIQUE(site_id, old_path)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS builds (
|
|
id TEXT PRIMARY KEY, site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
|
created_at TEXT NOT NULL, status TEXT NOT NULL, total_pages INTEGER NOT NULL DEFAULT 0,
|
|
report_json TEXT NOT NULL DEFAULT '{}', zip_path TEXT NOT NULL DEFAULT ''
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_builds_site ON builds(site_id, created_at);
|
|
CREATE TABLE IF NOT EXISTS presets (
|
|
id TEXT PRIMARY KEY,name TEXT NOT NULL,theme TEXT NOT NULL,
|
|
settings_json TEXT NOT NULL,home_blocks_json TEXT NOT NULL,created_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS content_schemas (
|
|
site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
|
content_type TEXT NOT NULL, fields_json TEXT NOT NULL DEFAULT '[]',
|
|
PRIMARY KEY(site_id,content_type)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS imports (
|
|
id TEXT PRIMARY KEY, site_id TEXT NOT NULL REFERENCES sites(id) ON DELETE CASCADE,
|
|
created_at TEXT NOT NULL, source TEXT NOT NULL,
|
|
result_json TEXT NOT NULL DEFAULT '{}'
|
|
);
|
|
''')
|
|
if not POSTGRES:
|
|
from . import schema_registry
|
|
schema_registry.ensure(c)
|
|
|
|
def rows(sql, params=()):
|
|
with connect() as c:
|
|
return [dict(r) for r in c.execute(sql, params).fetchall()]
|
|
|
|
def one(sql, params=()):
|
|
with connect() as c:
|
|
r=c.execute(sql, params).fetchone()
|
|
return dict(r) if r else None
|
|
|
|
def execute(sql, params=()):
|
|
with connect() as c:
|
|
cur = c.execute(sql, params)
|
|
return cur.rowcount
|
|
|
|
def insert(sql, params=()):
|
|
with connect() as c:
|
|
c.execute(sql, params)
|
|
|
|
if __name__ == '__main__':
|
|
init()
|