-- Better Auth Core Schema -- See: https://better-auth.com/docs/concepts/database -- Note: Using quoted "user" to avoid SQL keyword issues while keeping Better Auth's expected table name -- User table (better-auth core) CREATE TABLE IF NOT EXISTS "user" ( id TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, "emailVerified" BOOLEAN NOT NULL DEFAULT FALSE, image TEXT, "createdAt" TIMESTAMP NOT NULL DEFAULT NOW(), "updatedAt" TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_user_email ON "user"(email); -- Session table (better-auth core) CREATE TABLE IF NOT EXISTS session ( id TEXT PRIMARY KEY, "expiresAt" TIMESTAMP NOT NULL, token TEXT UNIQUE NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT NOW(), "updatedAt" TIMESTAMP NOT NULL DEFAULT NOW(), "ipAddress" TEXT, "userAgent" TEXT, "userId" TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE ); CREATE INDEX idx_session_userId ON session("userId"); CREATE INDEX idx_session_token ON session(token); -- Account table (better-auth core, for OAuth providers) CREATE TABLE IF NOT EXISTS account ( id TEXT PRIMARY KEY, "accountId" TEXT NOT NULL, "providerId" TEXT NOT NULL, "userId" TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, "accessToken" TEXT, "refreshToken" TEXT, "idToken" TEXT, "accessTokenExpiresAt" TIMESTAMP, "refreshTokenExpiresAt" TIMESTAMP, scope TEXT, password TEXT, "createdAt" TIMESTAMP NOT NULL DEFAULT NOW(), "updatedAt" TIMESTAMP NOT NULL DEFAULT NOW() ); CREATE INDEX idx_account_userId ON account("userId"); -- Verification table (better-auth core) CREATE TABLE IF NOT EXISTS verification ( id TEXT PRIMARY KEY, identifier TEXT NOT NULL, value TEXT NOT NULL, "expiresAt" TIMESTAMP NOT NULL, "createdAt" TIMESTAMP, "updatedAt" TIMESTAMP ); CREATE INDEX idx_verification_identifier ON verification(identifier); -- User license and authorization schema (custom tables) CREATE TABLE IF NOT EXISTS user_licenses ( user_id TEXT PRIMARY KEY REFERENCES "user"(id) ON DELETE CASCADE, email TEXT, license_type TEXT NOT NULL CHECK (license_type IN ('free', 'pro', 'enterprise')), features JSONB NOT NULL DEFAULT '{ "maxIndicators": 5, "maxStrategies": 3, "maxBacktestDays": 30, "realtimeData": false, "customExecutors": false, "apiAccess": false }', resource_limits JSONB NOT NULL DEFAULT '{ "maxConcurrentSessions": 1, "maxMessagesPerDay": 100, "maxTokensPerMessage": 4096, "rateLimitPerMinute": 10 }', mcp_server_url TEXT NOT NULL, preferred_model JSONB DEFAULT NULL, expires_at TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ); COMMENT ON COLUMN user_licenses.preferred_model IS 'Optional model preference: {"provider": "anthropic", "model": "claude-3-5-sonnet-20241022", "temperature": 0.7}'; CREATE INDEX idx_user_licenses_expires_at ON user_licenses(expires_at) WHERE expires_at IS NOT NULL; -- Channel linking for multi-channel support CREATE TABLE IF NOT EXISTS user_channel_links ( id SERIAL PRIMARY KEY, user_id TEXT NOT NULL REFERENCES user_licenses(user_id) ON DELETE CASCADE, channel_type TEXT NOT NULL CHECK (channel_type IN ('telegram', 'slack', 'discord', 'websocket')), channel_user_id TEXT NOT NULL, metadata JSONB, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), UNIQUE(channel_type, channel_user_id) ); CREATE INDEX idx_user_channel_links_user_id ON user_channel_links(user_id); CREATE INDEX idx_user_channel_links_channel ON user_channel_links(channel_type, channel_user_id);