Files
gooseek/backend/internal/admin/migrations.go
home a0e3748dde feat: auth service + security audit fixes + cleanup legacy services
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
2026-02-28 01:33:49 +03:00

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
}