Major changes:
- Add auth-svc: JWT auth, register/login/refresh, password reset
- Add auth UI: modals, pages (/login, /register, /forgot-password)
- Add usage tracking (usage_metrics table, daily limits)
- Add tiered rate limiting (free/pro/business)
- Add LLM usage limits per tier
Security fixes:
- All repos now require userID for Update/Delete operations
- JWT middleware in chat-svc, llm-svc, agent-svc, discover-svc
- ErrNotFound/ErrForbidden errors for proper access control
Cleanup:
- Remove legacy TypeScript services/ directory
- Remove computer-svc (to be reimplemented)
- Remove old deploy/docker configs
New files:
- backend/cmd/auth-svc/main.go
- backend/internal/auth/{types,repository}.go
- backend/internal/usage/{types,repository}.go
- backend/pkg/middleware/{llm_limits,ratelimit_tiered}.go
- backend/webui/src/components/auth/*
- backend/webui/src/app/(auth)/*
Made-with: Cursor
154 lines
5.7 KiB
Go
154 lines
5.7 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
)
|
|
|
|
func RunAdminMigrations(ctx context.Context, db *sql.DB) error {
|
|
migrations := []string{
|
|
`CREATE TABLE IF NOT EXISTS users (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
email VARCHAR(255) NOT NULL UNIQUE,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
display_name VARCHAR(255) NOT NULL,
|
|
avatar_url TEXT,
|
|
role VARCHAR(50) NOT NULL DEFAULT 'user',
|
|
tier VARCHAR(50) NOT NULL DEFAULT 'free',
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
last_login_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS posts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
title VARCHAR(500) NOT NULL,
|
|
slug VARCHAR(255) NOT NULL UNIQUE,
|
|
content TEXT NOT NULL,
|
|
excerpt TEXT,
|
|
cover_image TEXT,
|
|
author_id UUID REFERENCES users(id) ON DELETE SET NULL,
|
|
category VARCHAR(100) NOT NULL DEFAULT 'general',
|
|
tags JSONB DEFAULT '[]',
|
|
status VARCHAR(50) NOT NULL DEFAULT 'draft',
|
|
view_count INT NOT NULL DEFAULT 0,
|
|
published_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_posts_author ON posts(author_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_posts_category ON posts(category)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_posts_published_at ON posts(published_at DESC)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS platform_settings (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
site_name VARCHAR(255) NOT NULL DEFAULT 'GooSeek',
|
|
site_url VARCHAR(500) NOT NULL DEFAULT 'https://gooseek.ru',
|
|
logo_url TEXT,
|
|
favicon_url TEXT,
|
|
description TEXT,
|
|
support_email VARCHAR(255),
|
|
features JSONB NOT NULL DEFAULT '{}',
|
|
llm_settings JSONB NOT NULL DEFAULT '{}',
|
|
search_settings JSONB NOT NULL DEFAULT '{}',
|
|
metadata JSONB DEFAULT '{}',
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS discover_categories (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(100) NOT NULL UNIQUE,
|
|
name_ru VARCHAR(100) NOT NULL,
|
|
icon VARCHAR(10) NOT NULL DEFAULT '📰',
|
|
color VARCHAR(20) NOT NULL DEFAULT '#6B7280',
|
|
keywords JSONB NOT NULL DEFAULT '[]',
|
|
regions JSONB NOT NULL DEFAULT '["world"]',
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
sort_order INT NOT NULL DEFAULT 0,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_discover_categories_name ON discover_categories(name)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_discover_categories_sort ON discover_categories(sort_order)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS discover_sources (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(255) NOT NULL,
|
|
url VARCHAR(500) NOT NULL UNIQUE,
|
|
logo_url TEXT,
|
|
categories JSONB NOT NULL DEFAULT '[]',
|
|
trust_score DECIMAL(3,2) NOT NULL DEFAULT 0.5,
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
description TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_discover_sources_url ON discover_sources(url)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_discover_sources_trust ON discover_sources(trust_score DESC)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS audit_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL,
|
|
user_email VARCHAR(255) NOT NULL,
|
|
action VARCHAR(100) NOT NULL,
|
|
resource VARCHAR(100) NOT NULL,
|
|
resource_id VARCHAR(255),
|
|
details JSONB,
|
|
ip_address VARCHAR(45),
|
|
user_agent TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_user ON audit_logs(user_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON audit_logs(resource)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON audit_logs(created_at DESC)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS connectors (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
type VARCHAR(50) NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
config JSONB NOT NULL DEFAULT '{}',
|
|
is_active BOOLEAN NOT NULL DEFAULT true,
|
|
last_sync_at TIMESTAMPTZ,
|
|
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
|
error_msg TEXT,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_connectors_type ON connectors(type)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_connectors_status ON connectors(status)`,
|
|
|
|
`CREATE TABLE IF NOT EXISTS user_files (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
filename VARCHAR(500) NOT NULL,
|
|
original_name VARCHAR(500) NOT NULL,
|
|
file_type VARCHAR(100) NOT NULL,
|
|
file_size BIGINT NOT NULL,
|
|
bucket VARCHAR(100) NOT NULL DEFAULT 'user-files',
|
|
storage_key TEXT NOT NULL,
|
|
mime_type VARCHAR(100),
|
|
metadata JSONB DEFAULT '{}',
|
|
is_public BOOLEAN NOT NULL DEFAULT false,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_user_files_user ON user_files(user_id)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_user_files_type ON user_files(file_type)`,
|
|
`CREATE INDEX IF NOT EXISTS idx_user_files_bucket ON user_files(bucket)`,
|
|
}
|
|
|
|
for _, migration := range migrations {
|
|
if _, err := db.ExecContext(ctx, migration); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|