Files
JustAMassenger/server/internal/database/database.go
T
SashegDev 096c4d0a2d Initial commit: JustAMessenger v0.1.0
Серверная часть (Go):
- WebSocket сервер с бинарным протоколом
- XChaCha20-Poly1305 шифрование
- zstd сжатие с дедупликацией (64KB чанки)
- SQLite хранилище (WAL режим)
- Управление гильдиями, каналами, ролями
- Федерация между серверами (ed25519)
- REST API + WebSocket endpoints

Клиентская часть (Flutter):
- Material Design 3 тёмная тема (Discord-like)
- WebSocket соединение с сервером
- Экраны: сплэш, логин, домашний, гильдии, чат
- Модели: пользователи, гильдии, каналы, сообщения, роли
- Сервисы: соединение, API, криптография, тема
- Виджеты: иконки гильдий, сообщения, ввод чата
- Web сборка (PWA)

Документация:
- AGENTS.md — контекст для ИИ ассистентов
- docs/protocol.md — спецификация протокола
2026-06-06 22:39:14 +00:00

191 lines
4.7 KiB
Go

package database
import (
"database/sql"
"os"
"path/filepath"
"time"
_ "github.com/mattn/go-sqlite3"
)
type DB struct {
*sql.DB
}
func Open(dataDir string) (*DB, error) {
if err := os.MkdirAll(dataDir, 0755); err != nil {
return nil, err
}
dbPath := filepath.Join(dataDir, "jam.db")
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&_busy_timeout=5000&_synchronous=NORMAL")
if err != nil {
return nil, err
}
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(time.Hour)
d := &DB{db}
if err := d.migrate(); err != nil {
return nil, err
}
return d, nil
}
func (db *DB) migrate() error {
schema := `
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
avatar TEXT DEFAULT '',
bio TEXT DEFAULT '',
public_key BLOB,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS guilds (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
owner_id TEXT NOT NULL,
icon TEXT DEFAULT '',
description TEXT DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (owner_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS guild_members (
guild_id TEXT NOT NULL,
user_id TEXT NOT NULL,
nickname TEXT DEFAULT '',
avatar TEXT DEFAULT '',
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (guild_id, user_id),
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS categories (
id TEXT PRIMARY KEY,
guild_id TEXT NOT NULL,
name TEXT NOT NULL,
position INTEGER DEFAULT 0,
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS channels (
id TEXT PRIMARY KEY,
guild_id TEXT NOT NULL,
category_id TEXT DEFAULT '',
name TEXT NOT NULL,
type TEXT NOT NULL DEFAULT 'text',
topic TEXT DEFAULT '',
position INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS roles (
id TEXT PRIMARY KEY,
guild_id TEXT NOT NULL,
name TEXT NOT NULL,
color INTEGER DEFAULT 0,
position INTEGER DEFAULT 0,
permissions TEXT DEFAULT '[]',
is_default INTEGER DEFAULT 0,
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS role_members (
role_id TEXT NOT NULL,
user_id TEXT NOT NULL,
PRIMARY KEY (role_id, user_id),
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS messages (
id TEXT PRIMARY KEY,
channel_id TEXT NOT NULL,
author_id TEXT NOT NULL,
content BLOB,
encrypted INTEGER DEFAULT 0,
nonce BLOB,
message_type TEXT DEFAULT 'text',
reply_to TEXT,
pinned INTEGER DEFAULT 0,
self_destruct DATETIME,
edited_at DATETIME,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (channel_id) REFERENCES channels(id) ON DELETE CASCADE,
FOREIGN KEY (author_id) REFERENCES users(id)
);
CREATE INDEX IF NOT EXISTS idx_messages_channel ON messages(channel_id, created_at);
CREATE TABLE IF NOT EXISTS attachments (
id TEXT PRIMARY KEY,
message_id TEXT NOT NULL,
filename TEXT NOT NULL,
file_type TEXT DEFAULT '',
size INTEGER DEFAULT 0,
compressed INTEGER DEFAULT 0,
original_size INTEGER DEFAULT 0,
url TEXT NOT NULL,
hash TEXT DEFAULT '',
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS reactions (
message_id TEXT NOT NULL,
user_id TEXT NOT NULL,
emoji TEXT NOT NULL,
PRIMARY KEY (message_id, user_id, emoji),
FOREIGN KEY (message_id) REFERENCES messages(id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS stickers (
id TEXT PRIMARY KEY,
guild_id TEXT NOT NULL,
name TEXT NOT NULL,
data BLOB,
format TEXT DEFAULT 'png',
FOREIGN KEY (guild_id) REFERENCES guilds(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS voice_states (
user_id TEXT NOT NULL,
channel_id TEXT NOT NULL,
guild_id TEXT NOT NULL,
muted INTEGER DEFAULT 0,
deafened INTEGER DEFAULT 0,
PRIMARY KEY (user_id, channel_id)
);
CREATE TABLE IF NOT EXISTS streams (
id TEXT PRIMARY KEY,
channel_id TEXT NOT NULL,
user_id TEXT NOT NULL,
title TEXT DEFAULT '',
started_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS federation_peers (
domain TEXT PRIMARY KEY,
name TEXT NOT NULL,
public_key BLOB,
last_seen DATETIME,
is_active INTEGER DEFAULT 1
);
CREATE TABLE IF NOT EXISTS server_config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
`
_, err := db.Exec(schema)
return err
}