- Add login page with JWT authentication - Add dashboard with stats and quick actions - Add links management page (full CRUD with search) - Add collections management page - Add API key management page with copy-to-clipboard - Add admin user management page (admin only) - Fix UUID type mismatches across all endpoints - Add updated_at column to api_keys and audit_log in schema.sql - Fix DB_PASSWORD default in docker-compose.yml - Add PyJWT to requirements.txt - Fix API docs URL (/docs instead of /api/docs) - Improve JS error handling (show actual messages) - Rewrite conftest.py with proper DB lifecycle management - Add 42 new integration tests (84 total, all passing) - test_admin.py: 15 tests for admin endpoints - test_auth_extended.py: 9 tests for API key CRUD - test_tags.py: 12 tests for tag endpoints - test_sync.py: 6 tests for sync endpoints
118 lines
4.3 KiB
PL/PgSQL
118 lines
4.3 KiB
PL/PgSQL
-- LinkSyncServer Database Schema
|
|
|
|
-- Enable UUID extension
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- Users table
|
|
CREATE TABLE users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
username VARCHAR(100) UNIQUE NOT NULL,
|
|
email VARCHAR(255) UNIQUE NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
role VARCHAR(20) NOT NULL CHECK (role IN ('admin', 'user')),
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- API Keys table
|
|
CREATE TABLE api_keys (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
key_hash VARCHAR(255) NOT NULL,
|
|
name VARCHAR(100),
|
|
expires_at TIMESTAMP WITH TIME ZONE,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Tags table
|
|
CREATE TABLE tags (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name VARCHAR(100) UNIQUE NOT NULL,
|
|
color VARCHAR(7),
|
|
description TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Links table (bookmarks)
|
|
CREATE TABLE links (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
url TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
description TEXT,
|
|
notes TEXT,
|
|
tags JSONB DEFAULT '[]',
|
|
favicon_url TEXT,
|
|
path TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
visit_count INTEGER DEFAULT 0,
|
|
is_bookmarked BOOLEAN DEFAULT FALSE,
|
|
source_set_id UUID REFERENCES links(id), -- Self-reference for duplicate tracking
|
|
user_id UUID REFERENCES users(id)
|
|
);
|
|
|
|
-- Create indexes for links
|
|
CREATE INDEX links_url_idx ON links(url);
|
|
CREATE INDEX links_title_idx ON links(title);
|
|
CREATE INDEX links_tags_idx ON links USING GIN (tags);
|
|
CREATE INDEX links_created_idx ON links(created_at);
|
|
CREATE INDEX links_user_idx ON links(user_id);
|
|
CREATE INDEX links_fts_idx ON links USING GIN (to_tsvector('english', url || ' ' || title || ' ' || description || ' ' || notes));
|
|
|
|
-- Collections table
|
|
CREATE TABLE collections (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name VARCHAR(200) NOT NULL,
|
|
description TEXT,
|
|
query_type VARCHAR(20) NOT NULL CHECK (query_type IN ('static', 'dynamic')),
|
|
query_expression JSONB, -- Parsed AST
|
|
is_public BOOLEAN DEFAULT FALSE,
|
|
created_by UUID REFERENCES users(id),
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Collection links (for static collections)
|
|
CREATE TABLE collection_links (
|
|
collection_id UUID REFERENCES collections(id) ON DELETE CASCADE,
|
|
link_id UUID REFERENCES links(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (collection_id, link_id)
|
|
);
|
|
|
|
-- Audit log table
|
|
CREATE TABLE audit_log (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
action VARCHAR(100) NOT NULL,
|
|
entity_type VARCHAR(50) NOT NULL,
|
|
entity_id UUID,
|
|
old_value JSONB,
|
|
new_value JSONB,
|
|
ip_address INET,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Create audit log index
|
|
CREATE INDEX audit_log_created_idx ON audit_log(created_at);
|
|
CREATE INDEX audit_log_user_idx ON audit_log(user_id);
|
|
|
|
-- Full-text search for tags
|
|
CREATE INDEX tags_name_idx ON tags USING GIN (to_tsvector('english', name || ' ' || description));
|
|
|
|
-- Triggers for updated_at timestamp
|
|
CREATE OR REPLACE FUNCTION update_timestamps() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER update_users_timestamps BEFORE UPDATE ON users FOR EACH ROW EXECUTE FUNCTION update_timestamps();
|
|
CREATE TRIGGER update_links_timestamps BEFORE UPDATE ON links FOR EACH ROW EXECUTE FUNCTION update_timestamps();
|
|
CREATE TRIGGER update_collections_timestamps BEFORE UPDATE ON collections FOR EACH ROW EXECUTE FUNCTION update_timestamps();
|
|
CREATE TRIGGER update_tags_timestamps BEFORE UPDATE ON tags FOR EACH ROW EXECUTE FUNCTION update_timestamps(); |