using Microsoft.EntityFrameworkCore; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using QuotifyBE.Data; using QuotifyBE.Entities; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Security.Cryptography; using System.Text; namespace QuotifyBE.Controllers; public class GeneralUseHelpers(ApplicationDbContext db, IConfiguration appsettings) { private readonly ApplicationDbContext _db = db; private readonly IConfiguration _appsettings = appsettings; // Allows to check whether the user is of role present in roles. // Example: // For user with role 0, // - IsUser(["Manager"], req) yields false // - IsUser(["Admin"], req) yields true // - IsUser(["Admin", "Manager"], req) yields true because the user is an admin public bool IsUser(string[] roles, HttpRequest req) { // Get the user to read its roles User? user = GetUserFromToken(req.Headers.Authorization!); if (user == null) { return false; } foreach (var role in roles) { if (string.IsNullOrEmpty(role)) { continue; } switch (role) { case "Admin": if (user.Role == 0) return true; break; case "Manager": if (user.Role == 1) return true; break; case "Pracownik": if (user.Role == 2) return true; break; default: continue; } } return false; } public string UserRoleAsStr(User user) { switch (user.Role) { case 0: return "Admin"; case 1: return "Manager"; case 2: return "Pracownik"; default: return "Unknown role"; } } public User? GetUserFromToken(string token) { if (token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { token = token.Substring("Bearer ".Length).Trim(); } var handler = new JwtSecurityTokenHandler(); var jwtSecurityToken = handler.ReadJwtToken(token); if (int.TryParse(jwtSecurityToken.Subject, out int userId)) { return _db.Users.FirstOrDefault(u => u.Id == userId); } return null; } async public Task GetUserFromEmail(string email) { return await _db.Users.FirstOrDefaultAsync(e => e.Email == email); } public string HashWithSHA512(string s) { using (var sha512 = SHA512.Create()) { byte[] bytes = Encoding.ASCII.GetBytes(s); byte[] hash = sha512.ComputeHash(bytes); string hashstring = BitConverter.ToString(hash).Replace("-", "").ToLower(); return hashstring; } } public string GenerateJwtToken(User user) { var claims = new[] { new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; var key = new SymmetricSecurityKey( Encoding.UTF8.GetBytes( // JwtSecret won't be null here - otherwise Program.cs wouldn't start _appsettings["JwtSecret"]! ) ); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var token = new JwtSecurityToken( issuer: _appsettings["DomainName"]!, audience: _appsettings["DomainName"]!, claims: claims, // https://stackoverflow.com/questions/21978658/invalidating-json-web-tokens#comment45057142_23089839 // small validity timeframe is important for invalidating tokens after a user changed their password expires: DateTime.Now.AddMinutes(5), signingCredentials: creds ); return new JwtSecurityTokenHandler().WriteToken(token); } public async Task GenerateLLMResponse(string? prompt, string? model, float? temp, int? includedCategory, bool? includeCategorySample) { string _model = model ?? _appsettings.GetSection("LlmIntegration")["DefaultModel"] ?? "deepclaude"; float _temp = temp ?? 0.6f; // sane default string _included_sample = string.Empty; string _prompt = prompt ?? _appsettings.GetSection("LlmIntegration")["DefaultPrompt"] ?? "Cześć, czy jesteś w stanie wymyślić i stworzyć jeden oryginalny cytat? " + "Zastanów się nad jego puentą, a kiedy będziesz gotów - zwróć sam cytat. " + "Nie pytaj mnie co o nim sądzę, ani nie używaj emotikonów (emoji). " + "Pamiętaj, że dobre cytaty są krótkie, zwięzłe."; if (includedCategory != null) { // Check if category to be included is present. Category? cat = await _db.Categories.FirstOrDefaultAsync(c => c.Id == includedCategory.Value); // It isn't? if (cat == null) return null; // It is? _prompt += $" Niech należy on do kategorii o nazwie \"{cat.Name}\" ({cat.Description})."; } // Sanity check if (includeCategorySample != null && includeCategorySample == true) { if (includedCategory == null) { // Can't append something that we're not given. return null; } else { // Try to find the category in question. Category? cat = await _db.Categories.FirstOrDefaultAsync(c => c.Id == includedCategory.Value); // Failed? if (cat == null) { return null; } else { IQueryable query = _db.Quotes .Include(q => q.QuoteCategories!) .Where(q => q.QuoteCategories .Any(qc => qc.Category == cat) ); int totalQuotes = await query.CountAsync(); if (totalQuotes > 0) { Random random = new(); int skip = random.Next(0, totalQuotes); Quote? quote = await query .Skip(skip) .Take(1) .FirstOrDefaultAsync(); if (quote != null) { _prompt += $" Przykładowy cytat z tej kategorii brzmi: \"{quote.Text} ~ {quote.Author}\".\n"; } } } } } List> promptMessages = [ new() { { "role", "user" }, {"content", _prompt } } ]; // Will throw error if not present string apiUrl = _appsettings.GetSection("LlmIntegration")["ApiUrl"] + "/chat/completions" ?? throw new MissingFieldException("API URL missing in LlmIntegration section of appsettings.json!"); string apiKey = _appsettings.GetSection("LlmIntegration")["ApiKey"] ?? throw new MissingFieldException("API key missing in LlmIntegration section of appsettings.json!"); using (var client = new HttpClient()) { // Not the best practice if we want reusable connections // https://stackoverflow.com/a/40707446 client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}"); var json = JsonConvert.SerializeObject(new { model = _model, temperature = _temp, max_tokens = (includeCategorySample ?? false) ? 2000 : 1000, messages = promptMessages }); var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await client.PostAsync(apiUrl, content); if (response.IsSuccessStatusCode) { string receivedResponse = await response.Content.ReadAsStringAsync(); return JObject.Parse(receivedResponse); } else { // Handle the error Console.WriteLine($"[QuotifyBE] Error: response status code from API was {response.StatusCode}."); return null; } } } }