Files
crm-wiki/backend/Wiki.Api/Db.cs

126 lines
5.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Data.Sqlite;
namespace Wiki.Api;
/// <summary>Подключения к SQLite и схема. Файл базы лежит на постоянном томе (Storage:DataDir).</summary>
public sealed class Db
{
public string DataDir { get; }
public string ImagesDir { get; }
private readonly string _cs;
public Db(IConfiguration cfg)
{
DataDir = Path.GetFullPath(cfg["Storage:DataDir"] ?? "data");
ImagesDir = Path.Combine(DataDir, "images");
Directory.CreateDirectory(ImagesDir);
_cs = new SqliteConnectionStringBuilder
{
DataSource = Path.Combine(DataDir, "wiki.db"),
Cache = SqliteCacheMode.Shared,
ForeignKeys = true,
}.ToString();
}
public SqliteConnection Open()
{
var c = new SqliteConnection(_cs);
c.Open();
using var cmd = c.CreateCommand();
cmd.CommandText = "PRAGMA busy_timeout=5000;";
cmd.ExecuteNonQuery();
return c;
}
public void Init()
{
using var c = Open();
Exec(c, """
PRAGMA journal_mode=WAL;
CREATE TABLE IF NOT EXISTS meta(
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
INSERT OR IGNORE INTO meta(key, value) VALUES ('created_at', strftime('%Y-%m-%dT%H:%M:%fZ','now'));
INSERT OR IGNORE INTO meta(key, value) VALUES ('schema', '1');
-- Папки и страницы одной таблицей: kind = 'folder' | 'page'.
CREATE TABLE IF NOT EXISTS nodes(
id TEXT PRIMARY KEY,
workspace_id TEXT NOT NULL,
parent_id TEXT NULL REFERENCES nodes(id) ON DELETE CASCADE,
kind TEXT NOT NULL CHECK (kind IN ('folder','page')),
title TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
version INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
created_by TEXT NOT NULL,
updated_at TEXT NOT NULL,
updated_by TEXT NOT NULL,
updated_by_name TEXT NULL
);
CREATE INDEX IF NOT EXISTS ix_nodes_ws_parent ON nodes(workspace_id, parent_id);
CREATE TABLE IF NOT EXISTS images(
id TEXT PRIMARY KEY,
workspace_id TEXT NOT NULL,
page_id TEXT NULL,
mime TEXT NOT NULL,
size INTEGER NOT NULL,
file_name TEXT NOT NULL,
created_at TEXT NOT NULL,
created_by TEXT NOT NULL
);
-- Профили людей и агентов (автор, последний редактор): имя, почта, аватар и цвет кружка из CRM.
CREATE TABLE IF NOT EXISTS users(
uid TEXT PRIMARY KEY,
name TEXT NULL,
email TEXT NULL,
avatar_url TEXT NULL,
color TEXT NULL,
updated_at TEXT NOT NULL
);
-- Для уже существующих записей — хотя бы имя, сохранённое при правке.
INSERT OR IGNORE INTO users(uid, name, updated_at)
SELECT updated_by, updated_by_name, max(updated_at) FROM nodes
WHERE updated_by IS NOT NULL AND updated_by <> '' AND updated_by_name IS NOT NULL
GROUP BY updated_by;
-- Полнотекстовый поиск по названию и тексту (external content на nodes).
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
title, content,
content='nodes', content_rowid='rowid',
tokenize='unicode61 remove_diacritics 2'
);
CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
INSERT INTO nodes_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content);
END;
CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES ('delete', old.rowid, old.title, old.content);
END;
CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE OF title, content ON nodes BEGIN
INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES ('delete', old.rowid, old.title, old.content);
INSERT INTO nodes_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content);
END;
""");
}
public string? Meta(string key)
{
using var c = Open();
using var cmd = c.CreateCommand();
cmd.CommandText = "SELECT value FROM meta WHERE key=$k";
cmd.Parameters.AddWithValue("$k", key);
return cmd.ExecuteScalar() as string;
}
public static void Exec(SqliteConnection c, string sql)
{
using var cmd = c.CreateCommand();
cmd.CommandText = sql;
cmd.ExecuteNonQuery();
}
}