- OpenAI als vollwertiger Chat-Provider (Modell-Auswahl, Key-Routing, StreamChat) - Hardcoded OpenAI-Modelle im Cache (gpt-5.x, gpt-4.1, o3, o4-mini etc.) - /api/models/openai Endpoint, OpenAI-Optgroup im Agent-Dropdown - Key-Routing: OpenAI-Key aus api_keys-Tabelle, Fallback via GetFirstActiveKeyByProvider - OpenAI-Checker: 2-Schritt-Check (Models + chat/completions mit max_tokens=1) - Google-Checker: 2-Schritt-Check (Models + generateContent mit maxOutputTokens=1) - 429 insufficient_quota und billing_not_active korrekt als quota_exceeded erkannt - Provider-Erkennung beim Adopt via KeyPatterns.DetectType() statt hardcoded anthropic - HandleCheck/HandleCheckAll unterstützt jetzt Anthropic, OpenAI und Google - Neue scanner_logs-Tabelle mit details-Spalte (JSON) für strukturierte Auswertung - GitHubEventScannerService: AddLog persistiert in DB, detaillierte Key-Fund-Logs - KeyScannerService: AddLog-Methode mit DB-Persistierung, detaillierte Logs - Frontend: Ungeprüfte-prüfen-Button (sequenziell, stoppt bei valid) - Frontend: Provider-Indicator im Key-Switch-Modal (Anthropic/OpenAI/Google) - Frontend: Check-Button für alle Provider (nicht nur Anthropic)
234 lines
9.3 KiB
C#
234 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Dapper;
|
|
using Gott.Models;
|
|
|
|
namespace Gott.Services;
|
|
|
|
public partial class DatabaseService
|
|
{
|
|
// ── Shared Messages ──
|
|
|
|
public async Task<SharedMessageRecord> CreateSharedMessage(long messageId, string userId, bool includeContext, string expiresAt = null)
|
|
{
|
|
using var conn = CreateConnection();
|
|
var record = new SharedMessageRecord
|
|
{
|
|
ShareToken = Guid.NewGuid().ToString("N"),
|
|
MessageId = messageId,
|
|
UserId = userId,
|
|
IncludeContext = includeContext,
|
|
ExpiresAt = expiresAt,
|
|
CreatedAt = DateTime.UtcNow.ToString("o")
|
|
};
|
|
await conn.ExecuteAsync(@"
|
|
INSERT INTO shared_messages (share_token, message_id, user_id, include_context, expires_at, created_at)
|
|
VALUES (@ShareToken, @MessageId, @UserId, @IncludeContext, @ExpiresAt, @CreatedAt)
|
|
", record);
|
|
return record;
|
|
}
|
|
|
|
public async Task<SharedMessageRecord> GetSharedMessage(string shareToken)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<SharedMessageRecord>(
|
|
"SELECT * FROM shared_messages WHERE share_token = @shareToken", new { shareToken });
|
|
}
|
|
|
|
public async Task<IEnumerable<SharedMessageRecord>> GetSharedMessagesByUser(string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QueryAsync<SharedMessageRecord>(
|
|
"SELECT * FROM shared_messages WHERE user_id = @userId ORDER BY created_at DESC", new { userId });
|
|
}
|
|
|
|
public async Task DeleteSharedMessage(string shareToken, string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync(
|
|
"DELETE FROM shared_messages WHERE share_token = @shareToken AND user_id = @userId",
|
|
new { shareToken, userId });
|
|
}
|
|
|
|
public async Task<MessageRecord> GetMessage(long messageId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<MessageRecord>(
|
|
"SELECT * FROM messages WHERE id = @messageId", new { messageId });
|
|
}
|
|
|
|
public async Task<List<MessageRecord>> GetChainMessagesUpTo(string chainId, long messageId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
var result = await conn.QueryAsync<MessageRecord>(
|
|
"SELECT * FROM messages WHERE chain_id = @chainId AND id <= @messageId ORDER BY id ASC",
|
|
new { chainId, messageId });
|
|
return result.AsList();
|
|
}
|
|
|
|
|
|
// ── Users ──
|
|
|
|
public void EnsureDefaultUser(AuthService auth)
|
|
{
|
|
using var conn = CreateConnection();
|
|
var count = conn.ExecuteScalar<int>("SELECT COUNT(*) FROM users");
|
|
string adminUserId;
|
|
if (count == 0)
|
|
{
|
|
adminUserId = Guid.NewGuid().ToString("N");
|
|
var now = DateTime.UtcNow.ToString("o");
|
|
conn.Execute(@"
|
|
INSERT INTO users (user_id, username, password_hash, display_name, is_admin, created_at, updated_at)
|
|
VALUES (@userId, @username, @hash, @displayName, 1, @now, @now)",
|
|
new
|
|
{
|
|
userId = adminUserId,
|
|
username = "admin",
|
|
hash = auth.HashPassword("admin"),
|
|
displayName = "Admin",
|
|
now
|
|
});
|
|
}
|
|
else
|
|
{
|
|
adminUserId = conn.ExecuteScalar<string>("SELECT user_id FROM users ORDER BY id LIMIT 1");
|
|
}
|
|
|
|
// Bestehende Sessions/Agents ohne User dem ersten User zuweisen
|
|
conn.Execute("UPDATE sessions SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
|
conn.Execute("UPDATE agents SET user_id = @userId WHERE user_id IS NULL", new { userId = adminUserId });
|
|
|
|
// Erster User ist immer Admin
|
|
conn.Execute("UPDATE users SET is_admin = 1 WHERE user_id = @userId AND is_admin = 0", new { userId = adminUserId });
|
|
}
|
|
|
|
public async Task<UserRecord> GetUserByUsername(string username)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<UserRecord>(
|
|
"SELECT * FROM users WHERE username = @username COLLATE NOCASE",
|
|
new { username });
|
|
}
|
|
|
|
public async Task<UserRecord> GetUser(string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<UserRecord>(
|
|
"SELECT * FROM users WHERE user_id = @userId",
|
|
new { userId });
|
|
}
|
|
|
|
public async Task<IEnumerable<UserRecord>> GetUsers()
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QueryAsync<UserRecord>(
|
|
"SELECT * FROM users ORDER BY username");
|
|
}
|
|
|
|
public async Task UpdateUserPassword(string userId, string passwordHash)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync(
|
|
"UPDATE users SET password_hash = @passwordHash, updated_at = @now WHERE user_id = @userId",
|
|
new { userId, passwordHash, now = DateTime.UtcNow.ToString("o") });
|
|
}
|
|
|
|
public async Task UpdateUserProfile(string userId, string displayName, string persona)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync(
|
|
"UPDATE users SET display_name = @displayName, persona = @persona, updated_at = @now WHERE user_id = @userId",
|
|
new { userId, displayName, persona, now = DateTime.UtcNow.ToString("o") });
|
|
}
|
|
|
|
public async Task<string> GetUserSettings(string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<string>(
|
|
"SELECT settings FROM users WHERE user_id = @userId",
|
|
new { userId });
|
|
}
|
|
|
|
public async Task UpdateUserSettings(string userId, string settings)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync(
|
|
"UPDATE users SET settings = @settings, updated_at = @now WHERE user_id = @userId",
|
|
new { userId, settings, now = DateTime.UtcNow.ToString("o") });
|
|
}
|
|
|
|
public async Task<UserRecord> CreateUser(string username, string passwordHash, string displayName, bool isAdmin, string defaultPath = null)
|
|
{
|
|
using var conn = CreateConnection();
|
|
var now = DateTime.UtcNow.ToString("o");
|
|
var userId = Guid.NewGuid().ToString("N");
|
|
await conn.ExecuteAsync(@"
|
|
INSERT INTO users (user_id, username, password_hash, display_name, is_admin, default_path, created_at, updated_at)
|
|
VALUES (@userId, @username, @passwordHash, @displayName, @isAdmin, @defaultPath, @now, @now)",
|
|
new { userId, username, passwordHash, displayName, isAdmin, defaultPath, now });
|
|
return await GetUser(userId);
|
|
}
|
|
|
|
public async Task UpdateUserAdmin(string userId, string username, string displayName, bool isAdmin, string defaultPath)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync(
|
|
"UPDATE users SET username = @username, display_name = @displayName, is_admin = @isAdmin, default_path = @defaultPath, updated_at = @now WHERE user_id = @userId",
|
|
new { userId, username, displayName, isAdmin, defaultPath, now = DateTime.UtcNow.ToString("o") });
|
|
}
|
|
|
|
public async Task DeleteUser(string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync("DELETE FROM tokens WHERE user_id = @userId", new { userId });
|
|
await conn.ExecuteAsync("DELETE FROM shared_messages WHERE user_id = @userId", new { userId });
|
|
await conn.ExecuteAsync("DELETE FROM users WHERE user_id = @userId", new { userId });
|
|
}
|
|
|
|
// ── User API-Key Auswahl ──
|
|
|
|
public async Task SetUserSelectedApiKey(string userId, string keyId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
await conn.ExecuteAsync(
|
|
"UPDATE users SET selected_api_key_id = @keyId, updated_at = @now WHERE user_id = @userId",
|
|
new { userId, keyId = (object)keyId ?? DBNull.Value, now = DateTime.UtcNow.ToString("o") });
|
|
}
|
|
|
|
public async Task<string> GetUserSelectedApiKeyId(string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<string>(
|
|
"SELECT selected_api_key_id FROM users WHERE user_id = @userId",
|
|
new { userId });
|
|
}
|
|
|
|
public async Task<ApiKeyRecord> GetUserSelectedApiKey(string userId)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<ApiKeyRecord>(@"
|
|
SELECT k.* FROM api_keys k
|
|
INNER JOIN users u ON u.selected_api_key_id = k.key_id
|
|
WHERE u.user_id = @userId",
|
|
new { userId });
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gibt den ersten aktiven API-Key für einen bestimmten Provider zurück.
|
|
/// Wird verwendet um z.B. den OpenAI-Key für Modell-Abfragen zu holen.
|
|
/// </summary>
|
|
public async Task<string> GetFirstActiveKeyByProvider(string provider)
|
|
{
|
|
using var conn = CreateConnection();
|
|
return await conn.QuerySingleOrDefaultAsync<string>(@"
|
|
SELECT api_key FROM api_keys
|
|
WHERE provider = @provider AND (status IS NULL OR status = 'valid' OR status = 'ok')
|
|
ORDER BY created_at ASC
|
|
LIMIT 1",
|
|
new { provider });
|
|
}
|
|
|
|
}
|