mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 15:19:40 +02:00
mana-search-go → mana-search mana-notify-go → mana-notify mana-crawler-go → mana-crawler mana-api-gateway-go → mana-api-gateway Legacy NestJS versions are deleted, suffix no longer needed. Updated all references in docker-compose, CLAUDE.md, package.json, Forgejo workflows, and service package.json files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
119 lines
3.2 KiB
Go
119 lines
3.2 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
)
|
|
|
|
// DB wraps a pgx connection pool.
|
|
type DB struct {
|
|
Pool *pgxpool.Pool
|
|
}
|
|
|
|
// New creates a new database connection pool.
|
|
func New(ctx context.Context, databaseURL string) (*DB, error) {
|
|
config, err := pgxpool.ParseConfig(databaseURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse db config: %w", err)
|
|
}
|
|
|
|
config.MaxConns = 20
|
|
config.MinConns = 2
|
|
config.MaxConnLifetime = 30 * time.Minute
|
|
config.MaxConnIdleTime = 5 * time.Minute
|
|
|
|
pool, err := pgxpool.NewWithConfig(ctx, config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create pool: %w", err)
|
|
}
|
|
|
|
if err := pool.Ping(ctx); err != nil {
|
|
return nil, fmt.Errorf("ping: %w", err)
|
|
}
|
|
|
|
slog.Info("database connected")
|
|
return &DB{Pool: pool}, nil
|
|
}
|
|
|
|
// Migrate creates the schema and tables.
|
|
func (d *DB) Migrate(ctx context.Context) error {
|
|
sql := `
|
|
CREATE SCHEMA IF NOT EXISTS api_gateway;
|
|
|
|
CREATE TABLE IF NOT EXISTS api_gateway.api_keys (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
key TEXT NOT NULL UNIQUE,
|
|
key_hash TEXT NOT NULL,
|
|
key_prefix TEXT NOT NULL,
|
|
user_id TEXT,
|
|
organization_id TEXT,
|
|
name TEXT NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
tier TEXT NOT NULL DEFAULT 'free',
|
|
rate_limit INT NOT NULL DEFAULT 10,
|
|
monthly_credits INT NOT NULL DEFAULT 100,
|
|
credits_used INT NOT NULL DEFAULT 0,
|
|
credits_reset_at TIMESTAMPTZ,
|
|
allowed_endpoints JSONB DEFAULT '["search"]',
|
|
allowed_ips JSONB,
|
|
active BOOLEAN NOT NULL DEFAULT true,
|
|
expires_at TIMESTAMPTZ,
|
|
last_used_at TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_key_hash ON api_gateway.api_keys(key_hash);
|
|
CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_gateway.api_keys(user_id);
|
|
|
|
CREATE TABLE IF NOT EXISTS api_gateway.api_usage (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
api_key_id UUID NOT NULL REFERENCES api_gateway.api_keys(id) ON DELETE CASCADE,
|
|
endpoint TEXT NOT NULL,
|
|
method TEXT NOT NULL,
|
|
path TEXT NOT NULL,
|
|
request_size INT,
|
|
response_size INT,
|
|
latency_ms INT,
|
|
status_code INT,
|
|
credits_used INT NOT NULL DEFAULT 0,
|
|
credit_reason TEXT,
|
|
metadata JSONB,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_api_usage_key_id ON api_gateway.api_usage(api_key_id);
|
|
CREATE INDEX IF NOT EXISTS idx_api_usage_created ON api_gateway.api_usage(created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS api_gateway.api_usage_daily (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
api_key_id UUID NOT NULL REFERENCES api_gateway.api_keys(id) ON DELETE CASCADE,
|
|
date DATE NOT NULL,
|
|
endpoint TEXT NOT NULL,
|
|
request_count INT NOT NULL DEFAULT 0,
|
|
credits_used INT NOT NULL DEFAULT 0,
|
|
total_latency_ms INT NOT NULL DEFAULT 0,
|
|
error_count INT NOT NULL DEFAULT 0,
|
|
UNIQUE(api_key_id, date, endpoint)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_api_usage_daily_date ON api_gateway.api_usage_daily(date);
|
|
`
|
|
|
|
_, err := d.Pool.Exec(ctx, sql)
|
|
if err != nil {
|
|
return fmt.Errorf("migrate: %w", err)
|
|
}
|
|
|
|
slog.Info("database migrated")
|
|
return nil
|
|
}
|
|
|
|
// Close closes the connection pool.
|
|
func (d *DB) Close() {
|
|
d.Pool.Close()
|
|
}
|