diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 295f0c1..933d673 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -2,24 +2,27 @@ name: .NET on: push: - branches: [ "master" ] + branches: ["master"] pull_request: - branches: [ "master" ] + branches: ["master"] jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v2 - with: - dotnet-version: 6.0.x - - name: Restore dependencies - run: dotnet restore Huppy/Huppy.sln - - name: Build - run: dotnet build Huppy/Huppy.sln --no-restore - - name: Test - run: dotnet test Huppy/Huppy.sln --no-build --verbosity normal + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v2 + with: + dotnet-version: 7.0.x + - name: Restore dependencies Main + run: dotnet restore src/Main.sln + - name: Restore dependencies Huppy + run: dotnet restore src/Huppy/Huppy.sln + - name: Build + run: dotnet build src/Main.sln --no-restore + - name: Build Huppy + run: dotnet build src/Huppy/Huppy.sln --no-restore + - name: Test + run: dotnet test src/Main.sln --no-build --verbosity normal diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IReminderService.cs b/Huppy/Huppy.Core/Interfaces/IServices/IReminderService.cs deleted file mode 100644 index bdea92d..0000000 --- a/Huppy/Huppy.Core/Interfaces/IServices/IReminderService.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Discord; -using Huppy.Core.Models; - -namespace Huppy.Core.Interfaces.IServices -{ - public interface IReminderService - { - TimeSpan FetchReminderFrequency { get; } - Task RegisterFreshReminders(); - Task> GetUserRemindersAsync(ulong userId); - Task AddReminder(DateTime date, ulong userId, string message); - Task AddReminder(DateTime date, IUser user, string message); - Task RemoveReminder(Reminder reminder); - Task RemoveReminderRange(string[] ids); - Task RemoveReminderRange(int[] ids); - } -} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Interfaces/IServices/ITicketService.cs b/Huppy/Huppy.Core/Interfaces/IServices/ITicketService.cs deleted file mode 100644 index 6465ac8..0000000 --- a/Huppy/Huppy.Core/Interfaces/IServices/ITicketService.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Discord; -using Huppy.Core.Models; - -namespace Huppy.Core.Interfaces.IServices -{ - public interface ITicketService - { - Task GetCountAsync(ulong userId); - Task> GetTicketsAsync(); - Task> GetTicketsAsync(ulong userId); - Task> GetPaginatedTickets(int skip, int take); - Task> GetPaginatedTickets(ulong userId, int skip, int take); - Task GetTicketAsync(string ticketId, ulong userId); - Task AddTicketAsync(IUser user, string topic, string description); - Task RemoveTicketAsync(string ticketId); - Task UpdateTicketAsync(string ticketId, string description); - Task CloseTicket(string ticketId, string answer); - } -} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Services/GPT/GPTService.cs b/Huppy/Huppy.Core/Services/GPT/GPTService.cs deleted file mode 100644 index 3376425..0000000 --- a/Huppy/Huppy.Core/Services/GPT/GPTService.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Net.Http.Json; -using Huppy.Core.Entities; -using Huppy.Core.Interfaces.IServices; -using Huppy.Kernel; -using Huppy.Kernel.Constants; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Logging; - -namespace Huppy.Core.Services.GPT -{ - public class GPTService : IGPTService - { - private readonly IHttpClientFactory _clientFactory; - private readonly ILogger _logger; - private readonly AppSettings _settings; - public GPTService(IHttpClientFactory clientFactory, ILogger logger, AppSettings settings) - { - _clientFactory = clientFactory; - _logger = logger; - _settings = settings; - } - - public async Task GetEngines() - { - var client = _clientFactory.CreateClient("GPT"); - var result = await client.GetAsync("https://api.openai.com/v1/engines"); - - _logger.LogInformation("{response}", await result.Content.ReadAsStringAsync()); - } - - public async Task DavinciCompletion(string prompt) - { - if (string.IsNullOrEmpty(prompt)) - throw new Exception("Prompt for GPT was empty"); - - var aiContext = _settings?.GPT?.AiContextMessage; - - if (string.IsNullOrEmpty(aiContext)) aiContext = ""; - if (!(prompt.EndsWith('?') || prompt.EndsWith('.'))) prompt += '.'; - - aiContext += prompt + "\n[Huppy]:"; - - GPTRequest model = new() - { - MaxTokens = 200, - Prompt = aiContext, - Temperature = 0.6, - FrequencyPenalty = 0.5, - N = 1 - }; - - var client = _clientFactory.CreateClient("GPT"); - - var response = await client.PostAsJsonAsync(GPTEndpoints.TextDavinciCompletions, model); - - if (response.IsSuccessStatusCode) - { - var result = await response.Content!.ReadFromJsonAsync(); - return result!.Choices!.First()!.Text!; - } - else - { - var failedResponse = await response.Content.ReadAsStringAsync(); - _logger.LogError("{response}", failedResponse); - - throw new Exception("GPT request wasn't successful"); - } - } - } -} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Services/News/NewsApiService.cs b/Huppy/Huppy.Core/Services/News/NewsApiService.cs deleted file mode 100644 index e8dbd83..0000000 --- a/Huppy/Huppy.Core/Services/News/NewsApiService.cs +++ /dev/null @@ -1,118 +0,0 @@ -using System.Net.Http.Json; -using System.Text; -using Discord; -using Discord.WebSocket; -using Huppy.Core.Entities; -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Interfaces.IServices; -using Huppy.Core.Models; -using Huppy.Kernel.Constants; -using Microsoft.Extensions.Logging; - -namespace Huppy.Core.Services.News; - -/// -/// No Longer supported -/// -[Obsolete("No longer supported")] -public class NewsApiService : INewsApiService -{ - private readonly ILogger _logger; - private readonly IHttpClientFactory _clientFactory; - private readonly IServerRepository _serverRepository; - private readonly DiscordShardedClient _client; - public NewsApiService(ILogger logger, IHttpClientFactory clientFactory, IServerRepository serverRepository, DiscordShardedClient client) - { - _logger = logger; - _clientFactory = clientFactory; - _serverRepository = serverRepository; - _client = client; - } - - public async Task GetNews() - { - var fromTime = DateTime.UtcNow.AddMinutes(-180).ToString("o"); - var toTime = DateTime.UtcNow.ToString("o"); - - var client = _clientFactory.CreateClient("News"); - var response = await client.GetAsync($"everything?q=\"war\"+\"US\"+\"Russia\"+\"ukraine\"&SortBy=popularity&from={fromTime}&to={toTime}"); - - if (response.IsSuccessStatusCode) - { - var result = await response.Content!.ReadFromJsonAsync(); - return result!; - } - else - { - var failedResponse = await response.Content.ReadAsStringAsync(); - _logger.LogError(failedResponse); - - throw new Exception("News request wasn't successful"); - } - } - - // TODO better handler for errors - public async Task PostNews() - { - try - { - // var servers = (await _serverRepository.GetAll()).Where(en => en.AreNewsEnabled); - List servers = new(); - if (servers.Any()) - { - var news = (await GetNews()).Articles!.Take(5); - - StringBuilder sb = new(); - int count = 1; - foreach (var article in news) - { - sb.AppendLine($"**{count}. {article.Title?.Replace("*", string.Empty)}**\n"); - sb.AppendLine($"> {article.Description?.Replace("*", string.Empty)}\n"); - sb.AppendLine($"*{article.Author?.Replace("*", string.Empty)} - {article.Source!.Name?.Replace("*", string.Empty)}*"); - sb.AppendLine($"{article.Url}\n"); - count++; - } - - var embed = new EmbedBuilder().WithTitle("✨ Most recent news ✨") - .WithColor(Color.Teal) - .WithDescription(sb.ToString()) - .WithThumbnailUrl(Icons.Huppy1) - .WithCurrentTimestamp() - .Build(); - - foreach (var server in servers) - { - var guild = _client.GetGuild(server.Id); - - if (guild is null) - { - _logger.LogWarning("Didn't find server with ID {ServerID}, no news sent", server.Id); - - server.UseGreet = false; - - // TODO: consider fire and forget - await _serverRepository.UpdateAsync(server); - await _serverRepository.SaveChangesAsync(); - continue; - } - - ISocketMessageChannel? channel = default; - if (server!.Rooms is not null && server!.Rooms.GreetingRoom > 0) - channel = guild.GetChannel(server.Rooms.GreetingRoom) as ISocketMessageChannel; - - channel ??= guild.DefaultChannel; - - await channel.SendMessageAsync(null, false, embed); - } - } - else - { - _logger.LogWarning("No servers use News API"); - } - } - catch (Exception e) - { - _logger.LogError("News Error {message}\n{stack}", e.Message, e.StackTrace); - } - } -} diff --git a/Huppy/Huppy.Core/Services/Reminder/ReminderService.cs b/Huppy/Huppy.Core/Services/Reminder/ReminderService.cs deleted file mode 100644 index 2120744..0000000 --- a/Huppy/Huppy.Core/Services/Reminder/ReminderService.cs +++ /dev/null @@ -1,148 +0,0 @@ -using Discord; -using Discord.Interactions; -using Discord.WebSocket; -using Huppy.Core.Entities; -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Interfaces.IServices; -using Huppy.Kernel.Constants; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; - -namespace Huppy.Core.Services.Reminder; - -// public record ReminderInput(IUser User, string Message); - -public class ReminderService : IReminderService -{ - private readonly ILogger _logger; - private readonly IEventLoopService _eventService; - private readonly IReminderRepository _reminderRepository; - private readonly InteractionService _interactionService; - private DateTime FetchingDate => DateTime.UtcNow + FetchReminderFrequency; - public TimeSpan FetchReminderFrequency { get; } = new(1, 0, 0); - public ReminderService(IEventLoopService eventService, ILogger logger, DiscordShardedClient discord, IReminderRepository reminderRepository, InteractionService interactionService, ITimedEventsService timedEventsService) - { - _eventService = eventService; - _logger = logger; - _reminderRepository = reminderRepository; - _interactionService = interactionService; - } - - public async Task RegisterFreshReminders() - { - _logger.LogInformation("Registering fresh bulk of reminders"); - - // fetch reminders before fetchPeriod date - var remindersQueryable = await _reminderRepository.GetAllAsync(); - - var reminders = await remindersQueryable - .Where(reminder => reminder.RemindDate < FetchingDate) - .ToListAsync(); - - if (!reminders.Any()) return; - - // start adding reminder in async parallel manner - var jobs = reminders.Select(reminder => Task.Run(async () => - { - // fetch user - var user = await GetUser(reminder.UserId); - - // add reminder - ReminderInput reminderInput = new() { User = user, Message = reminder.Message }; - await _eventService.AddEvent(reminder.RemindDate, reminder.Id.ToString(), reminderInput, async (input) => - { - if (input is ReminderInput data) - { - await StandardReminder(data.User, data.Message!); - } - }); - })).ToList(); - - await Task.WhenAll(jobs); - - _logger.LogInformation("Enqueued {count} of reminders to execute until {time}", reminders.Count, FetchingDate); - } - - public async Task> GetUserRemindersAsync(ulong userId) - { - var reminders = await _reminderRepository.GetAllAsync(); - - return await reminders.Where(reminder => reminder.UserId == userId) - .ToListAsync(); - } - - public async Task AddReminder(DateTime date, ulong userId, string message) - { - await AddReminder(date, await GetUser(userId), message); - } - - public async Task AddReminder(DateTime date, IUser user, string message) - { - date = date.ToUniversalTime(); - Models.Reminder reminder = new() - { - Message = message, - RemindDate = date, - UserId = user.Id - }; - - var result = await _reminderRepository.AddAsync(reminder); - await _reminderRepository.SaveChangesAsync(); - - if (!result) throw new Exception("Failed to create reminder"); - - ReminderInput reminderInput = new() { User = user, Message = reminder.Message }; - - _logger.LogInformation("Added reminder for [{user}] at [{date}] UTC", user.Username, reminder.RemindDate); - - if (date < FetchingDate) - { - await _eventService.AddEvent(date, reminder.Id.ToString()!, reminderInput, async (input) => - { - if (input is ReminderInput data) - { - data.Message ??= ""; - await StandardReminder(data.User, data.Message); - } - }); - } - } - - public async Task RemoveReminder(Models.Reminder reminder) - { - await _reminderRepository.RemoveAsync(reminder); - await _reminderRepository.SaveChangesAsync(); - await _eventService.Remove(reminder.RemindDate, reminder.Id.ToString()); - } - - public async Task RemoveReminderRange(string[] ids) - { - int[] castedIds = ids.Select(int.Parse).ToArray(); - await RemoveReminderRange(castedIds); - } - - public async Task RemoveReminderRange(int[] ids) - { - if (!(ids.Length > 0)) return; - - await _reminderRepository.RemoveRangeAsync(ids); - await _reminderRepository.SaveChangesAsync(); - } - - private async Task StandardReminder(IUser user, string message) - { - var embed = new EmbedBuilder() - .WithTitle("Your reminder") - .WithColor(Color.Teal) - .WithThumbnailUrl(Icons.Huppy1) - .WithDescription(message) - .WithCurrentTimestamp() - .Build(); - - await user.SendMessageAsync("", false, embed); - - _logger.LogInformation("Sent reminder to [{user}]", user.Username); - } - - private async Task GetUser(ulong userId) => await _interactionService.RestClient.GetUserAsync(userId); -} diff --git a/Huppy/Huppy.Core/Services/Ticket/TicketService.cs b/Huppy/Huppy.Core/Services/Ticket/TicketService.cs deleted file mode 100644 index cd9d2ec..0000000 --- a/Huppy/Huppy.Core/Services/Ticket/TicketService.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Discord; -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Interfaces.IServices; -using Huppy.Core.Models; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Internal; - -namespace Huppy.Core.Services.Ticket; - -public class TicketService : ITicketService -{ - private readonly ITicketRepository _ticketRepository; - public TicketService(ITicketRepository ticketRepository) - { - _ticketRepository = ticketRepository; - } - - public async Task GetCountAsync(ulong userId) - { - // EFC lazy loading, DB is not called here - var tickets = await _ticketRepository.GetAllAsync(); - - // modify database query - return await tickets.Where(ticket => ticket.UserId == userId).CountAsync(); - } - - public async Task> GetTicketsAsync() - { - return await _ticketRepository.GetAllAsync(); - } - - public async Task> GetTicketsAsync(ulong userId) - { - var tickets = await _ticketRepository.GetAllAsync(); - return tickets.Where(ticket => ticket.UserId == userId); - } - - public async Task> GetPaginatedTickets(int skip, int take) - { - var tickets = (await _ticketRepository.GetAllAsync()) - .Include(ticket => ticket.User) - .OrderBy(ticket => ticket.IsClosed) - .ThenByDescending(ticket => ticket.CreatedDate) - .Skip(skip) - .Take(take) - .ToList(); - - return tickets; - } - - public async Task> GetPaginatedTickets(ulong userId, int skip, int take) - { - var tickets = (await _ticketRepository.GetAllAsync()) - .Where(ticket => ticket.UserId == userId) - .OrderBy(ticket => ticket.IsClosed) - .ThenByDescending(ticket => ticket.CreatedDate) - .Skip(skip) - .Take(take) - .ToList(); - - return tickets; - } - - public async Task GetTicketAsync(string ticketId, ulong userId) - { - var tickets = await _ticketRepository.GetAllAsync(); - - return await tickets.FirstOrDefaultAsync(ticket => ticket.Id == ticketId && ticket.UserId == userId); - } - - public async Task AddTicketAsync(IUser user, string topic, string description) - { - if (string.IsNullOrEmpty(description)) throw new ArgumentException("Ticket description cannot be null"); - - Models.Ticket ticket = new() - { - Id = Guid.NewGuid().ToString(), - Topic = topic, - Description = description, - CreatedDate = DateTime.UtcNow, - TicketAnswer = null, - ClosedDate = null, - IsClosed = false, - UserId = user.Id, - }; - - await _ticketRepository.AddAsync(ticket); - await _ticketRepository.SaveChangesAsync(); - - return ticket; - } - - public async Task RemoveTicketAsync(string ticketId) - { - if (string.IsNullOrEmpty(ticketId)) throw new ArgumentException("Ticket cannot be null or empty"); - - await _ticketRepository.RemoveAsync(ticketId); - await _ticketRepository.SaveChangesAsync(); - } - - public async Task UpdateTicketAsync(string ticketId, string description) - { - if (string.IsNullOrEmpty(ticketId) || string.IsNullOrEmpty(description)) - throw new ArgumentException("Both ticked ID and ticket description cannot be null or empty"); - - var ticket = await _ticketRepository.GetAsync(ticketId); - - if (ticket is null) throw new Exception("Ticket doesn't exist"); - - ticket.Description = description; - - await _ticketRepository.UpdateAsync(ticket); - await _ticketRepository.SaveChangesAsync(); - } - - public async Task CloseTicket(string ticketId, string answer) - { - if (string.IsNullOrEmpty(ticketId)) - throw new ArgumentException("Ticked ID cannot be empty"); - - var ticket = await _ticketRepository.GetAsync(ticketId); - - if (ticket is null) throw new Exception("Ticket doesn't exist"); - - ticket.IsClosed = true; - ticket.TicketAnswer = answer; - ticket.ClosedDate = DateTime.UtcNow; - - await _ticketRepository.UpdateAsync(ticket); - await _ticketRepository.SaveChangesAsync(); - } -} diff --git a/Huppy/Huppy.Infrastructure/Repositories/CommandLogRepository.cs b/Huppy/Huppy.Infrastructure/Repositories/CommandLogRepository.cs deleted file mode 100644 index 59e0c78..0000000 --- a/Huppy/Huppy.Infrastructure/Repositories/CommandLogRepository.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Huppy.Core.Entities; -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Models; -using Huppy.Kernel.Abstraction; -using Microsoft.EntityFrameworkCore; - -namespace Huppy.Infrastructure.Repositories -{ - public class CommandLogRepository : BaseRepository, ICommandLogRepository - { - public CommandLogRepository(HuppyDbContext context) : base(context) { } - - public async Task GetCount() => await _context.CommandLogs.CountAsync(); - - public async Task> GetAiUsage() - { - Dictionary result = new(); - - var commandLogs = await _context.CommandLogs?.Include(e => e.User) - .ToListAsync()!; - - var uniqueUsers = commandLogs.GroupBy(e => e.UserId) - .Select(e => e.First()) - .ToList(); - - foreach (var user in uniqueUsers) - { - result.TryAdd(user.UserId, new AiUser - { - Username = user.User!.Username, - Count = commandLogs.Where(x => x.UserId == user.UserId) - .Count() - }); - } - - return result; - } - } -} \ No newline at end of file diff --git a/Huppy/Huppy.Infrastructure/Repositories/ReminderRepository.cs b/Huppy/Huppy.Infrastructure/Repositories/ReminderRepository.cs deleted file mode 100644 index 4a860b8..0000000 --- a/Huppy/Huppy.Infrastructure/Repositories/ReminderRepository.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Models; -using Huppy.Kernel.Abstraction; -using Microsoft.EntityFrameworkCore; - -namespace Huppy.Infrastructure.Repositories -{ - public class ReminderRepository : BaseRepository, IReminderRepository - { - public ReminderRepository(HuppyDbContext context) : base(context) { } - - public async Task GetAsync(ulong userId, int id) - { - return await _context.Reminders.FirstOrDefaultAsync(e => e.Id == id && e.UserId == userId); - } - - public async Task RemoveRangeAsync(ICollection reminderIds) - { - if (reminderIds is null) return; - - var reminders = await _context.Reminders - .Where(reminder => reminderIds.Contains(reminder.Id)) - .ToListAsync(); - - _context.Reminders.RemoveRange(reminders); - await _context.SaveChangesAsync(); - } - } -} \ No newline at end of file diff --git a/Huppy/Huppy.Infrastructure/Repositories/ServerRepository.cs b/Huppy/Huppy.Infrastructure/Repositories/ServerRepository.cs deleted file mode 100644 index 0decb46..0000000 --- a/Huppy/Huppy.Infrastructure/Repositories/ServerRepository.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Discord.Interactions; -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Models; -using Huppy.Kernel.Abstraction; -using Microsoft.EntityFrameworkCore; - -namespace Huppy.Infrastructure.Repositories -{ - public class ServerRepository : BaseRepository, IServerRepository - { - public ServerRepository(HuppyDbContext context) : base(context) { } - - public override async Task GetAsync(ulong id) - { - return await _context.Servers.Include(e => e.Rooms).FirstOrDefaultAsync(entry => entry.Id == id); - } - - public async Task GetOrCreateAsync(ShardedInteractionContext DiscordContext) - { - var server = await GetAsync(DiscordContext.Guild.Id); - - if (server is not null) - { - if (server.Rooms is null) - { - server.Rooms = new() - { - OutputRoom = DiscordContext.Guild.DefaultChannel.Id, - GreetingRoom = default - }; - - await base.UpdateAsync(server); - await base.SaveChangesAsync(); - } - - return server; - } - - server = new() - { - Id = DiscordContext.Guild.Id, - GreetMessage = "Welcome {username}!", - Rooms = new() - { - OutputRoom = DiscordContext.Guild.DefaultChannel.Id, - GreetingRoom = 0 - }, - ServerName = DiscordContext.Guild.Name, - RoleID = 0, - UseGreet = false, - }; - - await base.AddAsync(server); - await base.SaveChangesAsync(); - - return server; - } - } -} \ No newline at end of file diff --git a/Huppy/Huppy.Infrastructure/Repositories/TicketRepository.cs b/Huppy/Huppy.Infrastructure/Repositories/TicketRepository.cs deleted file mode 100644 index a516e0d..0000000 --- a/Huppy/Huppy.Infrastructure/Repositories/TicketRepository.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Core.Models; -using Huppy.Kernel.Abstraction; - -namespace Huppy.Infrastructure.Repositories -{ - public class TicketRepository : BaseRepository, ITicketRepository - { - public TicketRepository(HuppyDbContext context) : base(context) { } - } -} \ No newline at end of file diff --git a/docs/Architecture-v1.excalidraw b/docs/Architecture-v1.excalidraw new file mode 100644 index 0000000..3f3a114 --- /dev/null +++ b/docs/Architecture-v1.excalidraw @@ -0,0 +1,3223 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "PCvs5x02hA8hhKD5JsYjM", + "type": "rectangle", + "x": 1120, + "y": 160, + "width": 1940, + "height": 1000, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 832588035, + "version": 149, + "versionNonce": 881239981, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "5oTBA41cewM2NEBIi8wR8" + } + ], + "updated": 1669671398995, + "link": null, + "locked": false + }, + { + "id": "5oTBA41cewM2NEBIi8wR8", + "type": "text", + "x": 1125, + "y": 165, + "width": 150, + "height": 43, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 949396803, + "version": 101, + "versionNonce": 979023267, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398995, + "link": null, + "locked": false, + "text": "Backend", + "fontSize": 36, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 34, + "containerId": "PCvs5x02hA8hhKD5JsYjM", + "originalText": "Backend" + }, + { + "type": "rectangle", + "version": 590, + "versionNonce": 1276665965, + "isDeleted": false, + "id": "-4-fJ9r-XcDcrRJ6r9qN7", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 220, + "y": 160, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 453.71077855124776, + "height": 1000, + "seed": 72939811, + "groupIds": [ + "tftUbuNp9GMZlFv1Onq-g" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "QrfX5PCIjVcCqzcOQgd1d" + } + ], + "updated": 1669671398995, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 764, + "versionNonce": 1219415501, + "isDeleted": false, + "id": "QrfX5PCIjVcCqzcOQgd1d", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 225.95238095238096, + "y": 165.95238095238096, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 197, + "height": 40, + "seed": 680453581, + "groupIds": [ + "tftUbuNp9GMZlFv1Onq-g" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671593755, + "link": null, + "locked": false, + "fontSize": 33.333333333333336, + "fontFamily": 3, + "text": "Client App", + "baseline": 32, + "textAlign": "left", + "verticalAlign": "top", + "containerId": "-4-fJ9r-XcDcrRJ6r9qN7", + "originalText": "Client App" + }, + { + "type": "ellipse", + "version": 644, + "versionNonce": 208390861, + "isDeleted": false, + "id": "rT_BPbgWa9BS6PU_RtiGT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 311.81330864281585, + "y": 733.8331790175993, + "strokeColor": "#862e9c", + "backgroundColor": "transparent", + "width": 275.4399259284477, + "height": 226.0902725329341, + "seed": 1352926627, + "groupIds": [ + "tftUbuNp9GMZlFv1Onq-g" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "flpWdsyqY3bj-pvBrXsCw" + }, + { + "id": "ICG8ZiqnOrFrY772X9Nuz", + "type": "arrow" + }, + { + "id": "jlP71Ip3PnoGpmpruSKDB", + "type": "arrow" + }, + { + "id": "G8ZeIejRNB1PMDcK_i5TD", + "type": "arrow" + }, + { + "id": "BPTK1FDUmu_Eq8YGEPa3j", + "type": "arrow" + } + ], + "updated": 1669671398995, + "link": "", + "locked": false + }, + { + "type": "text", + "version": 799, + "versionNonce": 421844099, + "isDeleted": false, + "id": "flpWdsyqY3bj-pvBrXsCw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 379.89041446418264, + "y": 826.6402200459712, + "strokeColor": "#862e9c", + "backgroundColor": "transparent", + "width": 139.28571428571428, + "height": 40.476190476190474, + "seed": 108417987, + "groupIds": [ + "tftUbuNp9GMZlFv1Onq-g" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "fontSize": 33.333333333333336, + "fontFamily": 3, + "text": "Discord", + "baseline": 32.476190476190474, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rT_BPbgWa9BS6PU_RtiGT", + "originalText": "Discord" + }, + { + "type": "ellipse", + "version": 580, + "versionNonce": 396552493, + "isDeleted": false, + "id": "agBIEHOyI84t2bhBA0R5I", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 311.81330864281585, + "y": 320.67329012492775, + "strokeColor": "#862e9c", + "backgroundColor": "transparent", + "width": 273.14459321237723, + "height": 218.0566080266877, + "seed": 2093304611, + "groupIds": [ + "tftUbuNp9GMZlFv1Onq-g" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "fOa6pH1krqO5lv0kYfs18" + }, + { + "id": "3m-DVtnv6jVvWhtOxqU_H", + "type": "arrow" + }, + { + "id": "TbeVN9uac6-VGFAsSEXXh", + "type": "arrow" + } + ], + "updated": 1669671398995, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 755, + "versionNonce": 1463634979, + "isDeleted": false, + "id": "fOa6pH1krqO5lv0kYfs18", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 418.0284623918616, + "y": 409.4634989001764, + "strokeColor": "#862e9c", + "backgroundColor": "transparent", + "width": 60.714285714285715, + "height": 40.476190476190474, + "seed": 1389836909, + "groupIds": [ + "tftUbuNp9GMZlFv1Onq-g" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "fontSize": 33.333333333333336, + "fontFamily": 3, + "text": "Web", + "baseline": 32.476190476190474, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "agBIEHOyI84t2bhBA0R5I", + "originalText": "Web" + }, + { + "type": "rectangle", + "version": 258, + "versionNonce": 440086563, + "isDeleted": false, + "id": "sIeERuuA6I9AlZkx4goov", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1920, + "y": 200, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 1103, + "height": 920, + "seed": 383293315, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "ZKvG4-y1YsjER0pfUMcnw" + }, + { + "id": "pMFLxR2SpsMCJhqBJCs-v", + "type": "arrow" + } + ], + "updated": 1669672033614, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 187, + "versionNonce": 510236557, + "isDeleted": false, + "id": "ZKvG4-y1YsjER0pfUMcnw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1925, + "y": 205, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "width": 96, + "height": 24, + "seed": 1208623267, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669672033614, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "Main net", + "baseline": 19, + "textAlign": "left", + "verticalAlign": "top", + "containerId": "sIeERuuA6I9AlZkx4goov", + "originalText": "Main net" + }, + { + "type": "rectangle", + "version": 362, + "versionNonce": 594644173, + "isDeleted": false, + "id": "Da_PqOluFjN7is_eBy8il", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1200, + "y": 480, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 320, + "height": 144, + "seed": 2051751427, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "LLYIUavBw7FUNWkrfv39W" + }, + { + "id": "udhskxhKKGvsyJPNvaMb_", + "type": "arrow" + }, + { + "id": "pMFLxR2SpsMCJhqBJCs-v", + "type": "arrow" + }, + { + "id": "zNYs5DFsod6OMrUoptwVJ", + "type": "arrow" + }, + { + "id": "frCyG5-a_JX1gRjwRmOrN", + "type": "arrow" + } + ], + "updated": 1669671801037, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 310, + "versionNonce": 379965379, + "isDeleted": false, + "id": "LLYIUavBw7FUNWkrfv39W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1318, + "y": 540, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 84, + "height": 24, + "seed": 1621145315, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "Gateway", + "baseline": 19, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Da_PqOluFjN7is_eBy8il", + "originalText": "Gateway" + }, + { + "type": "rectangle", + "version": 372, + "versionNonce": 1857943395, + "isDeleted": false, + "id": "Blt38Ylikw787bxPy5Asx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1200, + "y": 280, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 332, + "height": 139, + "seed": 1388513539, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "lc7UXHk57Q_YExP5KYoVZ" + }, + { + "id": "fTtltAou3PInMElYv01Qc", + "type": "arrow" + }, + { + "id": "BjVr3L1HhvxKKuj4pWaN1", + "type": "arrow" + } + ], + "updated": 1669671898677, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 310, + "versionNonce": 1232920419, + "isDeleted": false, + "id": "lc7UXHk57Q_YExP5KYoVZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1300.5, + "y": 337.5, + "strokeColor": "#c92a2a", + "backgroundColor": "transparent", + "width": 131, + "height": 24, + "seed": 261761059, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "Nether Auth", + "baseline": 19, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Blt38Ylikw787bxPy5Asx", + "originalText": "Nether Auth" + }, + { + "type": "rectangle", + "version": 302, + "versionNonce": 12297411, + "isDeleted": false, + "id": "kTxypuW_b__Uu6pFiQzjx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1200, + "y": 680, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 326, + "height": 134.66665649414062, + "seed": 1904721837, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "036hM1GpuwNrBLC_CC1oe" + }, + { + "id": "ICG8ZiqnOrFrY772X9Nuz", + "type": "arrow" + }, + { + "id": "qBqhqQmYrMSbZD8TpU3X5", + "type": "arrow" + } + ], + "updated": 1669671636005, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 206, + "versionNonce": 1692443299, + "isDeleted": false, + "id": "036hM1GpuwNrBLC_CC1oe", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1332.5, + "y": 735.3333282470703, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 61, + "height": 24, + "seed": 1843751917, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "Huppy", + "baseline": 19, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "kTxypuW_b__Uu6pFiQzjx", + "originalText": "Huppy" + }, + { + "type": "line", + "version": 6126, + "versionNonce": 1211088803, + "isDeleted": false, + "id": "TW37RUUI2kXXoh7a3w48x", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2321.0031830838507, + "y": 769.3971028843206, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 80.53132429055069, + "height": 103.93845895928327, + "seed": 500039309, + "groupIds": [ + "q2fRtTduIqGa0H1lToNK0", + "Ffk6Czd3CPwdoxaxo15b3", + "IrxRav1QCC2p02IewhkZV" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669672025684, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.26555095108349375, + 78.55612392717138 + ], + [ + 0.012427161119890295, + 87.4993975093521 + ], + [ + 4.1475413077954775, + 91.36367543467536 + ], + [ + 18.5478699947732, + 94.63410581335458 + ], + [ + 42.88850462025491, + 95.65246897840512 + ], + [ + 66.14437140294662, + 94.02650301203627 + ], + [ + 78.50038465379497, + 90.1380348112513 + ], + [ + 80.24303312533527, + 86.86001532654419 + ], + [ + 80.48778179471857, + 79.65985098305549 + ], + [ + 80.29568254840036, + 6.590420330112938 + ], + [ + 79.86262918571221, + -0.3132945591808347 + ], + [ + 74.69170574260916, + -4.171826224919654 + ], + [ + 63.802844491730454, + -6.406483593881079 + ], + [ + 38.98860092227267, + -8.285989980878151 + ], + [ + 19.093870923498052, + -7.1652360902447 + ], + [ + 3.4468008917272406, + -3.3637756422714404 + ], + [ + -0.043542495832124266, + -0.047201529095363945 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 6848, + "versionNonce": 1667743757, + "isDeleted": false, + "id": "v5JoGrvXuccmaZlxPWbxI", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2321.2886857693857, + "y": 760, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 80.01478318796464, + "height": 16.182387923834753, + "seed": 1828764867, + "groupIds": [ + "q2fRtTduIqGa0H1lToNK0", + "Ffk6Czd3CPwdoxaxo15b3", + "IrxRav1QCC2p02IewhkZV" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "bxuMGTzXLn7H-uBCptINx" + }, + { + "id": "dUw9Vy5ik3Zl5f4NVukLy", + "type": "arrow" + } + ], + "updated": 1669672025684, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1253, + "versionNonce": 1536123757, + "isDeleted": false, + "id": "6A6pS6svCIaszNQhWE9H6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -234.60251946408698, + "y": 487.8408653501981, + "strokeColor": "#000000", + "backgroundColor": "white", + "width": 88.15974907939453, + "height": 48.901110817476656, + "seed": 701452621, + "groupIds": [ + "TSbGhIqn7OqPQsfyr8Vcs" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "fontSize": 38.73019912365716, + "fontFamily": 1, + "text": "User", + "baseline": 33.901110817476656, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "User" + }, + { + "type": "line", + "version": 1561, + "versionNonce": 1914476003, + "isDeleted": false, + "id": "dKgb5RSOA4oaUkOT6q1FD", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -240, + "y": 469.1478780255777, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 95.4107237560352, + "height": 84.96050946149714, + "seed": 444790275, + "groupIds": [ + "rRRH3oLWciUxqG7_xmtZB", + "TSbGhIqn7OqPQsfyr8Vcs" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 10.686001060675933, + -56.299132775690886 + ], + [ + 45.7971474028969, + -84.96050946149714 + ], + [ + 80.90829374511785, + -62.440856351220724 + ], + [ + 95.4107237560352, + -5.845951514512893 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 1300, + "versionNonce": 1007316429, + "isDeleted": false, + "id": "icUEAhoEMbU1KA_5Xq7hf", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": -215.88295513245936, + "y": 340.00000000000006, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 48.85029056309006, + "height": 42.74400424270395, + "seed": 2077335469, + "groupIds": [ + "rRRH3oLWciUxqG7_xmtZB", + "TSbGhIqn7OqPQsfyr8Vcs" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 4212, + "versionNonce": 7473069, + "isDeleted": false, + "id": "OX_YIuGK7pGJh2NQf_Ur9", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2721.2047413378464, + "y": 695.022420002493, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 222.51216030216648, + "height": 227.19766405240813, + "seed": 1840454733, + "groupIds": [ + "3TrpUvsAbqCn3cChtlICV" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669672144858, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.6149041836331945, + 57.308930761322515 + ], + [ + 5.68671529869968, + 89.60303723371372 + ], + [ + 43.06122292251399, + 92.58840224003772 + ], + [ + 119.70962958627535, + 93.22619433023539 + ], + [ + 193.66244048188872, + 92.95023277798248 + ], + [ + 219.1483395255385, + 83.22511633173754 + ], + [ + 221.76581905058535, + 50.365057740177065 + ], + [ + 221.0247629435176, + -3.0193852698614876 + ], + [ + 215.64021459212094, + -30.055082717648048 + ], + [ + 194.0914508975089, + -39.4928975237943 + ], + [ + 164.526186982679, + -39.902251203379265 + ], + [ + 132.7280981676696, + -43.355833118774534 + ], + [ + 129.77823500058892, + -84.77020281562191 + ], + [ + 127.5747130850894, + -125.43823126088822 + ], + [ + 108.43271467987832, + -133.97146972217277 + ], + [ + 87.74357707144006, + -125.79789900857591 + ], + [ + 86.20103146784382, + -83.82403644132297 + ], + [ + 83.83057064515287, + -43.887238837845715 + ], + [ + 63.84549313219016, + -37.844142717608555 + ], + [ + 44.22002761129336, + -44.561213981837795 + ], + [ + 42.414607871801536, + -84.03897197383267 + ], + [ + 40.651469288415285, + -124.15481424631301 + ], + [ + 20.229799415503297, + -132.17798270080908 + ], + [ + 1.3823860132669994, + -123.8483396841886 + ], + [ + -0.7463412515811136, + -70.90241890019773 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 1196, + "versionNonce": 2102744483, + "isDeleted": false, + "id": "irH2V7ME8WAGrAtgcChY3", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2860.128315161237, + "y": 712.4147841200821, + "strokeColor": "#000000", + "backgroundColor": "#ffff", + "width": 43.806885253897676, + "height": 43.80166515470782, + "seed": 1339382531, + "groupIds": [ + "3TrpUvsAbqCn3cChtlICV" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669672144858, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 4.4944360491763975, + -8.46242235294183 + ], + [ + 14.205183043239405, + -11.401102796215019 + ], + [ + 30.176469468476416, + -11.931778087236 + ], + [ + 40.99092961037577, + -7.526645017806548 + ], + [ + 43.76910340461082, + 4.564863810690971 + ], + [ + 43.806885253897676, + 17.050911604199182 + ], + [ + 41.863931903233976, + 28.486171894768987 + ], + [ + 31.716683762741958, + 31.71015632621033 + ], + [ + 14.012254037547915, + 31.86988706747182 + ], + [ + 3.2186946999200887, + 27.78421548056531 + ], + [ + 0.9903675999752919, + 16.42882056062621 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 947, + "versionNonce": 949358797, + "isDeleted": false, + "id": "ly2VFJCer1qkb4CiICOv1", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 900.7698075659112, + "y": 321.74926661886275, + "strokeColor": "#000000", + "backgroundColor": "#40c057", + "width": 157.62053952763247, + "height": 180.36428311277535, + "seed": 1171358861, + "groupIds": [ + "6UMGFcqTdi_mDm2WGgcbo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -79.7737204944253, + 45.87526331346678 + ], + [ + -79.7737204944252, + 133.31273099639924 + ], + [ + 0.38538029224368986, + 180.36428311277535 + ], + [ + 77.84681903320718, + 129.39176832003454 + ], + [ + 77.84681903320718, + 42.73849317237496 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "line", + "version": 2373, + "versionNonce": 1344925315, + "isDeleted": false, + "id": "rTfC2MCuIF_mvoAqLBrQ0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 874.5204581360309, + "y": 392.19601409890964, + "strokeColor": "#000000", + "backgroundColor": "#ffff", + "width": 82.35366697088277, + "height": 76.41186014431482, + "seed": 1566611139, + "groupIds": [ + "6UMGFcqTdi_mDm2WGgcbo" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.42058583758064405, + 26.700236188358204 + ], + [ + -0.621090909757515, + 55.80115150768735 + ], + [ + -13.85975694693417, + 54.45442906836227 + ], + [ + -16.363696279190936, + 12.881692897892055 + ], + [ + -12.138262210157398, + -19.556751944981738 + ], + [ + 3.4429894735535314, + -20.610708636627464 + ], + [ + 25.536913035409565, + 7.026377944304767 + ], + [ + 48.05142243484627, + 34.19503932895 + ], + [ + 50.790153459107415, + -18.268582655192525 + ], + [ + 63.4599304351324, + -20.14228344034047 + ], + [ + 65.98997069169184, + 13.818543290466081 + ], + [ + 62.63180922212235, + 52.93204718042952 + ], + [ + 43.64339852241379, + 55.27417316186446 + ], + [ + 19.249186689294916, + 27.16866138464529 + ], + [ + 0, + 0 + ] + ] + }, + { + "id": "O47Rl2ny-9GWkH7iUnHYD", + "type": "line", + "x": 2540.2731636038425, + "y": -160.66834298186004, + "width": 362.49996185302734, + "height": 3.3333587646484375, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 2031774509, + "version": 172, + "versionNonce": 311717677, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398995, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 362.49996185302734, + -3.3333587646484375 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "Wp0UklOccIKWXmAh0V70t", + "type": "text", + "x": 2680, + "y": -220, + "width": 68, + "height": 34, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1011121837, + "version": 110, + "versionNonce": 1653548579, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398995, + "link": null, + "locked": false, + "text": "gRPC", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 27, + "containerId": null, + "originalText": "gRPC" + }, + { + "id": "V4VXeXUg8jIzCAQjG-zGz", + "type": "line", + "x": 2542.7020890894532, + "y": -79.8808427195996, + "width": 362.5, + "height": 2.5, + "angle": 0, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 24730605, + "version": 121, + "versionNonce": 771228045, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398995, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 362.5, + -2.5 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "pe5eIFrGfGDotw0SdzWsA", + "type": "text", + "x": 2680, + "y": -140, + "width": 68, + "height": 34, + "angle": 0, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1545489837, + "version": 83, + "versionNonce": 1319922115, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398995, + "link": null, + "locked": false, + "text": "HTTP", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 27, + "containerId": null, + "originalText": "HTTP" + }, + { + "type": "line", + "version": 132, + "versionNonce": 268060653, + "isDeleted": false, + "id": "zz8t9X9tgEYPvHAzLQWlp", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2541.1346378345042, + "y": -1.315683611109847, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "width": 362.5, + "height": 2.5, + "seed": 1597769379, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669671398995, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 362.5, + -2.5 + ] + ] + }, + { + "id": "10DS1H3RhkJBvZL32yITP", + "type": "text", + "x": 2680, + "y": -60, + "width": 51, + "height": 34, + "angle": 0, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1487567693, + "version": 83, + "versionNonce": 966801763, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398995, + "link": null, + "locked": false, + "text": "WSS", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 27, + "containerId": null, + "originalText": "WSS" + }, + { + "type": "rectangle", + "version": 330, + "versionNonce": 1335035245, + "isDeleted": false, + "id": "d_UeujwAgOT55YLJ46fZG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1200, + "y": 880, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 326, + "height": 134.66665649414062, + "seed": 410062509, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "Kx5ZmbLi-7ChdkkiZwGIi", + "type": "text" + }, + { + "type": "text", + "id": "Kx5ZmbLi-7ChdkkiZwGIi" + }, + { + "id": "jlP71Ip3PnoGpmpruSKDB", + "type": "arrow" + }, + { + "id": "0dxfJI7r46Y6aANYWfsTe", + "type": "arrow" + } + ], + "updated": 1669671677386, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 240, + "versionNonce": 549000451, + "isDeleted": false, + "id": "Kx5ZmbLi-7ChdkkiZwGIi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1321, + "y": 935.3333282470703, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 84, + "height": 24, + "seed": 770588323, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398996, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 3, + "text": "nth bot", + "baseline": 19, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "d_UeujwAgOT55YLJ46fZG", + "originalText": "nth bot" + }, + { + "type": "line", + "version": 135, + "versionNonce": 1400208547, + "isDeleted": false, + "id": "eq9kEEi0MX0IOoOtTmx9O", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2541.0336507935076, + "y": 78.68431638889015, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 362.5, + "height": 2.5, + "seed": 1997215107, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669671398996, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 362.5, + -2.5 + ] + ] + }, + { + "type": "text", + "version": 91, + "versionNonce": 1713918733, + "isDeleted": false, + "id": "rpydPPtaAW4ghlNh4lGUs", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2679.8990129590034, + "y": 20, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 84, + "height": 34, + "seed": 587020333, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671398996, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "Mixed", + "baseline": 27, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Mixed" + }, + { + "id": "3m-DVtnv6jVvWhtOxqU_H", + "type": "arrow", + "x": 602.3253708592057, + "y": 420.1940627741387, + "width": 197.6746291407943, + "height": 20, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1469855949, + "version": 47, + "versionNonce": 428380131, + "isDeleted": false, + "boundElements": null, + "updated": 1669671611924, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 100, + -20 + ], + [ + 197.6746291407943, + -0.194062774138672 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "agBIEHOyI84t2bhBA0R5I", + "focus": 0.18933141149240867, + "gap": 17.79919646174048 + }, + "endBinding": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "fTtltAou3PInMElYv01Qc", + "type": "arrow", + "x": 1200, + "y": 340, + "width": 200, + "height": 60, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 814679277, + "version": 79, + "versionNonce": 1252286861, + "isDeleted": false, + "boundElements": null, + "updated": 1669671606926, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -100, + 20 + ], + [ + -200, + 60 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Blt38Ylikw787bxPy5Asx", + "focus": 0.41577409931840315, + "gap": 1 + }, + "endBinding": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "udhskxhKKGvsyJPNvaMb_", + "type": "arrow", + "x": 1200, + "y": 560, + "width": 200, + "height": 120, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 278872461, + "version": 65, + "versionNonce": 911823021, + "isDeleted": false, + "boundElements": null, + "updated": 1669671609629, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -120, + -60 + ], + [ + -200, + -120 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Da_PqOluFjN7is_eBy8il", + "focus": -0.5789473684210525, + "gap": 1 + }, + "endBinding": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "ICG8ZiqnOrFrY772X9Nuz", + "type": "arrow", + "x": 600, + "y": 840, + "width": 580, + "height": 100, + "angle": 0, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1879684461, + "version": 162, + "versionNonce": 524352387, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398996, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 300, + -60 + ], + [ + 580, + -100 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "rT_BPbgWa9BS6PU_RtiGT", + "focus": 0.19952348274956327, + "gap": 12.970603212881286 + }, + "endBinding": { + "elementId": "kTxypuW_b__Uu6pFiQzjx", + "focus": 0.3694166750638121, + "gap": 20 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "jlP71Ip3PnoGpmpruSKDB", + "type": "arrow", + "x": 600, + "y": 840, + "width": 580, + "height": 120, + "angle": 0, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1169233923, + "version": 72, + "versionNonce": 771797549, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398996, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 300, + 80 + ], + [ + 580, + 120 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "rT_BPbgWa9BS6PU_RtiGT", + "focus": -0.3954430083089716, + "gap": 12.970603212881286 + }, + "endBinding": { + "elementId": "d_UeujwAgOT55YLJ46fZG", + "focus": -0.42827123098046566, + "gap": 20 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "G8ZeIejRNB1PMDcK_i5TD", + "type": "arrow", + "x": -119.99999999999999, + "y": 420, + "width": 420, + "height": 440, + "angle": 0, + "strokeColor": "#e67700", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1437024099, + "version": 142, + "versionNonce": 893162275, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398996, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 99.99999999999999, + 40 + ], + [ + 140, + 400 + ], + [ + 420, + 440 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": { + "elementId": "rT_BPbgWa9BS6PU_RtiGT", + "focus": -0.3005251408952414, + "gap": 12.632180755695003 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "TbeVN9uac6-VGFAsSEXXh", + "type": "arrow", + "x": -120, + "y": 420, + "width": 420, + "height": 0, + "angle": 0, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1651576525, + "version": 93, + "versionNonce": 954459277, + "isDeleted": false, + "boundElements": null, + "updated": 1669671398996, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 240, + 0 + ], + [ + 420, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": null, + "endBinding": { + "elementId": "agBIEHOyI84t2bhBA0R5I", + "focus": 0.08898234477796012, + "gap": 12.287873902050706 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "BPTK1FDUmu_Eq8YGEPa3j", + "type": "arrow", + "x": 300, + "y": 900, + "width": 419.4637558681145, + "height": 478.4795097565278, + "angle": 0, + "strokeColor": "#5c940d", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 903178595, + "version": 143, + "versionNonce": 553012717, + "isDeleted": false, + "boundElements": null, + "updated": 1669671406954, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -160, + 0 + ], + [ + -280, + -20 + ], + [ + -340, + -120 + ], + [ + -359.4637558681145, + -418.4795097565278 + ], + [ + -419.4637558681145, + -478.4795097565278 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "rT_BPbgWa9BS6PU_RtiGT", + "focus": -0.46991570332328747, + "gap": 24.354400236743174 + }, + "endBinding": null, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "-GOyfGIP2WbWTrml7lzS9", + "type": "rectangle", + "x": 1980, + "y": 720, + "width": 260, + "height": 44, + "angle": 0, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 1189949187, + "version": 127, + "versionNonce": 1680446243, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "VfPxPsqNXihi_08niLEr3" + }, + { + "id": "qBqhqQmYrMSbZD8TpU3X5", + "type": "arrow" + }, + { + "id": "zNYs5DFsod6OMrUoptwVJ", + "type": "arrow" + }, + { + "id": "SNVuq9FsfQoESebS5T57A", + "type": "arrow" + }, + { + "id": "dUw9Vy5ik3Zl5f4NVukLy", + "type": "arrow" + }, + { + "id": "Ius4Fnu7nZujALP8n_I5-", + "type": "arrow" + } + ], + "updated": 1669672060962, + "link": null, + "locked": false + }, + { + "id": "VfPxPsqNXihi_08niLEr3", + "type": "text", + "x": 2035, + "y": 725, + "width": 150, + "height": 34, + "angle": 0, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 301556483, + "version": 96, + "versionNonce": 1536399203, + "isDeleted": false, + "boundElements": null, + "updated": 1669671981699, + "link": null, + "locked": false, + "text": "Huppy API", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 27, + "containerId": "-GOyfGIP2WbWTrml7lzS9", + "originalText": "Huppy API" + }, + { + "type": "rectangle", + "version": 212, + "versionNonce": 30233005, + "isDeleted": false, + "id": "qHH-cRZS4we7YwkiiB0eG", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1980, + "y": 900, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 260, + "height": 44, + "seed": 442315053, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "tAFselDWlVpsQ7KveyPA2", + "type": "text" + }, + { + "type": "text", + "id": "tAFselDWlVpsQ7KveyPA2" + }, + { + "id": "0dxfJI7r46Y6aANYWfsTe", + "type": "arrow" + }, + { + "id": "frCyG5-a_JX1gRjwRmOrN", + "type": "arrow" + }, + { + "id": "30VBRBB9yUFKojrtqDJ8A", + "type": "arrow" + }, + { + "id": "WYCb79CVNWcXcpwWm2gUp", + "type": "arrow" + } + ], + "updated": 1669672094868, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 183, + "versionNonce": 1164544163, + "isDeleted": false, + "id": "tAFselDWlVpsQ7KveyPA2", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2019, + "y": 905, + "strokeColor": "#087f5b", + "backgroundColor": "transparent", + "width": 182, + "height": 34, + "seed": 1210156451, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1669671995801, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "nth bot API", + "baseline": 27, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "qHH-cRZS4we7YwkiiB0eG", + "originalText": "nth bot API" + }, + { + "id": "qBqhqQmYrMSbZD8TpU3X5", + "type": "arrow", + "x": 1532.16, + "y": 729.3160331869856, + "width": 439.03999999999996, + "height": 14.901418356018894, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 137144451, + "version": 320, + "versionNonce": 50702403, + "isDeleted": false, + "boundElements": null, + "updated": 1669671981978, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 439.03999999999996, + 14.901418356018894 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "kTxypuW_b__Uu6pFiQzjx", + "gap": 6.16, + "focus": -0.3260620681155567 + }, + "endBinding": { + "elementId": "-GOyfGIP2WbWTrml7lzS9", + "gap": 8.799999999999999, + "focus": -0.2623188947497152 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "0dxfJI7r46Y6aANYWfsTe", + "type": "arrow", + "x": 1532.16, + "y": 919.2825057802027, + "width": 439.03999999999974, + "height": 1.3385896958179728, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1869400493, + "version": 306, + "versionNonce": 1994362819, + "isDeleted": false, + "boundElements": null, + "updated": 1669671996606, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 439.03999999999974, + -1.3385896958179728 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "d_UeujwAgOT55YLJ46fZG", + "gap": 6.16, + "focus": -0.4059405491850107 + }, + "endBinding": { + "elementId": "qHH-cRZS4we7YwkiiB0eG", + "gap": 8.799999999999999, + "focus": 0.20000000000000004 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "pb4S3hyZFgYfSpVYsmcKV", + "type": "rectangle", + "x": 1980, + "y": 500, + "width": 320, + "height": 80, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 805830797, + "version": 102, + "versionNonce": 300121891, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "TDp0Rur1QaA23mdM43xL4" + }, + { + "id": "pMFLxR2SpsMCJhqBJCs-v", + "type": "arrow" + }, + { + "id": "mpEPzO4tcYM8CXskBTTf4", + "type": "arrow" + } + ], + "updated": 1669672139898, + "link": null, + "locked": false + }, + { + "id": "TDp0Rur1QaA23mdM43xL4", + "type": "text", + "x": 2007.5, + "y": 523, + "width": 265, + "height": 34, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 2109430317, + "version": 87, + "versionNonce": 1147486669, + "isDeleted": false, + "boundElements": null, + "updated": 1669672123139, + "link": null, + "locked": false, + "text": "nth microservice", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 27, + "containerId": "pb4S3hyZFgYfSpVYsmcKV", + "originalText": "nth microservice" + }, + { + "id": "pMFLxR2SpsMCJhqBJCs-v", + "type": "arrow", + "x": 1540, + "y": 560, + "width": 432.06349206349205, + "height": 40, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 427479907, + "version": 339, + "versionNonce": 627846883, + "isDeleted": false, + "boundElements": null, + "updated": 1669672127255, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 120, + 0 + ], + [ + 160, + -40 + ], + [ + 432.06349206349205, + -31.361737677527117 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Da_PqOluFjN7is_eBy8il", + "focus": 0.1111111111111111, + "gap": 20 + }, + "endBinding": { + "elementId": "pb4S3hyZFgYfSpVYsmcKV", + "focus": 0.13375292899569377, + "gap": 7.936507936507951 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "zNYs5DFsod6OMrUoptwVJ", + "type": "arrow", + "x": 1540, + "y": 560, + "width": 431.20000000000005, + "height": 182.3161044460128, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1697072237, + "version": 85, + "versionNonce": 1471820141, + "isDeleted": false, + "boundElements": null, + "updated": 1669671981978, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 120, + 20 + ], + [ + 160, + 160 + ], + [ + 431.20000000000005, + 182.3161044460128 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Da_PqOluFjN7is_eBy8il", + "focus": -0.22297297297297297, + "gap": 20 + }, + "endBinding": { + "elementId": "-GOyfGIP2WbWTrml7lzS9", + "gap": 8.799999999999999, + "focus": -0.358974358974359 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "frCyG5-a_JX1gRjwRmOrN", + "type": "arrow", + "x": 1540, + "y": 560, + "width": 431.20000000000005, + "height": 352.26953846153845, + "angle": 0, + "strokeColor": "#0b7285", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 913687139, + "version": 355, + "versionNonce": 343179757, + "isDeleted": false, + "boundElements": null, + "updated": 1669671996606, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 120, + 40 + ], + [ + 180, + 320 + ], + [ + 431.20000000000005, + 352.26953846153845 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "Da_PqOluFjN7is_eBy8il", + "focus": -0.4148936170212766, + "gap": 20 + }, + "endBinding": { + "elementId": "qHH-cRZS4we7YwkiiB0eG", + "gap": 8.799999999999999, + "focus": -0.2093023255813953 + }, + "startArrowhead": "arrow", + "endArrowhead": "arrow" + }, + { + "id": "dUw9Vy5ik3Zl5f4NVukLy", + "type": "arrow", + "x": 2248.8, + "y": 754.8425713052964, + "width": 86.27032582742277, + "height": 1.2332437835043493, + "angle": 0, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1152082445, + "version": 260, + "versionNonce": 1136624589, + "isDeleted": false, + "boundElements": null, + "updated": 1669672025950, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 86.27032582742277, + 1.2332437835043493 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "-GOyfGIP2WbWTrml7lzS9", + "focus": 0.4551198139561458, + "gap": 8.800000000000182 + }, + "endBinding": { + "elementId": "v5JoGrvXuccmaZlxPWbxI", + "focus": 1.4350797912880504, + "gap": 6.46343642090601 + }, + "startArrowhead": "dot", + "endArrowhead": "dot" + }, + { + "type": "line", + "version": 6186, + "versionNonce": 956084835, + "isDeleted": false, + "id": "bdjI5RlNLAgZmvYI0ZJ78", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2321.275996923696, + "y": 949.3971028843206, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 80.5313242905507, + "height": 103.93845895928328, + "seed": 699049837, + "groupIds": [ + "EowPv5ZtARkN87l6qg8Zo", + "jGEAGebHmu5Qfk4xKcuy0", + "Bm-sABxDPXxyISbCYMwWJ" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669672050022, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.26555095108349375, + 78.55612392717138 + ], + [ + 0.012427161119890295, + 87.4993975093521 + ], + [ + 4.1475413077954775, + 91.36367543467536 + ], + [ + 18.5478699947732, + 94.63410581335458 + ], + [ + 42.88850462025491, + 95.65246897840512 + ], + [ + 66.14437140294662, + 94.02650301203627 + ], + [ + 78.50038465379497, + 90.1380348112513 + ], + [ + 80.24303312533527, + 86.86001532654419 + ], + [ + 80.48778179471857, + 79.65985098305549 + ], + [ + 80.29568254840036, + 6.590420330112938 + ], + [ + 79.86262918571221, + -0.3132945591808347 + ], + [ + 74.69170574260916, + -4.171826224919654 + ], + [ + 63.802844491730454, + -6.406483593881079 + ], + [ + 38.98860092227267, + -8.285989980878151 + ], + [ + 19.093870923498052, + -7.1652360902447 + ], + [ + 3.4468008917272406, + -3.3637756422714404 + ], + [ + -0.043542495832124266, + -0.047201529095363945 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 6909, + "versionNonce": 1747474765, + "isDeleted": false, + "id": "dPSNdhIOYNqO1mKibJIY4", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2321.561499609231, + "y": 940, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 80.01478318796464, + "height": 16.182387923834753, + "seed": 424120803, + "groupIds": [ + "EowPv5ZtARkN87l6qg8Zo", + "jGEAGebHmu5Qfk4xKcuy0", + "Bm-sABxDPXxyISbCYMwWJ" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "bxuMGTzXLn7H-uBCptINx" + }, + { + "id": "30VBRBB9yUFKojrtqDJ8A", + "type": "arrow" + } + ], + "updated": 1669672050022, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 748, + "versionNonce": 881773261, + "isDeleted": false, + "id": "30VBRBB9yUFKojrtqDJ8A", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 2241, + "y": 939.9943154195713, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 81.62752576095909, + "height": 0.018532065093154415, + "seed": 773665229, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669672050644, + "link": null, + "locked": false, + "startBinding": { + "elementId": "qHH-cRZS4we7YwkiiB0eG", + "focus": 0.8107095436301976, + "gap": 1 + }, + "endBinding": { + "elementId": "dPSNdhIOYNqO1mKibJIY4", + "focus": 0.9973188743779573, + "gap": 6.606423611904564 + }, + "lastCommittedPoint": null, + "startArrowhead": "dot", + "endArrowhead": "dot", + "points": [ + [ + 0, + 0 + ], + [ + 81.62752576095909, + 0.018532065093154415 + ] + ] + }, + { + "type": "line", + "version": 6130, + "versionNonce": 962880547, + "isDeleted": false, + "id": "g6mAFurHbxTEJmrivVtbJ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1642.8005683940833, + "y": 309.3971028843207, + "strokeColor": "#000000", + "backgroundColor": "#ced4da", + "width": 80.53132429055069, + "height": 103.93845895928327, + "seed": 757806413, + "groupIds": [ + "18NqZ2VOxAZkeapNRvsGO", + "_1rCO24XwZ0S_tZ9tBQcc", + "vR1FzOQiR_2p27qddXOTi" + ], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669671898199, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0.26555095108349375, + 78.55612392717138 + ], + [ + 0.012427161119890295, + 87.4993975093521 + ], + [ + 4.1475413077954775, + 91.36367543467536 + ], + [ + 18.5478699947732, + 94.63410581335458 + ], + [ + 42.88850462025491, + 95.65246897840512 + ], + [ + 66.14437140294662, + 94.02650301203627 + ], + [ + 78.50038465379497, + 90.1380348112513 + ], + [ + 80.24303312533527, + 86.86001532654419 + ], + [ + 80.48778179471857, + 79.65985098305549 + ], + [ + 80.29568254840036, + 6.590420330112938 + ], + [ + 79.86262918571221, + -0.3132945591808347 + ], + [ + 74.69170574260916, + -4.171826224919654 + ], + [ + 63.802844491730454, + -6.406483593881079 + ], + [ + 38.98860092227267, + -8.285989980878151 + ], + [ + 19.093870923498052, + -7.1652360902447 + ], + [ + 3.4468008917272406, + -3.3637756422714404 + ], + [ + -0.043542495832124266, + -0.047201529095363945 + ], + [ + 0, + 0 + ] + ] + }, + { + "type": "ellipse", + "version": 6852, + "versionNonce": 1218290573, + "isDeleted": false, + "id": "5loS-yQjTpfIxjlWeo0Sf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1643.0860710796183, + "y": 300, + "strokeColor": "#000000", + "backgroundColor": "#fff", + "width": 80.01478318796464, + "height": 16.182387923834753, + "seed": 1984385539, + "groupIds": [ + "18NqZ2VOxAZkeapNRvsGO", + "_1rCO24XwZ0S_tZ9tBQcc", + "vR1FzOQiR_2p27qddXOTi" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "arrow", + "id": "bxuMGTzXLn7H-uBCptINx" + } + ], + "updated": 1669671898199, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 45, + "versionNonce": 1946679683, + "isDeleted": false, + "id": "BjVr3L1HhvxKKuj4pWaN1", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "angle": 0, + "x": 1560, + "y": 360, + "strokeColor": "#d9480f", + "backgroundColor": "transparent", + "width": 81.79738531023258, + "height": 0, + "seed": 1913717677, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1669671902950, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Blt38Ylikw787bxPy5Asx", + "focus": 0.15107913669064743, + "gap": 28 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": "dot", + "endArrowhead": "dot", + "points": [ + [ + 0, + 0 + ], + [ + 81.79738531023258, + 0 + ] + ] + }, + { + "id": "ArChNM5fCplFZPsSOzV59", + "type": "rectangle", + "x": 2540, + "y": 380, + "width": 120, + "height": 620, + "angle": 0, + "strokeColor": "#d9480f", + "backgroundColor": "#e64980", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "sharp", + "seed": 847632077, + "version": 38, + "versionNonce": 604873411, + "isDeleted": false, + "boundElements": [ + { + "id": "Ius4Fnu7nZujALP8n_I5-", + "type": "arrow" + }, + { + "id": "WYCb79CVNWcXcpwWm2gUp", + "type": "arrow" + }, + { + "id": "mpEPzO4tcYM8CXskBTTf4", + "type": "arrow" + } + ], + "updated": 1669672139898, + "link": null, + "locked": false + }, + { + "id": "Ius4Fnu7nZujALP8n_I5-", + "type": "arrow", + "x": 2240, + "y": 740, + "width": 280, + "height": 200, + "angle": 0, + "strokeColor": "#862e9c", + "backgroundColor": "#e64980", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1130752301, + "version": 130, + "versionNonce": 1934999107, + "isDeleted": false, + "boundElements": null, + "updated": 1669672081485, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + -20 + ], + [ + 220, + -180 + ], + [ + 280, + -200 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "-GOyfGIP2WbWTrml7lzS9", + "focus": 0.3142857142857143, + "gap": 1 + }, + "endBinding": { + "elementId": "ArChNM5fCplFZPsSOzV59", + "focus": 0.5353535353535354, + "gap": 20 + }, + "startArrowhead": "dot", + "endArrowhead": "dot" + }, + { + "id": "WYCb79CVNWcXcpwWm2gUp", + "type": "arrow", + "x": 2240, + "y": 920, + "width": 280, + "height": 220, + "angle": 0, + "strokeColor": "#862e9c", + "backgroundColor": "#e64980", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 388693475, + "version": 176, + "versionNonce": 942319277, + "isDeleted": false, + "boundElements": null, + "updated": 1669672111091, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 180, + -20 + ], + [ + 200, + -200 + ], + [ + 280, + -220 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "qHH-cRZS4we7YwkiiB0eG", + "focus": 0.34146341463414637, + "gap": 1 + }, + "endBinding": { + "elementId": "ArChNM5fCplFZPsSOzV59", + "focus": 0.030769230769230774, + "gap": 20 + }, + "startArrowhead": "dot", + "endArrowhead": "dot" + }, + { + "id": "mpEPzO4tcYM8CXskBTTf4", + "type": "arrow", + "x": 2320, + "y": 540, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#862e9c", + "backgroundColor": "#e64980", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 2, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 199279405, + "version": 19, + "versionNonce": 656744173, + "isDeleted": false, + "boundElements": null, + "updated": 1669672139900, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "pb4S3hyZFgYfSpVYsmcKV", + "focus": 0, + "gap": 20 + }, + "endBinding": { + "elementId": "ArChNM5fCplFZPsSOzV59", + "focus": 0.4838709677419355, + "gap": 20 + }, + "startArrowhead": "dot", + "endArrowhead": "dot" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/image.png b/docs/image.png new file mode 100644 index 0000000..99dd0f1 Binary files /dev/null and b/docs/image.png differ diff --git a/src/Gateway/Gateway.Api/Controllers/WeatherForecastController.cs b/src/Gateway/Gateway.Api/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..db3ae65 --- /dev/null +++ b/src/Gateway/Gateway.Api/Controllers/WeatherForecastController.cs @@ -0,0 +1,33 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Gateway.Api.Controllers +{ + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Gateway/Gateway.Api/Gateway.Api.csproj b/src/Gateway/Gateway.Api/Gateway.Api.csproj new file mode 100644 index 0000000..60bf9ea --- /dev/null +++ b/src/Gateway/Gateway.Api/Gateway.Api.csproj @@ -0,0 +1,13 @@ + + + + net6.0 + enable + enable + + + + + + + diff --git a/src/Gateway/Gateway.Api/Program.cs b/src/Gateway/Gateway.Api/Program.cs new file mode 100644 index 0000000..48863a6 --- /dev/null +++ b/src/Gateway/Gateway.Api/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/Gateway/Gateway.Api/Properties/launchSettings.json b/src/Gateway/Gateway.Api/Properties/launchSettings.json new file mode 100644 index 0000000..bbca344 --- /dev/null +++ b/src/Gateway/Gateway.Api/Properties/launchSettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:39630", + "sslPort": 44359 + } + }, + "profiles": { + "Gateway.Api": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7179;http://localhost:5179", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/Gateway/Gateway.Api/WeatherForecast.cs b/src/Gateway/Gateway.Api/WeatherForecast.cs new file mode 100644 index 0000000..2d6cdfa --- /dev/null +++ b/src/Gateway/Gateway.Api/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace Gateway.Api +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } + } +} \ No newline at end of file diff --git a/src/Gateway/Gateway.sln b/src/Gateway/Gateway.sln new file mode 100644 index 0000000..d1397a0 --- /dev/null +++ b/src/Gateway/Gateway.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gateway.Api", "Gateway.Api\Gateway.Api.csproj", "{4E6C99DC-7A62-4E36-A476-C9E6EB37A82A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4E6C99DC-7A62-4E36-A476-C9E6EB37A82A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4E6C99DC-7A62-4E36-A476-C9E6EB37A82A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4E6C99DC-7A62-4E36-A476-C9E6EB37A82A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4E6C99DC-7A62-4E36-A476-C9E6EB37A82A}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2D7A39C4-CE1B-4156-8228-2A07C80A1EB3} + EndGlobalSection +EndGlobal diff --git a/Huppy/Huppy.App/Commands/ComponentInteractions/PaginatorInteractions.cs b/src/Huppy/Huppy.App/Commands/ComponentInteractions/PaginatorInteractions.cs similarity index 100% rename from Huppy/Huppy.App/Commands/ComponentInteractions/PaginatorInteractions.cs rename to src/Huppy/Huppy.App/Commands/ComponentInteractions/PaginatorInteractions.cs diff --git a/Huppy/Huppy.App/Commands/SlashCommands/AdminCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/AdminCommands.cs similarity index 100% rename from Huppy/Huppy.App/Commands/SlashCommands/AdminCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/AdminCommands.cs diff --git a/Huppy/Huppy.App/Commands/SlashCommands/AiCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/AiCommands.cs similarity index 87% rename from Huppy/Huppy.App/Commands/SlashCommands/AiCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/AiCommands.cs index 35fe16e..29a40a4 100644 --- a/Huppy/Huppy.App/Commands/SlashCommands/AiCommands.cs +++ b/src/Huppy/Huppy.App/Commands/SlashCommands/AiCommands.cs @@ -1,6 +1,7 @@ using System.Text; using Discord; using Discord.Interactions; +using Discord.WebSocket; using Huppy.Core.Interfaces.IServices; using Huppy.Core.Services.HuppyCacheStorage; using Huppy.Kernel; @@ -13,10 +14,12 @@ public class AiCommands : InteractionModuleBase x.Value.Count).Take(5); + var topStats = stats.OrderByDescending(x => x.Value).Take(5); StringBuilder sb = new(); sb.AppendLine("✨ Top Huppy friends ✨\n"); + //TODO: user username! foreach (var item in topStats) { - sb.AppendLine($"> **{item.Value.Username}** : `{item.Value.Count}`\n"); + sb.AppendLine($"> **{_client.GetUser(item.Key).Username}** : `{item.Value}`\n"); } var embed = new EmbedBuilder().WithCurrentTimestamp() @@ -75,7 +79,7 @@ public async Task GetAiStats() .WithColor(Color.Magenta) .WithDescription(sb.ToString()); - embed.AddField("Conversations", stats.Sum(x => x.Value.Count), true); + embed.AddField("Conversations", stats.Sum(x => x.Value), true); embed.AddField("Huppy friend count", stats.Keys.Count, true); await ModifyOriginalResponseAsync((msg) => msg.Embed = embed.Build()); diff --git a/Huppy/Huppy.App/Commands/SlashCommands/AvatarCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/AvatarCommands.cs similarity index 100% rename from Huppy/Huppy.App/Commands/SlashCommands/AvatarCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/AvatarCommands.cs diff --git a/Huppy/Huppy.App/Commands/SlashCommands/DevCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/DevCommands.cs similarity index 93% rename from Huppy/Huppy.App/Commands/SlashCommands/DevCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/DevCommands.cs index a598d81..78631c5 100644 --- a/Huppy/Huppy.App/Commands/SlashCommands/DevCommands.cs +++ b/src/Huppy/Huppy.App/Commands/SlashCommands/DevCommands.cs @@ -9,6 +9,7 @@ using Huppy.Core.Services.HuppyCacheStorage; using Huppy.Core.Services.JobManager; using Huppy.Core.Services.Paginator.Entities; +using Huppy.Core.Utilities; using Huppy.Kernel.Constants; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -112,14 +113,14 @@ await ModifyOriginalResponseAsync((msg) => foreach (var ticket in tickets) { - ticket.CreatedDate = DateTime.SpecifyKind(ticket.CreatedDate, DateTimeKind.Utc); - + // ticket.CreatedDate = DateTime.SpecifyKind(ticket.CreatedDate, DateTimeKind.Utc); + var userName = _client.GetUser(ticket.UserId); StringBuilder sb = new(); sb.AppendLine($"> Ticked ID:`{ticket.Id}`"); - sb.AppendLine($"> Created date: {TimestampTag.FromDateTime(ticket.CreatedDate)}"); + sb.AppendLine($"> Created date: {TimestampTag.FromDateTime(Miscellaneous.UnixTimeStampToUtcDateTime(ticket.CreatedDate))}"); sb.AppendLine($"> Status: {(ticket.IsClosed ? "`Closed`" : "`Open`")}"); sb.AppendLine($"> Topic: `{ticket.Topic}`"); - sb.AppendLine($"> Owner: `{ticket.User.Username} ({ticket.UserId})`"); + sb.AppendLine($"> Owner: `{userName} ({ticket.UserId})`"); embed.AddField("📜 Ticket", sb.ToString()); } diff --git a/Huppy/Huppy.App/Commands/SlashCommands/GeneralCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/GeneralCommands.cs similarity index 90% rename from Huppy/Huppy.App/Commands/SlashCommands/GeneralCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/GeneralCommands.cs index e79879d..5240af9 100644 --- a/Huppy/Huppy.App/Commands/SlashCommands/GeneralCommands.cs +++ b/src/Huppy/Huppy.App/Commands/SlashCommands/GeneralCommands.cs @@ -15,15 +15,15 @@ namespace Huppy.App.Commands.SlashCommands; public class GeneralCommands : InteractionModuleBase { private readonly ILogger _logger; - private readonly ICommandLogRepository _commandRepository; + private readonly ICommandLogService _commandLogService; private readonly IServiceScopeFactory _scopeFactory; private readonly CacheStorageService _cacheService; private readonly InteractionService _interactionService; private readonly IPaginatorService _paginatorService; - public GeneralCommands(ILogger logger, CacheStorageService cacheService, ICommandLogRepository commandLogRepository, InteractionService interactionService, IPaginatorService paginatorService, IServiceScopeFactory scopeFactory) + public GeneralCommands(ILogger logger, ICommandLogService commandLogService, CacheStorageService cacheService, InteractionService interactionService, IPaginatorService paginatorService, IServiceScopeFactory scopeFactory) { _logger = logger; - _commandRepository = commandLogRepository; + _commandLogService = commandLogService; _cacheService = cacheService; _interactionService = interactionService; _paginatorService = paginatorService; @@ -86,7 +86,7 @@ public async Task AboutMe() .WithCurrentTimestamp(); embed.AddField("Users", _cacheService.GetUserNames().Count, true); - embed.AddField("Commands Used", await _commandRepository.GetCount(), true); + embed.AddField("Commands Used", await _commandLogService.GetCount(), true); await ModifyOriginalResponseAsync((msg) => msg.Embed = embed.Build()); } diff --git a/Huppy/Huppy.App/Commands/SlashCommands/ReminderCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/ReminderCommands.cs similarity index 80% rename from Huppy/Huppy.App/Commands/SlashCommands/ReminderCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/ReminderCommands.cs index eb988b5..820a31d 100644 --- a/Huppy/Huppy.App/Commands/SlashCommands/ReminderCommands.cs +++ b/src/Huppy/Huppy.App/Commands/SlashCommands/ReminderCommands.cs @@ -4,6 +4,9 @@ using Huppy.Core.Interfaces.IRepositories; using Huppy.Core.Interfaces.IServices; using Huppy.Core.Services.Paginator.Entities; +using Huppy.Core.Services.Reminder; +using Huppy.Core.Utilities; +using HuppyService.Service.Protos; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -13,18 +16,15 @@ namespace Huppy.App.Commands.SlashCommands; [Group("reminder", "reminder commands")] public class ReminderCommands : InteractionModuleBase { - private readonly ILogger _logger; private readonly IReminderService _reminderService; private readonly IPaginatorService _paginatorService; private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly IReminderRepository _reminderRepository; private const int RemindersPerPage = 5; - public ReminderCommands(IReminderService reminderService, IPaginatorService paginatorService, IServiceScopeFactory serviceScopeFactory, IReminderRepository reminderRepository) + public ReminderCommands(IReminderService reminderService, IPaginatorService paginatorService, IServiceScopeFactory serviceScopeFactory) { _reminderService = reminderService; _paginatorService = paginatorService; _serviceScopeFactory = serviceScopeFactory; - _reminderRepository = reminderRepository; } [SlashCommand("date", "Add reminder by date (format: dd/mm/yyyy hh:mm:ss)")] @@ -56,7 +56,7 @@ private async Task AddRemindMe(DateTime date, string message) } else { - await _reminderService.AddReminder(date, Context.User, message); + await _reminderService.AddReminderAsync(date, Context.User, message); embed = new EmbedBuilder() .WithTitle("RemindMe result") @@ -75,7 +75,7 @@ await ModifyOriginalResponseAsync((msg) => [Ephemeral] public async Task RemoveReminder(int reminderId) { - var reminder = await _reminderRepository.GetAsync(Context.User.Id, reminderId); + var reminder = await _reminderService.GetReminderAsync(Context.User.Id, reminderId); var embed = new EmbedBuilder().WithColor(Color.LightOrange) .WithCurrentTimestamp(); @@ -93,11 +93,11 @@ await ModifyOriginalResponseAsync((msg) => return; } - await _reminderService.RemoveReminder(reminder); + await _reminderService.RemoveReminderAsync(reminder); embed.WithTitle("Success") .WithDescription($"Reminder with `{reminderId}` ID got removed") - .AddField("Date", TimestampTag.FromDateTime(DateTime.SpecifyKind(reminder.RemindDate, DateTimeKind.Utc))) + .AddField("Date", TimestampTag.FromDateTime(DateTime.SpecifyKind(Miscellaneous.UnixTimeStampToUtcDateTime(reminder.RemindDate), DateTimeKind.Utc))) .AddField("Message", $"```vb\n{reminder.Message}\n```"); await ModifyOriginalResponseAsync((msg) => @@ -119,10 +119,9 @@ public async Task GetUserReminders() Data = Context.User }; - var reminders = await _reminderRepository.GetAllAsync(); - var reminderCount = await reminders.Where(entry => entry.UserId == Context.User.Id).CountAsync(); + var remindersCount = await _reminderService.GetRemindersCount(Context.User.Id); - if (!(reminderCount > 0)) + if (!(remindersCount > 0)) { var page = Task (AsyncServiceScope scope, object? data) => { @@ -149,7 +148,7 @@ public async Task GetUserReminders() } else { - int pages = Convert.ToInt32(Math.Ceiling((decimal)reminderCount / RemindersPerPage)); + int pages = Convert.ToInt32(Math.Ceiling((decimal)remindersCount / RemindersPerPage)); for (int i = 0; i < pages; i++) { int currentPage = i; @@ -157,15 +156,8 @@ public async Task GetUserReminders() { if (data is not IUser user) return null!; - var reminderRepository = scope.ServiceProvider.GetRequiredService(); - - var reminders = await reminderRepository.GetAllAsync(); - var pageReminders = await reminders - .Where(reminder => reminder.UserId == user.Id) - .OrderBy(e => e.RemindDate) - .Skip(currentPage * RemindersPerPage) - .Take(RemindersPerPage) - .ToListAsync(); + var reminderService = scope.ServiceProvider.GetRequiredService(); + var reminders = await reminderService.GetSortedUserReminders(Context.User.Id, currentPage * RemindersPerPage, RemindersPerPage); var embed = new EmbedBuilder() .WithAuthor(user) @@ -174,9 +166,9 @@ public async Task GetUserReminders() .WithColor(Color.Orange) .WithCurrentTimestamp(); - foreach (var reminder in pageReminders) + foreach (var reminder in reminders) { - TimestampTag timestamp = TimestampTag.FromDateTime(DateTime.SpecifyKind(reminder.RemindDate, DateTimeKind.Utc)); + TimestampTag timestamp = TimestampTag.FromDateTime(DateTime.SpecifyKind(Miscellaneous.UnixTimeStampToUtcDateTime(reminder.RemindDate), DateTimeKind.Utc)); embed.AddField( $"✨ Reminder at {timestamp} | ID: {reminder.Id}", $"```vb\n{reminder.Message}```", diff --git a/Huppy/Huppy.App/Commands/SlashCommands/ServerCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/ServerCommands.cs similarity index 83% rename from Huppy/Huppy.App/Commands/SlashCommands/ServerCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/ServerCommands.cs index 7eab6db..223062a 100644 --- a/Huppy/Huppy.App/Commands/SlashCommands/ServerCommands.cs +++ b/src/Huppy/Huppy.App/Commands/SlashCommands/ServerCommands.cs @@ -14,15 +14,15 @@ namespace Huppy.App.Commands.SlashCommands; public class ServerCommands : InteractionModuleBase { private readonly ILogger _logger; - private readonly IServerRepository _serverRepository; + private readonly IServerService _serverService; private readonly IServiceScopeFactory _scopeFactory; private readonly IPaginatorService _paginatorService; - public ServerCommands(ILogger logger, IServerRepository serverRepository, IServiceScopeFactory scopeFactory, IPaginatorService paginatorService) + public ServerCommands(ILogger logger, IServiceScopeFactory scopeFactory, IPaginatorService paginatorService, IServerService serverService) { _logger = logger; - _serverRepository = serverRepository; _scopeFactory = scopeFactory; _paginatorService = paginatorService; + _serverService = serverService; } [SlashCommand("info", "Get current configuration for this server")] @@ -40,9 +40,9 @@ public async Task GetServerInfo() var page1 = async Task (AsyncServiceScope scope, object? data) => { - var serverRepository = scope.ServiceProvider.GetRequiredService(); + var serverRepository = scope.ServiceProvider.GetRequiredService(); - var server = await serverRepository.GetOrCreateAsync(Context); + var server = await _serverService.GetOrCreateAsync(Context.Guild.Id, Context.Guild.Name, Context.Guild.DefaultChannel.Id); var embed = new EmbedBuilder().WithAuthor(Context.User) .WithColor(Color.DarkPurple) @@ -56,7 +56,7 @@ public async Task GetServerInfo() embed.AddField("Use Greet", server.UseGreet, true); embed.AddField("Greet message", string.IsNullOrEmpty(server.GreetMessage) ? "`empty`" : server.GreetMessage); - var defaultRole = Context.Guild.GetRole(server.RoleID); + var defaultRole = Context.Guild.GetRole(server.RoleId); if (defaultRole is not null) embed.AddField("Default role", defaultRole.Mention, true); @@ -69,9 +69,9 @@ public async Task GetServerInfo() var page2 = async Task (AsyncServiceScope scope, object? data) => { - var serverRepository = scope.ServiceProvider.GetRequiredService(); + var serverRepository = scope.ServiceProvider.GetRequiredService(); - var server = await serverRepository.GetOrCreateAsync(Context); + var server = await _serverService.GetOrCreateAsync(Context.Guild.Id, Context.Guild.Name, Context.Guild.DefaultChannel.Id); var embed = new EmbedBuilder().WithAuthor(Context.User) .WithColor(Color.DarkPurple) @@ -98,9 +98,9 @@ public async Task GetServerInfo() [SlashCommand("configure", "Configure Huppy for your server")] [RequireUserPermission(GuildPermission.Administrator)] - public async Task ConfigureHuppy(bool? UseGreet = null, string? GreetingMessage = null, IRole? DefaultRole = null, bool? EnableNews = false, SocketGuildChannel? HuppyRoom = null, SocketGuildChannel? NewsRoom = null, SocketGuildChannel? GreetingRoom = null) + public async Task ConfigureHuppy(bool? UseGreet = null, string? GreetingMessage = null, IRole? DefaultRole = null, SocketGuildChannel? HuppyRoom = null, SocketGuildChannel? GreetingRoom = null) { - var server = await _serverRepository.GetOrCreateAsync(Context); + var server = await _serverService.GetOrCreateAsync(Context.Guild.Id, Context.Guild.Name, Context.Guild.DefaultChannel.Id); server.ServerName = Context.Guild.Name; @@ -111,7 +111,7 @@ public async Task ConfigureHuppy(bool? UseGreet = null, string? GreetingMessage server.GreetMessage = GreetingMessage; if (DefaultRole is not null) - server.RoleID = DefaultRole.Id; + server.RoleId = DefaultRole.Id; if (server.Rooms is not null) { @@ -122,8 +122,7 @@ public async Task ConfigureHuppy(bool? UseGreet = null, string? GreetingMessage server.Rooms.GreetingRoom = GreetingRoom.Id; } - await _serverRepository.UpdateAsync(server); - await _serverRepository.SaveChangesAsync(); + await _serverService.UpdateAsync(server); var embed = new EmbedBuilder().WithDescription("Updated your server settings\nUse `/server info` command to see current configuration") .WithThumbnailUrl(Icons.Huppy1) diff --git a/Huppy/Huppy.App/Commands/SlashCommands/TicketCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/TicketCommands.cs similarity index 89% rename from Huppy/Huppy.App/Commands/SlashCommands/TicketCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/TicketCommands.cs index 229add9..7371c7b 100644 --- a/Huppy/Huppy.App/Commands/SlashCommands/TicketCommands.cs +++ b/src/Huppy/Huppy.App/Commands/SlashCommands/TicketCommands.cs @@ -4,6 +4,7 @@ using Huppy.Core.Attributes; using Huppy.Core.Interfaces.IServices; using Huppy.Core.Services.Paginator.Entities; +using Huppy.Core.Utilities; using Huppy.Kernel.Constants; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -51,7 +52,7 @@ public async Task CreateTicketAsync(string topic, string description) .WithThumbnailUrl(Icons.Huppy1) .WithTitle("Ticket created!") .AddField("Ticket ID", $"`{ticket.Id}`", true) - .AddField("Creation date", TimestampTag.FromDateTime(ticket.CreatedDate), true) + .AddField("Creation date", TimestampTag.FromDateTime(Miscellaneous.UnixTimeStampToUtcDateTime(ticket.CreatedDate)), true) .AddField("Status", ticket.IsClosed ? "`Closed`" : "`Open`", true) .AddField("Topic", ticket.Topic) .AddField("Ticket description", ticket.Description) @@ -92,13 +93,15 @@ public async Task GetStatusAsync(string ticketId) } else { - ticket.CreatedDate = DateTime.SpecifyKind(ticket.CreatedDate, DateTimeKind.Utc); + // ticket.CreatedDate = DateTime.SpecifyKind(ticket.CreatedDate, DateTimeKind.Utc); + DateTime date = Miscellaneous.UnixTimeStampToUtcDateTime(ticket.ClosedDate); + string closedDate = date != default ? TimestampTag.FromDateTime(Miscellaneous.UnixTimeStampToUtcDateTime(ticket.CreatedDate)).ToString() : "Ticket still open"; embed = new EmbedBuilder().WithColor(Color.Magenta) .WithThumbnailUrl(Icons.Huppy1) .WithTitle("Ticket details") - .AddField("Creation date", TimestampTag.FromDateTime(ticket.CreatedDate), true) - .AddField("Closed date", ticket.ClosedDate is not null ? TimestampTag.FromDateTime(ticket.CreatedDate) : "Ticket still open", true) + .AddField("Creation date", TimestampTag.FromDateTime(Miscellaneous.UnixTimeStampToUtcDateTime(ticket.CreatedDate)), true) + .AddField("Closed date", closedDate, true) .AddField("Status", ticket.IsClosed ? "`Closed`" : "`Open`", true) .AddField("Topic", ticket.Topic) .AddField("Ticket description", ticket.Description) @@ -148,11 +151,11 @@ await ModifyOriginalResponseAsync((msg) => foreach (var ticket in tickets) { - ticket.CreatedDate = DateTime.SpecifyKind(ticket.CreatedDate, DateTimeKind.Utc); + // ticket.CreatedDate = DateTime.SpecifyKind(ticket.CreatedDate, DateTimeKind.Utc); StringBuilder sb = new(); sb.AppendLine($"> Ticked ID:`{ticket.Id}`"); - sb.AppendLine($"> Created date: {TimestampTag.FromDateTime(ticket.CreatedDate)}"); + sb.AppendLine($"> Created date: {TimestampTag.FromDateTime(Miscellaneous.UnixTimeStampToUtcDateTime(ticket.CreatedDate))}"); sb.AppendLine($"> Status: {(ticket.IsClosed ? "`Closed`" : "`Open`")}"); sb.AppendLine($"> Topic: `{ticket.Topic}`"); diff --git a/Huppy/Huppy.App/Commands/SlashCommands/UrbanCommands.cs b/src/Huppy/Huppy.App/Commands/SlashCommands/UrbanCommands.cs similarity index 100% rename from Huppy/Huppy.App/Commands/SlashCommands/UrbanCommands.cs rename to src/Huppy/Huppy.App/Commands/SlashCommands/UrbanCommands.cs diff --git a/Huppy/Huppy.App/Configuration/ModuleConfigurator.cs b/src/Huppy/Huppy.App/Configuration/ModuleConfigurator.cs similarity index 80% rename from Huppy/Huppy.App/Configuration/ModuleConfigurator.cs rename to src/Huppy/Huppy.App/Configuration/ModuleConfigurator.cs index 0c7e759..2d693ea 100644 --- a/Huppy/Huppy.App/Configuration/ModuleConfigurator.cs +++ b/src/Huppy/Huppy.App/Configuration/ModuleConfigurator.cs @@ -9,6 +9,7 @@ using Huppy.Core.Services.AiStabilizer; using Huppy.Core.Services.App; using Huppy.Core.Services.CommandHandler; +using Huppy.Core.Services.CommandLog; using Huppy.Core.Services.EventLoop; using Huppy.Core.Services.GPT; using Huppy.Core.Services.HuppyCacheStorage; @@ -19,12 +20,14 @@ using Huppy.Core.Services.Reminder; using Huppy.Core.Services.Resources; using Huppy.Core.Services.ScopedData; +using Huppy.Core.Services.Server; using Huppy.Core.Services.ServerInteraction; using Huppy.Core.Services.Ticket; using Huppy.Core.Services.TimedEvents; using Huppy.Core.Services.Urban; using Huppy.Infrastructure; using Huppy.Infrastructure.Repositories; +using HuppyService.Service.Protos; using Microsoft.Extensions.DependencyInjection; using Serilog; @@ -51,6 +54,41 @@ public ModuleConfigurator AddAppSettings(AppSettings? settings = null) return this; } + public ModuleConfigurator AddGRPCServices() + { + Uri huppyCoreUrl = new(_appSettings?.Microservices?.HuppyCoreUrl!); + Log.Logger.Information("HuppyCore address: {address}", huppyCoreUrl); + + _services.AddGrpcClient((services, options) => + { + // TODO remake to this + //var basketApi = services.GetRequiredService>().Value.HuppyCoreUrl; + options.Address = huppyCoreUrl; + }); + + _services.AddGrpcClient((services, options) => + { + options.Address = huppyCoreUrl; + }); + + _services.AddGrpcClient((services, options) => + { + options.Address = huppyCoreUrl; + }); + + _services.AddGrpcClient((services, options) => + { + options.Address = huppyCoreUrl; + }); + + _services.AddGrpcClient((services, options) => + { + options.Address = huppyCoreUrl; + }); + + return this; + } + public ModuleConfigurator AddLogger(ILogger logger) { _services.AddLogging(conf => conf.AddSerilog(logger)); @@ -132,17 +170,15 @@ public ModuleConfigurator AddServices() _services.AddScoped(); _services.AddScoped(); _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); - // http clients + // externals _services.AddScoped(); _services.AddScoped(); // repositories _services.AddScoped(); - _services.AddScoped(); - _services.AddScoped(); - _services.AddScoped(); - _services.AddScoped(); return this; } diff --git a/Huppy/Huppy.App/GlobalUsings.cs b/src/Huppy/Huppy.App/GlobalUsings.cs similarity index 100% rename from Huppy/Huppy.App/GlobalUsings.cs rename to src/Huppy/Huppy.App/GlobalUsings.cs diff --git a/Huppy/Huppy.App/Huppy.App.csproj b/src/Huppy/Huppy.App/Huppy.App.csproj similarity index 96% rename from Huppy/Huppy.App/Huppy.App.csproj rename to src/Huppy/Huppy.App/Huppy.App.csproj index 5d38c6d..590c543 100644 --- a/Huppy/Huppy.App/Huppy.App.csproj +++ b/src/Huppy/Huppy.App/Huppy.App.csproj @@ -13,6 +13,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Huppy/Huppy.App/HuppyHostedService.cs b/src/Huppy/Huppy.App/HuppyHostedService.cs similarity index 99% rename from Huppy/Huppy.App/HuppyHostedService.cs rename to src/Huppy/Huppy.App/HuppyHostedService.cs index 672385e..57f6a80 100644 --- a/Huppy/Huppy.App/HuppyHostedService.cs +++ b/src/Huppy/Huppy.App/HuppyHostedService.cs @@ -124,7 +124,7 @@ public Task CreateEvents() _interactionService.Log += _loggingService.OnLogAsync; // others - _eventService.OnEventsRemoved += _reminderService.RemoveReminderRange; + _eventService.OnEventsRemoved += _reminderService.RemoveReminderRangeAsync; _middlewareExecutor.OnLogAsync += _loggingService.OnMiddlewareLog; return Task.CompletedTask; @@ -263,7 +263,7 @@ private static bool IsProd() #if DEBUG return false; #else - return true; + return true; #endif } } \ No newline at end of file diff --git a/Huppy/Huppy.App/Middlewares/CommandLogMiddleware.cs b/src/Huppy/Huppy.App/Middlewares/CommandLogMiddleware.cs similarity index 70% rename from Huppy/Huppy.App/Middlewares/CommandLogMiddleware.cs rename to src/Huppy/Huppy.App/Middlewares/CommandLogMiddleware.cs index 2917d5a..6bf3c9f 100644 --- a/Huppy/Huppy.App/Middlewares/CommandLogMiddleware.cs +++ b/src/Huppy/Huppy.App/Middlewares/CommandLogMiddleware.cs @@ -1,20 +1,20 @@ using System.Diagnostics; using Discord.Interactions; -using Huppy.Core.Interfaces; -using Huppy.Core.Interfaces.IRepositories; -using Huppy.Kernel; +using Huppy.Core.Interfaces.IServices; +using Huppy.Core.Utilities; +using HuppyService.Service.Protos.Models; using Microsoft.Extensions.Logging; namespace Huppy.App.Middlewares { public class CommandLogMiddleware : IMiddleware { - private readonly ICommandLogRepository _commandRepository; + private readonly ICommandLogService _commandLogService; private readonly ILogger _logger; private readonly Stopwatch watch = new(); - public CommandLogMiddleware(ICommandLogRepository commandLogRepository, ILogger logger) + public CommandLogMiddleware(ICommandLogService commandLogService, ILogger logger) { - _commandRepository = commandLogRepository; + _commandLogService = commandLogService; _logger = logger; } @@ -28,20 +28,19 @@ public async Task AfterAsync(ExtendedShardedInteractionContext context, ICommand { watch.Stop(); - CommandLog log = new() + CommandLogModel log = new() { CommandName = commandInfo.ToString(), - Date = DateTime.UtcNow, + Date = Miscellaneous.DateTimeToUnixTimestamp(DateTime.UtcNow), IsSuccess = result.IsSuccess, UserId = context.User.Id, ExecutionTimeMs = watch.ElapsedMilliseconds, ChannelId = context.Channel.Id, - ErrorMessage = result.ErrorReason, + ErrorMessage = !string.IsNullOrEmpty(result.ErrorReason) ? result.ErrorReason : "", GuildId = context.Guild.Id }; - await _commandRepository.AddAsync(log); - await _commandRepository.SaveChangesAsync(); + await _commandLogService.AddCommand(log); if (result.IsSuccess) { diff --git a/Huppy/Huppy.App/Middlewares/DataSynchronizationMiddleware.cs b/src/Huppy/Huppy.App/Middlewares/DataSynchronizationMiddleware.cs similarity index 85% rename from Huppy/Huppy.App/Middlewares/DataSynchronizationMiddleware.cs rename to src/Huppy/Huppy.App/Middlewares/DataSynchronizationMiddleware.cs index 7bf72f3..f2b61c4 100644 --- a/Huppy/Huppy.App/Middlewares/DataSynchronizationMiddleware.cs +++ b/src/Huppy/Huppy.App/Middlewares/DataSynchronizationMiddleware.cs @@ -1,8 +1,10 @@ using Discord.Interactions; using Huppy.Core.Interfaces; using Huppy.Core.Interfaces.IRepositories; +using Huppy.Core.Interfaces.IServices; using Huppy.Core.Services.HuppyCacheStorage; using Huppy.Kernel; +using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Logging; namespace Huppy.App.Middlewares @@ -10,15 +12,15 @@ namespace Huppy.App.Middlewares public class DataSynchronizationMiddleware : IMiddleware { private readonly CacheStorageService _cacheService; - private readonly IServerRepository _serverRepository; + private readonly IServerService _serverService; private readonly IUserRepository _userRepository; private readonly ILogger _logger; - public DataSynchronizationMiddleware(CacheStorageService cacheService, IServerRepository serverRepository, IUserRepository userRepository, ILogger logger) + public DataSynchronizationMiddleware(CacheStorageService cacheService, IUserRepository userRepository, ILogger logger, IServerService serverService) { _cacheService = cacheService; - _serverRepository = serverRepository; _userRepository = userRepository; _logger = logger; + _serverService = serverService; } public async Task BeforeAsync(ExtendedShardedInteractionContext context) @@ -66,8 +68,8 @@ private async Task SyncServerAsync(ExtendedShardedInteractionContext ctx) { if (ctx.Guild is not null && !_cacheService.RegisteredGuildsIds.Contains(ctx.Guild.Id)) { - await _serverRepository.GetOrCreateAsync(ctx); - await _serverRepository.SaveChangesAsync(); + await _serverService.GetOrCreateAsync(ctx.Guild.Id, ctx.Guild.Name, ctx.Guild.DefaultChannel.Id); + _cacheService.RegisteredGuildsIds.Add(ctx.Guild.Id); } } diff --git a/Huppy/Huppy.App/Middlewares/ScopedDataMiddleware.cs b/src/Huppy/Huppy.App/Middlewares/ScopedDataMiddleware.cs similarity index 100% rename from Huppy/Huppy.App/Middlewares/ScopedDataMiddleware.cs rename to src/Huppy/Huppy.App/Middlewares/ScopedDataMiddleware.cs diff --git a/Huppy/Huppy.App/Program.cs b/src/Huppy/Huppy.App/Program.cs similarity index 97% rename from Huppy/Huppy.App/Program.cs rename to src/Huppy/Huppy.App/Program.cs index e48d972..01a7bdc 100644 --- a/Huppy/Huppy.App/Program.cs +++ b/src/Huppy/Huppy.App/Program.cs @@ -25,6 +25,7 @@ await Host.CreateDefaultBuilder(args) { _ = new ModuleConfigurator(services) .AddAppSettings(appSettings) + .AddGRPCServices() .AddLogger(Log.Logger) .AddDiscord() .AddServices() diff --git a/Huppy/Huppy.Core/Attributes/BetaCommandAttribute.cs b/src/Huppy/Huppy.Core/Attributes/BetaCommandAttribute.cs similarity index 100% rename from Huppy/Huppy.Core/Attributes/BetaCommandAttribute.cs rename to src/Huppy/Huppy.Core/Attributes/BetaCommandAttribute.cs diff --git a/Huppy/Huppy.Core/Attributes/BetaCommandGroupAttribute.cs b/src/Huppy/Huppy.Core/Attributes/BetaCommandGroupAttribute.cs similarity index 100% rename from Huppy/Huppy.Core/Attributes/BetaCommandGroupAttribute.cs rename to src/Huppy/Huppy.Core/Attributes/BetaCommandGroupAttribute.cs diff --git a/Huppy/Huppy.Core/Attributes/DebugCommandAttribute.cs b/src/Huppy/Huppy.Core/Attributes/DebugCommandAttribute.cs similarity index 100% rename from Huppy/Huppy.Core/Attributes/DebugCommandAttribute.cs rename to src/Huppy/Huppy.Core/Attributes/DebugCommandAttribute.cs diff --git a/Huppy/Huppy.Core/Attributes/DebugCommandGroupAttribute.cs b/src/Huppy/Huppy.Core/Attributes/DebugCommandGroupAttribute.cs similarity index 100% rename from Huppy/Huppy.Core/Attributes/DebugCommandGroupAttribute.cs rename to src/Huppy/Huppy.Core/Attributes/DebugCommandGroupAttribute.cs diff --git a/Huppy/Huppy.Core/Attributes/EphemeralAttribute .cs b/src/Huppy/Huppy.Core/Attributes/EphemeralAttribute .cs similarity index 100% rename from Huppy/Huppy.Core/Attributes/EphemeralAttribute .cs rename to src/Huppy/Huppy.Core/Attributes/EphemeralAttribute .cs diff --git a/Huppy/Huppy.Core/Attributes/RequireDevAttribute.cs b/src/Huppy/Huppy.Core/Attributes/RequireDevAttribute.cs similarity index 100% rename from Huppy/Huppy.Core/Attributes/RequireDevAttribute.cs rename to src/Huppy/Huppy.Core/Attributes/RequireDevAttribute.cs diff --git a/Huppy/Huppy.Core/Entities/AiUser.cs b/src/Huppy/Huppy.Core/Entities/AiUser.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/AiUser.cs rename to src/Huppy/Huppy.Core/Entities/AiUser.cs diff --git a/Huppy/Huppy.Core/Entities/EventLoopEntry.cs b/src/Huppy/Huppy.Core/Entities/EventLoopEntry.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/EventLoopEntry.cs rename to src/Huppy/Huppy.Core/Entities/EventLoopEntry.cs diff --git a/Huppy/Huppy.Core/Entities/GPTRequest.cs b/src/Huppy/Huppy.Core/Entities/GPTRequest.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/GPTRequest.cs rename to src/Huppy/Huppy.Core/Entities/GPTRequest.cs diff --git a/Huppy/Huppy.Core/Entities/GPTResponse.cs b/src/Huppy/Huppy.Core/Entities/GPTResponse.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/GPTResponse.cs rename to src/Huppy/Huppy.Core/Entities/GPTResponse.cs diff --git a/Huppy/Huppy.Core/Entities/NewsResponse.cs b/src/Huppy/Huppy.Core/Entities/NewsResponse.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/NewsResponse.cs rename to src/Huppy/Huppy.Core/Entities/NewsResponse.cs diff --git a/Huppy/Huppy.Core/Entities/ReminderInput.cs b/src/Huppy/Huppy.Core/Entities/ReminderInput.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/ReminderInput.cs rename to src/Huppy/Huppy.Core/Entities/ReminderInput.cs diff --git a/Huppy/Huppy.Core/Entities/TimedJob.cs b/src/Huppy/Huppy.Core/Entities/TimedJob.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/TimedJob.cs rename to src/Huppy/Huppy.Core/Entities/TimedJob.cs diff --git a/Huppy/Huppy.Core/Entities/UrbanResponse.cs b/src/Huppy/Huppy.Core/Entities/UrbanResponse.cs similarity index 100% rename from Huppy/Huppy.Core/Entities/UrbanResponse.cs rename to src/Huppy/Huppy.Core/Entities/UrbanResponse.cs diff --git a/Huppy/Huppy.Core/Extensions/MiddlewareExtensions.cs b/src/Huppy/Huppy.Core/Extensions/MiddlewareExtensions.cs similarity index 100% rename from Huppy/Huppy.Core/Extensions/MiddlewareExtensions.cs rename to src/Huppy/Huppy.Core/Extensions/MiddlewareExtensions.cs diff --git a/Huppy/Huppy.Core/Huppy.Core.csproj b/src/Huppy/Huppy.Core/Huppy.Core.csproj similarity index 60% rename from Huppy/Huppy.Core/Huppy.Core.csproj rename to src/Huppy/Huppy.Core/Huppy.Core.csproj index 4cc3219..a0f78fd 100644 --- a/Huppy/Huppy.Core/Huppy.Core.csproj +++ b/src/Huppy/Huppy.Core/Huppy.Core.csproj @@ -1,10 +1,17 @@ - + net6.0 enable enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -18,4 +25,7 @@ + + + \ No newline at end of file diff --git a/Huppy/Huppy.Core/Interfaces/IMiddleware.cs b/src/Huppy/Huppy.Core/Interfaces/IMiddleware.cs similarity index 80% rename from Huppy/Huppy.Core/Interfaces/IMiddleware.cs rename to src/Huppy/Huppy.Core/Interfaces/IMiddleware.cs index 5d504e3..d5e50da 100644 --- a/Huppy/Huppy.Core/Interfaces/IMiddleware.cs +++ b/src/Huppy/Huppy.Core/Interfaces/IMiddleware.cs @@ -1,3 +1,6 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; using Discord.Interactions; using Huppy.Kernel; diff --git a/Huppy/Huppy.Core/Interfaces/IPaginatorEntry.cs b/src/Huppy/Huppy.Core/Interfaces/IPaginatorEntry.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IPaginatorEntry.cs rename to src/Huppy/Huppy.Core/Interfaces/IPaginatorEntry.cs diff --git a/Huppy/Huppy.Core/Interfaces/IRepositories/ICommandLogRepository.cs b/src/Huppy/Huppy.Core/Interfaces/IRepositories/ICommandLogRepository.cs similarity index 95% rename from Huppy/Huppy.Core/Interfaces/IRepositories/ICommandLogRepository.cs rename to src/Huppy/Huppy.Core/Interfaces/IRepositories/ICommandLogRepository.cs index 0159e5f..dbc4724 100644 --- a/Huppy/Huppy.Core/Interfaces/IRepositories/ICommandLogRepository.cs +++ b/src/Huppy/Huppy.Core/Interfaces/IRepositories/ICommandLogRepository.cs @@ -4,6 +4,7 @@ namespace Huppy.Core.Interfaces.IRepositories { + [Obsolete] public interface ICommandLogRepository : IRepository { Task GetCount(); diff --git a/Huppy/Huppy.Core/Interfaces/IRepositories/IReminderRepository.cs b/src/Huppy/Huppy.Core/Interfaces/IRepositories/IReminderRepository.cs similarity index 95% rename from Huppy/Huppy.Core/Interfaces/IRepositories/IReminderRepository.cs rename to src/Huppy/Huppy.Core/Interfaces/IRepositories/IReminderRepository.cs index 1f60ac6..14d0990 100644 --- a/Huppy/Huppy.Core/Interfaces/IRepositories/IReminderRepository.cs +++ b/src/Huppy/Huppy.Core/Interfaces/IRepositories/IReminderRepository.cs @@ -3,6 +3,7 @@ namespace Huppy.Core.Interfaces.IRepositories { + [Obsolete] public interface IReminderRepository : IRepository { Task GetAsync(ulong userId, int id); diff --git a/Huppy/Huppy.Core/Interfaces/IRepositories/IServerRepository.cs b/src/Huppy/Huppy.Core/Interfaces/IRepositories/IServerRepository.cs similarity index 94% rename from Huppy/Huppy.Core/Interfaces/IRepositories/IServerRepository.cs rename to src/Huppy/Huppy.Core/Interfaces/IRepositories/IServerRepository.cs index 9f0b3d2..6a15052 100644 --- a/Huppy/Huppy.Core/Interfaces/IRepositories/IServerRepository.cs +++ b/src/Huppy/Huppy.Core/Interfaces/IRepositories/IServerRepository.cs @@ -4,6 +4,7 @@ namespace Huppy.Core.Interfaces.IRepositories { + [Obsolete] public interface IServerRepository : IRepository { Task GetOrCreateAsync(ShardedInteractionContext DiscordContext); diff --git a/Huppy/Huppy.Core/Interfaces/IRepositories/ITicketRepository.cs b/src/Huppy/Huppy.Core/Interfaces/IRepositories/ITicketRepository.cs similarity index 92% rename from Huppy/Huppy.Core/Interfaces/IRepositories/ITicketRepository.cs rename to src/Huppy/Huppy.Core/Interfaces/IRepositories/ITicketRepository.cs index 4ecbe69..7a7233d 100644 --- a/Huppy/Huppy.Core/Interfaces/IRepositories/ITicketRepository.cs +++ b/src/Huppy/Huppy.Core/Interfaces/IRepositories/ITicketRepository.cs @@ -3,6 +3,7 @@ namespace Huppy.Core.Interfaces.IRepositories { + [Obsolete] public interface ITicketRepository : IRepository { diff --git a/Huppy/Huppy.Core/Interfaces/IRepositories/IUserRepository.cs b/src/Huppy/Huppy.Core/Interfaces/IRepositories/IUserRepository.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IRepositories/IUserRepository.cs rename to src/Huppy/Huppy.Core/Interfaces/IRepositories/IUserRepository.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IActivityControlService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IActivityControlService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IActivityControlService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IActivityControlService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IAiStabilizer.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IAiStabilizer.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IAiStabilizer.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IAiStabilizer.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IAppMetadataService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IAppMetadataService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IAppMetadataService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IAppMetadataService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/ICommandHandlerService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/ICommandHandlerService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/ICommandHandlerService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/ICommandHandlerService.cs diff --git a/src/Huppy/Huppy.Core/Interfaces/IServices/ICommandLogService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/ICommandLogService.cs new file mode 100644 index 0000000..61b092d --- /dev/null +++ b/src/Huppy/Huppy.Core/Interfaces/IServices/ICommandLogService.cs @@ -0,0 +1,15 @@ +using Google.Protobuf.Collections; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; + +namespace Huppy.Core.Interfaces.IServices +{ + public interface ICommandLogService + { + Task GetCount(); + Task GetAverageExecutionTime(); + Task> GetAiUsage(); + Task AddCommand(CommandLogModel model); + Task RemoveCommand(CommandLogModel model); + } +} diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IEventLoopService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IEventLoopService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IEventLoopService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IEventLoopService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IGPTService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IGPTService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IGPTService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IGPTService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IJobManagerService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IJobManagerService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IJobManagerService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IJobManagerService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/INewsApiService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/INewsApiService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/INewsApiService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/INewsApiService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IPaginatorService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IPaginatorService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IPaginatorService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IPaginatorService.cs diff --git a/src/Huppy/Huppy.Core/Interfaces/IServices/IReminderService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IReminderService.cs new file mode 100644 index 0000000..0e86d30 --- /dev/null +++ b/src/Huppy/Huppy.Core/Interfaces/IServices/IReminderService.cs @@ -0,0 +1,22 @@ +using Discord; +using Huppy.Core.Models; +using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Huppy.Core.Interfaces.IServices +{ + public interface IReminderService + { + TimeSpan FetchReminderFrequency { get; } + Task RegisterFreshRemindersAsync(); + Task GetRemindersCount(ulong userId); + Task> GetSortedUserReminders(ulong userId, int skip, int take); + Task GetReminderAsync(ulong userId, int reminderId); + Task> GetUserRemindersAsync(ulong userId); + Task AddReminderAsync(DateTime date, ulong userId, string message); + Task AddReminderAsync(DateTime date, IUser user, string message); + Task RemoveReminderAsync(ReminderModel reminder); + Task RemoveReminderRangeAsync(string[] ids); + Task RemoveReminderRangeAsync(int[] ids); + } +} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IResourcesService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IResourcesService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IResourcesService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IResourcesService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IScopedDataService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IScopedDataService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IScopedDataService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IScopedDataService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IServerInteractionService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IServerInteractionService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IServerInteractionService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IServerInteractionService.cs diff --git a/src/Huppy/Huppy.Core/Interfaces/IServices/IServerService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IServerService.cs new file mode 100644 index 0000000..602cc6a --- /dev/null +++ b/src/Huppy/Huppy.Core/Interfaces/IServices/IServerService.cs @@ -0,0 +1,14 @@ +using Google.Protobuf.WellKnownTypes; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; + +namespace Huppy.Core.Interfaces.IServices +{ + public interface IServerService + { + public Task GetAsync(ulong serverId); + public Task GetOrCreateAsync(ulong serverId, string serverName, ulong defaultChannel); + public Task UpdateAsync(ServerModel server); + public Task GetAllAsync(Empty empty); + } +} diff --git a/src/Huppy/Huppy.Core/Interfaces/IServices/ITicketService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/ITicketService.cs new file mode 100644 index 0000000..db38835 --- /dev/null +++ b/src/Huppy/Huppy.Core/Interfaces/IServices/ITicketService.cs @@ -0,0 +1,20 @@ +using Discord; +using Huppy.Core.Models; +using HuppyService.Service.Protos.Models; + +namespace Huppy.Core.Interfaces.IServices +{ + public interface ITicketService + { + Task GetCountAsync(ulong userId); + Task> GetTicketsAsync(); + Task> GetTicketsAsync(ulong userId); + Task> GetPaginatedTickets(int skip, int take); + Task> GetPaginatedTickets(ulong userId, int skip, int take); + Task GetTicketAsync(string ticketId, ulong userId); + Task AddTicketAsync(IUser user, string topic, string description); + Task RemoveTicketAsync(string ticketId); + Task UpdateTicketAsync(string ticketId, string description); + Task CloseTicket(string ticketId, string answer); + } +} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Interfaces/IServices/ITimedEventsService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/ITimedEventsService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/ITimedEventsService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/ITimedEventsService.cs diff --git a/Huppy/Huppy.Core/Interfaces/IServices/IUrbanService.cs b/src/Huppy/Huppy.Core/Interfaces/IServices/IUrbanService.cs similarity index 100% rename from Huppy/Huppy.Core/Interfaces/IServices/IUrbanService.cs rename to src/Huppy/Huppy.Core/Interfaces/IServices/IUrbanService.cs diff --git a/Huppy/Huppy.Core/Models/CommandLog.cs b/src/Huppy/Huppy.Core/Models/CommandLog.cs similarity index 98% rename from Huppy/Huppy.Core/Models/CommandLog.cs rename to src/Huppy/Huppy.Core/Models/CommandLog.cs index 21bd96c..0792b2e 100644 --- a/Huppy/Huppy.Core/Models/CommandLog.cs +++ b/src/Huppy/Huppy.Core/Models/CommandLog.cs @@ -1,9 +1,10 @@ -using Huppy.Kernel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Huppy.Kernel; namespace Huppy.Core.Models { + [Obsolete] public class CommandLog : DbModel { [Key] diff --git a/Huppy/Huppy.Core/Models/Reminder.cs b/src/Huppy/Huppy.Core/Models/Reminder.cs similarity index 97% rename from Huppy/Huppy.Core/Models/Reminder.cs rename to src/Huppy/Huppy.Core/Models/Reminder.cs index 7c7d2bd..c2f871b 100644 --- a/Huppy/Huppy.Core/Models/Reminder.cs +++ b/src/Huppy/Huppy.Core/Models/Reminder.cs @@ -1,8 +1,9 @@ -using Huppy.Kernel; using System.ComponentModel.DataAnnotations.Schema; +using Huppy.Kernel; namespace Huppy.Core.Models { + [Obsolete] public class Reminder : DbModel { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] diff --git a/Huppy/Huppy.Core/Models/Server.cs b/src/Huppy/Huppy.Core/Models/Server.cs similarity index 97% rename from Huppy/Huppy.Core/Models/Server.cs rename to src/Huppy/Huppy.Core/Models/Server.cs index 5f6f6fa..28c7f4e 100644 --- a/Huppy/Huppy.Core/Models/Server.cs +++ b/src/Huppy/Huppy.Core/Models/Server.cs @@ -1,9 +1,10 @@ -using Huppy.Kernel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Huppy.Kernel; namespace Huppy.Core.Models { + [Obsolete] public class Server : DbModel { [Key] diff --git a/Huppy/Huppy.Core/Models/ServerRooms.cs b/src/Huppy/Huppy.Core/Models/ServerRooms.cs similarity index 97% rename from Huppy/Huppy.Core/Models/ServerRooms.cs rename to src/Huppy/Huppy.Core/Models/ServerRooms.cs index 326d89c..61ac71f 100644 --- a/Huppy/Huppy.Core/Models/ServerRooms.cs +++ b/src/Huppy/Huppy.Core/Models/ServerRooms.cs @@ -4,6 +4,7 @@ namespace Huppy.Core.Models { + [Obsolete] public class ServerRooms : DbModel { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] diff --git a/Huppy/Huppy.Core/Models/Ticket.cs b/src/Huppy/Huppy.Core/Models/Ticket.cs similarity index 97% rename from Huppy/Huppy.Core/Models/Ticket.cs rename to src/Huppy/Huppy.Core/Models/Ticket.cs index 1c30af1..bed58ba 100644 --- a/Huppy/Huppy.Core/Models/Ticket.cs +++ b/src/Huppy/Huppy.Core/Models/Ticket.cs @@ -1,9 +1,10 @@ -using Huppy.Kernel; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using Huppy.Kernel; namespace Huppy.Core.Models { + [Obsolete] public class Ticket : DbModel { [Key] diff --git a/Huppy/Huppy.Core/Models/User.cs b/src/Huppy/Huppy.Core/Models/User.cs similarity index 100% rename from Huppy/Huppy.Core/Models/User.cs rename to src/Huppy/Huppy.Core/Models/User.cs diff --git a/src/Huppy/Huppy.Core/README.md b/src/Huppy/Huppy.Core/README.md new file mode 100644 index 0000000..75f95ce --- /dev/null +++ b/src/Huppy/Huppy.Core/README.md @@ -0,0 +1,14 @@ +## gRPC services +- CommandLog +- GPT +- Reminder +- Server +- Ticket + +## Migration consideration +- ServerInteraction +- JobManager +- EventLoop +- App (?) +- AiStabilizer +- Activity \ No newline at end of file diff --git a/Huppy/Huppy.Core/Services/Activity/ActivityControlService.cs b/src/Huppy/Huppy.Core/Services/Activity/ActivityControlService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Activity/ActivityControlService.cs rename to src/Huppy/Huppy.Core/Services/Activity/ActivityControlService.cs diff --git a/Huppy/Huppy.Core/Services/AiStabilizer/AiStabilizer.cs b/src/Huppy/Huppy.Core/Services/AiStabilizer/AiStabilizer.cs similarity index 100% rename from Huppy/Huppy.Core/Services/AiStabilizer/AiStabilizer.cs rename to src/Huppy/Huppy.Core/Services/AiStabilizer/AiStabilizer.cs diff --git a/Huppy/Huppy.Core/Services/App/AppMetadataService.cs b/src/Huppy/Huppy.Core/Services/App/AppMetadataService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/App/AppMetadataService.cs rename to src/Huppy/Huppy.Core/Services/App/AppMetadataService.cs diff --git a/Huppy/Huppy.Core/Services/CommandHandler/CommandHandlerService.cs b/src/Huppy/Huppy.Core/Services/CommandHandler/CommandHandlerService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/CommandHandler/CommandHandlerService.cs rename to src/Huppy/Huppy.Core/Services/CommandHandler/CommandHandlerService.cs diff --git a/src/Huppy/Huppy.Core/Services/CommandLog/CommandLogService.cs b/src/Huppy/Huppy.Core/Services/CommandLog/CommandLogService.cs new file mode 100644 index 0000000..e962659 --- /dev/null +++ b/src/Huppy/Huppy.Core/Services/CommandLog/CommandLogService.cs @@ -0,0 +1,53 @@ +using System.Security.Authentication.ExtendedProtection; +using Google.Protobuf.WellKnownTypes; +using Huppy.Core.Interfaces.IServices; +using HuppyService.Service; +using HuppyService.Service.Protos.Models; + +namespace Huppy.Core.Services.CommandLog +{ + public class CommandLogService : ICommandLogService + { + private readonly HuppyService.Service.Protos.CommandLogProto.CommandLogProtoClient _commandLogClient; + public CommandLogService(HuppyService.Service.Protos.CommandLogProto.CommandLogProtoClient commandLogClient) + { + _commandLogClient = commandLogClient; + } + + public async Task AddCommand(CommandLogModel commandLog) + { + var result = await _commandLogClient.AddCommandAsync(commandLog); + + return result; + } + + public async Task> GetAiUsage() + { + // implement single instance of empty? + var result = await _commandLogClient.GetAiUsageAsync(new HuppyService.Service.Protos.Void()); + + return result.AiUsers; + } + + public async Task GetAverageExecutionTime() + { + var result = await _commandLogClient.GetAverageExecutionTimeAsync(new HuppyService.Service.Protos.Void()); + + return result.AverageTime; + } + + public async Task GetCount() + { + var result = await _commandLogClient.GetCountAsync(new HuppyService.Service.Protos.Void()); + + return result.Number; + } + + public async Task RemoveCommand(CommandLogModel commandLog) + { + var result = await _commandLogClient.RemoveCommandAsync(commandLog); + + return result.IsSuccess; + } + } +} diff --git a/Huppy/Huppy.Core/Services/EventLoop/EventLoopService.cs b/src/Huppy/Huppy.Core/Services/EventLoop/EventLoopService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/EventLoop/EventLoopService.cs rename to src/Huppy/Huppy.Core/Services/EventLoop/EventLoopService.cs diff --git a/src/Huppy/Huppy.Core/Services/GPT/GPTService.cs b/src/Huppy/Huppy.Core/Services/GPT/GPTService.cs new file mode 100644 index 0000000..8cadc47 --- /dev/null +++ b/src/Huppy/Huppy.Core/Services/GPT/GPTService.cs @@ -0,0 +1,41 @@ +using System.Net.Http.Json; +using System.Runtime.CompilerServices; +using Huppy.Core.Entities; +using Huppy.Core.Interfaces.IServices; +using Huppy.Kernel; +using Huppy.Kernel.Constants; +using HuppyService.Service.Protos; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; + +namespace Huppy.Core.Services.GPT +{ + public class GPTService : IGPTService + { + private readonly IHttpClientFactory _clientFactory; + private readonly ILogger _logger; + private readonly AppSettings _settings; + private readonly HuppyService.Service.Protos.GPTProto.GPTProtoClient _gptClient; + public GPTService(HuppyService.Service.Protos.GPTProto.GPTProtoClient gptClient, IHttpClientFactory clientFactory, ILogger logger, AppSettings settings) + { + _gptClient = gptClient; + _clientFactory = clientFactory; + _logger = logger; + _settings = settings; + } + + public async Task GetEngines() + { + var client = _clientFactory.CreateClient("GPT"); + var result = await client.GetAsync("https://api.openai.com/v1/engines"); + + _logger.LogInformation("{response}", await result.Content.ReadAsStringAsync()); + } + + public async Task DavinciCompletion(string prompt) + { + var result = await _gptClient.DavinciCompletionAsync(new GPTInputRequest() { Prompt = prompt }); + return result.Answer; + } + } +} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.AiUsage.cs b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.AiUsage.cs similarity index 59% rename from Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.AiUsage.cs rename to src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.AiUsage.cs index ce5b9af..26bef78 100644 --- a/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.AiUsage.cs +++ b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.AiUsage.cs @@ -1,4 +1,4 @@ -using Huppy.Core.Entities; +using HuppyService.Service.Protos; namespace Huppy.Core.Services.HuppyCacheStorage; @@ -9,19 +9,19 @@ public async Task LogUsageAsync(string Username, ulong UserId) await AddToCache(UserId, Username); } - public Task GetUserUsage(ulong UserId) + public Task GetUserUsage(ulong UserId) { - _userAiUsage.TryGetValue(UserId, out var user); - return Task.FromResult(user!); + _userAiUsage.TryGetValue(UserId, out var userUsage); + return Task.FromResult(userUsage!); } public Task GetCurrentMessageCount() { - var count = _userAiUsage.Sum(e => e.Value.Count); + var count = _userAiUsage.Sum(e => e.Value); return Task.FromResult(count); } - public Task> GetAiStatistics() + public Task> GetAiStatistics() { return Task.FromResult(_userAiUsage.ToDictionary(p => p.Key, p => p.Value)); } @@ -30,11 +30,11 @@ private Task AddToCache(ulong UserId, string Username) { if (_userAiUsage.TryGetValue(UserId, out var currentValue)) { - _userAiUsage[UserId].Count = currentValue.Count + 1; + _userAiUsage[UserId] = currentValue + 1; } else { - _userAiUsage.TryAdd(UserId, new AiUser { Username = Username, Count = 1 }); + _userAiUsage.TryAdd(UserId, 1); } return Task.CompletedTask; diff --git a/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Paginated.cs b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Paginated.cs similarity index 100% rename from Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Paginated.cs rename to src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Paginated.cs diff --git a/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Reminder.cs b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Reminder.cs new file mode 100644 index 0000000..f590a13 --- /dev/null +++ b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.Reminder.cs @@ -0,0 +1,7 @@ +namespace Huppy.Core.Services.HuppyCacheStorage +{ + public partial class CacheStorageService + { + public void UpdateNextReminderFetchingDate(DateTime newTime) => _nextReminderFetchingDate = newTime; + } +} \ No newline at end of file diff --git a/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.UserData.cs b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.UserData.cs similarity index 100% rename from Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.UserData.cs rename to src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.UserData.cs diff --git a/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.cs b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.cs similarity index 64% rename from Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.cs rename to src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.cs index 255c010..f28517c 100644 --- a/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.cs +++ b/src/Huppy/Huppy.Core/Services/HuppyCacheStorage/CacheStorageService.cs @@ -1,7 +1,10 @@ using System.Collections.Specialized; -using Huppy.Core.Entities; +using Google.Protobuf.WellKnownTypes; +//using Huppy.Core.Entities; using Huppy.Core.Interfaces.IRepositories; +using Huppy.Core.Interfaces.IServices; using Huppy.Kernel; +using HuppyService.Service.Protos; using Microsoft.Extensions.DependencyInjection; namespace Huppy.Core.Services.HuppyCacheStorage; @@ -14,8 +17,8 @@ public partial class CacheStorageService public IReadOnlyDictionary CacheUsers => _cacheUsers; private Dictionary _cacheUsers = null!; - public IReadOnlyDictionary UserAiUsage => _userAiUsage; - private Dictionary _userAiUsage = null!; + public IReadOnlyDictionary UserAiUsage => (IReadOnlyDictionary)_userAiUsage; + private IDictionary _userAiUsage = null!; // Switch to IReadOnlySet public HashSet RegisteredGuildsIds => _registeredGuildsIds; @@ -26,6 +29,9 @@ public partial class CacheStorageService public OrderedDictionary PaginatorEntries { get; private set; } = null!; + public DateTime NextReminderFetchingDate => _nextReminderFetchingDate; + private DateTime _nextReminderFetchingDate = default; + public CacheStorageService(IServiceScopeFactory serviceScopeFactory) { _serviceFactory = serviceScopeFactory; @@ -34,16 +40,19 @@ public CacheStorageService(IServiceScopeFactory serviceScopeFactory) public async Task Initialize() { using var scope = _serviceFactory.CreateAsyncScope(); - var commandRepository = scope.ServiceProvider.GetRequiredService(); + var commandLogService = scope.ServiceProvider.GetRequiredService(); var userRepository = scope.ServiceProvider.GetRequiredService(); - var serverRepository = scope.ServiceProvider.GetRequiredService(); + var serverService = scope.ServiceProvider.GetRequiredService(); var appSettings = scope.ServiceProvider.GetRequiredService(); + var aiUsage = await commandLogService.GetAiUsage(); + _cacheUsers = new(await userRepository.GetUsersForCacheAsync()); - _userAiUsage = new(await commandRepository.GetAiUsage()); - _registeredGuildsIds = new((await serverRepository.GetAllAsync()).Select(guild => guild.Id).ToHashSet()); + _userAiUsage = aiUsage; + _registeredGuildsIds = (await serverService.GetAllAsync(new Empty())).ServerModels.Select(guild => guild.Id).ToHashSet(); _developerIds = appSettings.Developers!.Split(';').Where(sId => !string.IsNullOrEmpty(sId)).Select(sId => ulong.Parse(sId)).ToHashSet(); PaginatorEntries = new(); + _nextReminderFetchingDate = DateTime.UtcNow; // TODO: configurable in appsettings SetMaxMessageCacheSize(MessageCacheSize); diff --git a/Huppy/Huppy.Core/Services/JobManager/JobManagerService.cs b/src/Huppy/Huppy.Core/Services/JobManager/JobManagerService.cs similarity index 99% rename from Huppy/Huppy.Core/Services/JobManager/JobManagerService.cs rename to src/Huppy/Huppy.Core/Services/JobManager/JobManagerService.cs index c37210f..56acf0b 100644 --- a/Huppy/Huppy.Core/Services/JobManager/JobManagerService.cs +++ b/src/Huppy/Huppy.Core/Services/JobManager/JobManagerService.cs @@ -64,7 +64,7 @@ public Task StartReminderJobs() Function = async (scope, data) => { var reminderService = scope.ServiceProvider.GetRequiredService(); - await reminderService.RegisterFreshReminders(); + await reminderService.RegisterFreshRemindersAsync(); } }; diff --git a/Huppy/Huppy.Core/Services/Logger/LoggingService.cs b/src/Huppy/Huppy.Core/Services/Logger/LoggingService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Logger/LoggingService.cs rename to src/Huppy/Huppy.Core/Services/Logger/LoggingService.cs diff --git a/Huppy/Huppy.Core/Services/MiddlewareExecutor/MiddlewareExecutorService.cs b/src/Huppy/Huppy.Core/Services/MiddlewareExecutor/MiddlewareExecutorService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/MiddlewareExecutor/MiddlewareExecutorService.cs rename to src/Huppy/Huppy.Core/Services/MiddlewareExecutor/MiddlewareExecutorService.cs diff --git a/Huppy/Huppy.Core/Services/Paginator/BuildStaticEmbeds.cs b/src/Huppy/Huppy.Core/Services/Paginator/BuildStaticEmbeds.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Paginator/BuildStaticEmbeds.cs rename to src/Huppy/Huppy.Core/Services/Paginator/BuildStaticEmbeds.cs diff --git a/Huppy/Huppy.Core/Services/Paginator/Entities/DynamicPaginatorEntry.cs b/src/Huppy/Huppy.Core/Services/Paginator/Entities/DynamicPaginatorEntry.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Paginator/Entities/DynamicPaginatorEntry.cs rename to src/Huppy/Huppy.Core/Services/Paginator/Entities/DynamicPaginatorEntry.cs diff --git a/Huppy/Huppy.Core/Services/Paginator/Entities/PaginatorPage.cs b/src/Huppy/Huppy.Core/Services/Paginator/Entities/PaginatorPage.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Paginator/Entities/PaginatorPage.cs rename to src/Huppy/Huppy.Core/Services/Paginator/Entities/PaginatorPage.cs diff --git a/Huppy/Huppy.Core/Services/Paginator/Entities/StaticPaginatorEntry.cs b/src/Huppy/Huppy.Core/Services/Paginator/Entities/StaticPaginatorEntry.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Paginator/Entities/StaticPaginatorEntry.cs rename to src/Huppy/Huppy.Core/Services/Paginator/Entities/StaticPaginatorEntry.cs diff --git a/Huppy/Huppy.Core/Services/Paginator/PaginatorService.cs b/src/Huppy/Huppy.Core/Services/Paginator/PaginatorService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Paginator/PaginatorService.cs rename to src/Huppy/Huppy.Core/Services/Paginator/PaginatorService.cs diff --git a/src/Huppy/Huppy.Core/Services/Reminder/ReminderService.cs b/src/Huppy/Huppy.Core/Services/Reminder/ReminderService.cs new file mode 100644 index 0000000..ea186dd --- /dev/null +++ b/src/Huppy/Huppy.Core/Services/Reminder/ReminderService.cs @@ -0,0 +1,169 @@ +using Discord; +using Discord.Interactions; +using Discord.WebSocket; +using Huppy.Core.Entities; +using Huppy.Core.Interfaces.IServices; +using Huppy.Core.Services.HuppyCacheStorage; +using Huppy.Core.Utilities; +using Huppy.Kernel.Constants; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; +using Microsoft.Extensions.Logging; + +namespace Huppy.Core.Services.Reminder; + +public class ReminderService : IReminderService +{ + private readonly ILogger _logger; + private readonly IEventLoopService _eventService; + private readonly InteractionService _interactionService; + private readonly ReminderProto.ReminderProtoClient _reminderClient; + private readonly CacheStorageService _cacheStorage; + private DateTime FetchingDate => DateTime.UtcNow + FetchReminderFrequency; + public TimeSpan FetchReminderFrequency { get; } = new(0, 0, 30); + public ReminderService(IEventLoopService eventService, ILogger logger, DiscordShardedClient discord, InteractionService interactionService, ITimedEventsService timedEventsService, ReminderProto.ReminderProtoClient reminderClient, CacheStorageService cacheStorage) + { + _eventService = eventService; + _logger = logger; + _interactionService = interactionService; + _reminderClient = reminderClient; + _cacheStorage = cacheStorage; + } + + public async Task> GetSortedUserReminders(ulong userId, int skip, int take) + { + var result = await _reminderClient.GetSortedUserRemindersAsync(new() { UserId = userId, Skip = skip, Take = take }); + return result.ReminderModels; + } + + public async Task GetRemindersCount(ulong userId) + { + var result = await _reminderClient.GetRemindersCountAsync(new HuppyService.Service.Protos.UserId() { Id = userId }); + return result.Number; + } + + public async Task GetReminderAsync(ulong userId, int reminderId) + { + return await _reminderClient.GetReminderAsync(new GetReminderInput() { ReminderId = reminderId, UserId = userId }); + } + + public async Task RegisterFreshRemindersAsync() + { + var currentFetchingDate = FetchingDate; + _cacheStorage.UpdateNextReminderFetchingDate(currentFetchingDate); + + var reminders = await _reminderClient.GetReminderBatchAsync(new ReminderBatchInput() + { + EndDate = Miscellaneous.DateTimeToUnixTimestamp(currentFetchingDate) + }); + + _logger.LogInformation("Registering fresh bulk of reminders"); + + if (!reminders.ReminderModels.Any()) return; + + // start adding reminder in async parallel manner + var jobs = reminders.ReminderModels.Select(reminder => Task.Run(async () => + { + // fetch user + var user = await GetUser(reminder.UserId); + + // add reminder + ReminderInput reminderInput = new() { User = user, Message = reminder.Message }; + await _eventService.AddEvent(Miscellaneous.UnixTimeStampToUtcDateTime(reminder.RemindDate), reminder.Id.ToString(), reminderInput, async (input) => + { + if (input is ReminderInput data) + { + await StandardReminder(data.User, data.Message!); + } + }); + })).ToList(); + + await Task.WhenAll(jobs); + + _logger.LogInformation("Enqueued {count} of reminders to execute until {time}", reminders.ReminderModels.Count, currentFetchingDate); + } + + public async Task> GetUserRemindersAsync(ulong userId) + { + return (await _reminderClient.GetUserRemindersAsync(new HuppyService.Service.Protos.UserId() { Id = userId })).ReminderModels; + } + + public async Task AddReminderAsync(DateTime date, ulong userId, string message) + { + await AddReminderAsync(date, await GetUser(userId), message); + } + + public async Task AddReminderAsync(DateTime date, IUser user, string message) + { + date = date.ToUniversalTime(); + ReminderModel reminder = new() + { + Message = message, + RemindDate = Miscellaneous.DateTimeToUnixTimestamp(date), + UserId = user.Id + }; + + reminder = await _reminderClient.AddReminderAsync(reminder); + + if (reminder.Id == 0) throw new Exception("Failed to create reminder"); + + ReminderInput reminderInput = new() { User = user, Message = reminder.Message }; + + _logger.LogInformation("Added reminder for [{user}] at [{date}] UTC", user.Username, date); + + // with error margin + if (date < _cacheStorage.NextReminderFetchingDate - new TimeSpan(0, 0, 5)) + { + await _eventService.AddEvent(date, reminder.Id.ToString()!, reminderInput, async (input) => + { + if (input is ReminderInput data) + { + data.Message ??= ""; + await StandardReminder(data.User, data.Message); + } + }); + } + } + + public async Task RemoveReminderAsync(ReminderModel reminder) + { + var result = await _reminderClient.RemoveReminderAsync(reminder); + + if (result.IsSuccess) throw new Exception($"Failed to remove reminder {reminder.Id}"); + + await _eventService.Remove(Miscellaneous.UnixTimeStampToUtcDateTime(reminder.RemindDate), reminder.Id.ToString()); + } + + public async Task RemoveReminderRangeAsync(string[] ids) + { + if (ids.Length <= 0) return; + + int[] castedIds = ids.Select(int.Parse).ToArray(); + await RemoveReminderRangeAsync(castedIds); + } + + public async Task RemoveReminderRangeAsync(int[] ids) + { + RemoveReminderRangeInput request = new(); + request.Ids.AddRange(ids); + + await _reminderClient.RemoveReminderRangeAsync(request); + } + + private async Task StandardReminder(IUser user, string message) + { + var embed = new EmbedBuilder() + .WithTitle("Your reminder") + .WithColor(Color.Teal) + .WithThumbnailUrl(Icons.Huppy1) + .WithDescription(message) + .WithCurrentTimestamp() + .Build(); + + await user.SendMessageAsync("", false, embed); + + _logger.LogInformation("Sent reminder to [{user}]", user.Username); + } + + private async Task GetUser(ulong userId) => await _interactionService.RestClient.GetUserAsync(userId); +} diff --git a/Huppy/Huppy.Core/Services/Resources/ResourcesService.cs b/src/Huppy/Huppy.Core/Services/Resources/ResourcesService.cs similarity index 84% rename from Huppy/Huppy.Core/Services/Resources/ResourcesService.cs rename to src/Huppy/Huppy.Core/Services/Resources/ResourcesService.cs index b957b84..dba89e4 100644 --- a/Huppy/Huppy.Core/Services/Resources/ResourcesService.cs +++ b/src/Huppy/Huppy.Core/Services/Resources/ResourcesService.cs @@ -9,11 +9,11 @@ namespace Huppy.Core.Services.Resources; public class ResourcesService : IResourcesService { private readonly DiscordShardedClient _client; - private readonly ICommandLogRepository _commandRepository; - public ResourcesService(DiscordShardedClient client, ICommandLogRepository commandRepository) + private readonly ICommandLogService _commandLogService; + public ResourcesService(DiscordShardedClient client, ICommandLogService commandLogService) { _client = client; - _commandRepository = commandRepository; + _commandLogService = commandLogService; } public Task GetBotVersionAsync() @@ -40,10 +40,9 @@ public async Task GetCpuUsageAsync() public async Task GetAverageExecutionTimeAsync() { // TODO don't include debug commands - var commandsQuery = await _commandRepository.GetAllAsync(); - var avgTime = await commandsQuery.AverageAsync(e => e.ExecutionTimeMs); + var averageTime = await _commandLogService.GetAverageExecutionTime(); - return Math.Round(avgTime, 2); + return Math.Round(averageTime, 2); } public string GetRamUsage() diff --git a/Huppy/Huppy.Core/Services/ScopedData/ScopedDataService.cs b/src/Huppy/Huppy.Core/Services/ScopedData/ScopedDataService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/ScopedData/ScopedDataService.cs rename to src/Huppy/Huppy.Core/Services/ScopedData/ScopedDataService.cs diff --git a/src/Huppy/Huppy.Core/Services/Server/ServerService.cs b/src/Huppy/Huppy.Core/Services/Server/ServerService.cs new file mode 100644 index 0000000..e2f4f75 --- /dev/null +++ b/src/Huppy/Huppy.Core/Services/Server/ServerService.cs @@ -0,0 +1,47 @@ +using Google.Protobuf.WellKnownTypes; +using Huppy.Core.Interfaces.IServices; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; + +namespace Huppy.Core.Services.Server +{ + public class ServerService : IServerService + { + private readonly ServerProto.ServerProtoClient _serverClient; + public ServerService(ServerProto.ServerProtoClient serverClient) + { + _serverClient = serverClient; + } + + public async Task GetAllAsync(Empty empty) + { + var result = await _serverClient.GetAllAsync(new HuppyService.Service.Protos.Void()); + return result; + } + + public async Task GetAsync(ulong serverId) + { + var result = await _serverClient.GetAsync(new HuppyService.Service.Protos.ServerId() { Id = serverId }); + return result; + } + + public async Task GetOrCreateAsync(ulong serverId, string serverName, ulong defaultChannel) + { + var result = await _serverClient.GetOrCreateAsync(new GetOrCreateServerInput() + { + Id = serverId, + ServerName = serverName, + DefaultChannel = defaultChannel + }); + + return result; + } + + public async Task UpdateAsync(ServerModel server) + { + var updateResult = await _serverClient.UpdateAsync(server); + + return updateResult; + } + } +} diff --git a/Huppy/Huppy.Core/Services/ServerInteraction/ServerInteractionService.cs b/src/Huppy/Huppy.Core/Services/ServerInteraction/ServerInteractionService.cs similarity index 60% rename from Huppy/Huppy.Core/Services/ServerInteraction/ServerInteractionService.cs rename to src/Huppy/Huppy.Core/Services/ServerInteraction/ServerInteractionService.cs index 70b5471..e3e65f1 100644 --- a/Huppy/Huppy.Core/Services/ServerInteraction/ServerInteractionService.cs +++ b/src/Huppy/Huppy.Core/Services/ServerInteraction/ServerInteractionService.cs @@ -25,46 +25,46 @@ public ServerInteractionService(ILogger logger, IServi public async Task HuppyJoined(SocketGuild guild) { - var embed = new EmbedBuilder().WithTitle("✨ Hello I'm Huppy! ✨") - .WithColor(Color.Teal) - .WithDescription(HuppyBasicMessages.AboutMe) - .WithThumbnailUrl(Icons.Huppy1) - .WithCurrentTimestamp(); - - if (!_cacheService.RegisteredGuildsIds.Contains(guild.Id)) - { - using var scope = _serviceFactory.CreateAsyncScope(); - var serverRepository = scope.ServiceProvider.GetRequiredService(); - - Server server = new() - { - Id = guild.Id, - GreetMessage = "Welcome {username}!", - Rooms = new() - { - OutputRoom = guild.DefaultChannel.Id, - GreetingRoom = 0 - }, - ServerName = guild.Name, - RoleID = 0, - UseGreet = false, - }; - - await serverRepository.AddAsync(server); - await serverRepository.SaveChangesAsync(); - } - - await guild.DefaultChannel.SendMessageAsync(null, false, embed.Build()); + //var embed = new EmbedBuilder().WithTitle("✨ Hello I'm Huppy! ✨") + // .WithColor(Color.Teal) + // .WithDescription(HuppyBasicMessages.AboutMe) + // .WithThumbnailUrl(Icons.Huppy1) + // .WithCurrentTimestamp(); + + //if (!_cacheService.RegisteredGuildsIds.Contains(guild.Id)) + //{ + // using var scope = _serviceFactory.CreateAsyncScope(); + // var serverRepository = scope.ServiceProvider.GetRequiredService(); + + // Server server = new() + // { + // Id = guild.Id, + // GreetMessage = "Welcome {username}!", + // Rooms = new() + // { + // OutputRoom = guild.DefaultChannel.Id, + // GreetingRoom = 0 + // }, + // ServerName = guild.Name, + // RoleID = 0, + // UseGreet = false, + // }; + + // await serverRepository.AddAsync(server); + // await serverRepository.SaveChangesAsync(); + //} + + //await guild.DefaultChannel.SendMessageAsync(null, false, embed.Build()); } public async Task OnUserJoined(SocketGuildUser user) { using var scope = _serviceFactory.CreateAsyncScope(); - var serverRepository = scope.ServiceProvider.GetRequiredService(); + var serverService = scope.ServiceProvider.GetRequiredService(); _logger.LogInformation("New user joined [{Username}] at [{ServerName}]", user.Username, user.Guild.Name); - var server = await serverRepository.GetAsync(user.Guild.Id); + var server = await serverService.GetAsync(user.Guild.Id); if (server is not null) { if (server.UseGreet) @@ -84,20 +84,19 @@ public async Task OnUserJoined(SocketGuildUser user) await channel.SendMessageAsync(null, false, embed.Build()); } - if (server.RoleID > 0) + if (server.RoleId > 0) { - var role = user.Guild.GetRole(server.RoleID); + var role = user.Guild.GetRole(server.RoleId); if (role is null) { - _logger.LogWarning("Role with [{RoleID}] ID on [{ServerName}] is not found. Updating default role to none", server.RoleID, user.Guild.Name); - server.RoleID = default; + _logger.LogWarning("Role with [{RoleID}] ID on [{ServerName}] is not found. Updating default role to none", server.RoleId, user.Guild.Name); + server.RoleId = default; - await serverRepository.UpdateAsync(server); - await serverRepository.SaveChangesAsync(); + await serverService.UpdateAsync(server); return; } - await (user as IGuildUser).AddRoleAsync(server.RoleID); + await (user as IGuildUser).AddRoleAsync(server.RoleId); } } diff --git a/src/Huppy/Huppy.Core/Services/Ticket/TicketService.cs b/src/Huppy/Huppy.Core/Services/Ticket/TicketService.cs new file mode 100644 index 0000000..1e72931 --- /dev/null +++ b/src/Huppy/Huppy.Core/Services/Ticket/TicketService.cs @@ -0,0 +1,90 @@ +using System.Reflection.Metadata.Ecma335; +using Discord; +using Huppy.Core.Interfaces.IRepositories; +using Huppy.Core.Interfaces.IServices; +using Huppy.Core.Models; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; +using Microsoft.AspNetCore.Authentication; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Internal; + +namespace Huppy.Core.Services.Ticket; +public class TicketService : ITicketService +{ + private readonly TicketProto.TicketProtoClient _ticketClient; + public TicketService(TicketProto.TicketProtoClient ticketClient) + { + _ticketClient = ticketClient; + } + + public async Task GetCountAsync(ulong userId) + { + var result = await _ticketClient.GetCountAsync(new() { Id = userId }); + + return result.Number; + } + + public async Task> GetTicketsAsync() + { + var result = await _ticketClient.GetTicketsAsync(new()); + return result.TicketsModels; + } + + public async Task> GetTicketsAsync(ulong userId) + { + var tickets = await _ticketClient.GetUserTicketsAsync(new() { Id = userId }); + return tickets.TicketsModels; + } + + public async Task> GetPaginatedTickets(int skip, int take) + { + var tickets = await _ticketClient.GetPaginatedTicketsAsync(new() { Skip = skip, Take = take }); + + return tickets.TicketsModels; + } + + public async Task> GetPaginatedTickets(ulong userId, int skip, int take) + { + var tickets = await _ticketClient.GetUserPaginatedTicketsAsync(new() { Skip = skip, Take = take, UserId = userId }); + + return tickets.TicketsModels; + } + + public async Task GetTicketAsync(string ticketId, ulong userId) + { + var ticket = await _ticketClient.GetTicketAsync(new() { TicketId = ticketId, UserId = userId }); + return ticket; + } + + public async Task AddTicketAsync(IUser user, string topic, string description) + { + if (string.IsNullOrEmpty(description)) throw new ArgumentException("Ticket description cannot be null"); + + var result = await _ticketClient.AddTicketAsync(new() { UserId = user.Id, Description = description, Topic = topic }); + return result; + } + + public async Task RemoveTicketAsync(string ticketId) + { + if (string.IsNullOrEmpty(ticketId)) throw new ArgumentException("Ticket cannot be null or empty"); + + var result = await _ticketClient.RemoveTicketAsync(new() { Id = ticketId }); + } + + public async Task UpdateTicketAsync(string ticketId, string description) + { + if (string.IsNullOrEmpty(ticketId) || string.IsNullOrEmpty(description)) + throw new ArgumentException("Both ticked ID and ticket description cannot be null or empty"); + + var result = await _ticketClient.UpdateTicketAsync(new() { TicketId = ticketId, Description = description }); + } + + public async Task CloseTicket(string ticketId, string answer) + { + if (string.IsNullOrEmpty(ticketId)) + throw new ArgumentException("Ticked ID cannot be empty"); + + _ = await _ticketClient.CloseTicketAsync(new() { TicketId = ticketId, Answer = answer }); + } +} diff --git a/Huppy/Huppy.Core/Services/TimedEvents/TimedEventsService.cs b/src/Huppy/Huppy.Core/Services/TimedEvents/TimedEventsService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/TimedEvents/TimedEventsService.cs rename to src/Huppy/Huppy.Core/Services/TimedEvents/TimedEventsService.cs diff --git a/Huppy/Huppy.Core/Services/Urban/UrbanService.cs b/src/Huppy/Huppy.Core/Services/Urban/UrbanService.cs similarity index 100% rename from Huppy/Huppy.Core/Services/Urban/UrbanService.cs rename to src/Huppy/Huppy.Core/Services/Urban/UrbanService.cs diff --git a/Huppy/Huppy.Core/Utilities/ExtendedConsole.cs b/src/Huppy/Huppy.Core/Utilities/ExtendedConsole.cs similarity index 100% rename from Huppy/Huppy.Core/Utilities/ExtendedConsole.cs rename to src/Huppy/Huppy.Core/Utilities/ExtendedConsole.cs diff --git a/Huppy/Huppy.Core/Utilities/Logo.cs b/src/Huppy/Huppy.Core/Utilities/Logo.cs similarity index 100% rename from Huppy/Huppy.Core/Utilities/Logo.cs rename to src/Huppy/Huppy.Core/Utilities/Logo.cs diff --git a/src/Huppy/Huppy.Core/Utilities/Miscellaneous.cs b/src/Huppy/Huppy.Core/Utilities/Miscellaneous.cs new file mode 100644 index 0000000..cff21d5 --- /dev/null +++ b/src/Huppy/Huppy.Core/Utilities/Miscellaneous.cs @@ -0,0 +1,19 @@ +namespace Huppy.Core.Utilities +{ + public static class Miscellaneous + { + public static ulong DateTimeToUnixTimestamp(DateTime dateTime) + { + return (ulong)(TimeZoneInfo.ConvertTimeToUtc(dateTime) - + new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc)).TotalSeconds; + } + + public static DateTime UnixTimeStampToUtcDateTime(ulong unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dateTime = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(unixTimeStamp); + return dateTime; + } + } +} diff --git a/Huppy/Huppy.Core/Utilities/SerilogConfigurator.cs b/src/Huppy/Huppy.Core/Utilities/SerilogConfigurator.cs similarity index 100% rename from Huppy/Huppy.Core/Utilities/SerilogConfigurator.cs rename to src/Huppy/Huppy.Core/Utilities/SerilogConfigurator.cs diff --git a/Huppy/Huppy.Infrastructure/Huppy.Infrastructure.csproj b/src/Huppy/Huppy.Infrastructure/Huppy.Infrastructure.csproj similarity index 100% rename from Huppy/Huppy.Infrastructure/Huppy.Infrastructure.csproj rename to src/Huppy/Huppy.Infrastructure/Huppy.Infrastructure.csproj diff --git a/src/Huppy/Huppy.Infrastructure/HuppyDbContext.cs b/src/Huppy/Huppy.Infrastructure/HuppyDbContext.cs new file mode 100644 index 0000000..84eae0b --- /dev/null +++ b/src/Huppy/Huppy.Infrastructure/HuppyDbContext.cs @@ -0,0 +1,29 @@ +using Huppy.Core.Models; +using Microsoft.EntityFrameworkCore; + +namespace Huppy.Infrastructure +{ + [Obsolete] + public class HuppyDbContext : DbContext + { + public HuppyDbContext() { } + public HuppyDbContext(DbContextOptions options) : base(options) { } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { } + + protected override void OnModelCreating(ModelBuilder builder) + { + // builder.Entity() + // .HasOne(e => e.Rooms) + // .WithOne(e => e.Server) + // .HasForeignKey(k => k.Id) + // .OnDelete(DeleteBehavior.Cascade); + } + + public DbSet Users { get; set; } + // public DbSet CommandLogs { get; set; } + // public DbSet Servers { get; set; } + // public DbSet Reminders { get; set; } + // public DbSet Tickets { get; set; } + } +} \ No newline at end of file diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220219114052_init.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220164656_User-CommandLog.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220173409_NewCommandLogger.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220222705_ServerInfo.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220220222848_ServerInfov2.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220221155025_default role.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220224193748_News.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220315234754_Rooms table.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220803193508_Removed News.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220803194759_Removed News cleaning.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220804184917_ReminderUpdate.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220804191505_ReminderModelUpdate.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220812153538_Executiontime.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220812160129_CommandLogExtension.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820210141_Tickets.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820221513_TicketsUpdate.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820222655_TicketsKeyUpdate.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220820224856_TicketTopic.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.Designer.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.Designer.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.Designer.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.Designer.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.cs b/src/Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/20220821221306_KeysRename.cs diff --git a/Huppy/Huppy.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs b/src/Huppy/Huppy.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs rename to src/Huppy/Huppy.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs diff --git a/Huppy/Huppy.Infrastructure/Repositories/UserRepository.cs b/src/Huppy/Huppy.Infrastructure/Repositories/UserRepository.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Repositories/UserRepository.cs rename to src/Huppy/Huppy.Infrastructure/Repositories/UserRepository.cs diff --git a/Huppy/Huppy.Infrastructure/Setup.cs b/src/Huppy/Huppy.Infrastructure/Setup.cs similarity index 100% rename from Huppy/Huppy.Infrastructure/Setup.cs rename to src/Huppy/Huppy.Infrastructure/Setup.cs diff --git a/Huppy/Huppy.Kernel/Abstraction/BaseRepository.cs b/src/Huppy/Huppy.Kernel/Abstraction/BaseRepository.cs similarity index 99% rename from Huppy/Huppy.Kernel/Abstraction/BaseRepository.cs rename to src/Huppy/Huppy.Kernel/Abstraction/BaseRepository.cs index 8fa3db4..7d42f77 100644 --- a/Huppy/Huppy.Kernel/Abstraction/BaseRepository.cs +++ b/src/Huppy/Huppy.Kernel/Abstraction/BaseRepository.cs @@ -2,6 +2,7 @@ namespace Huppy.Kernel.Abstraction; +[Obsolete] public class BaseRepository : IRepository where TKeyType : IConvertible where TEntity : DbModel, new() diff --git a/Huppy/Huppy.Kernel/Abstraction/DbModel.cs b/src/Huppy/Huppy.Kernel/Abstraction/DbModel.cs similarity index 92% rename from Huppy/Huppy.Kernel/Abstraction/DbModel.cs rename to src/Huppy/Huppy.Kernel/Abstraction/DbModel.cs index e313344..c503b1f 100644 --- a/Huppy/Huppy.Kernel/Abstraction/DbModel.cs +++ b/src/Huppy/Huppy.Kernel/Abstraction/DbModel.cs @@ -2,7 +2,7 @@ namespace Huppy.Kernel { - + [Obsolete] public class DbModel where TKey : IConvertible { public virtual TKey Id { get; set; } diff --git a/Huppy/Huppy.Kernel/Abstraction/IRepository.cs b/src/Huppy/Huppy.Kernel/Abstraction/IRepository.cs similarity index 97% rename from Huppy/Huppy.Kernel/Abstraction/IRepository.cs rename to src/Huppy/Huppy.Kernel/Abstraction/IRepository.cs index ec7663a..4100c0c 100644 --- a/Huppy/Huppy.Kernel/Abstraction/IRepository.cs +++ b/src/Huppy/Huppy.Kernel/Abstraction/IRepository.cs @@ -1,5 +1,6 @@ namespace Huppy.Kernel { + [Obsolete] public interface IRepository where Tkey : IConvertible where TEntity : DbModel { Task GetAsync(Tkey id); diff --git a/Huppy/Huppy.Kernel/AppSettings.cs b/src/Huppy/Huppy.Kernel/AppSettings.cs similarity index 91% rename from Huppy/Huppy.Kernel/AppSettings.cs rename to src/Huppy/Huppy.Kernel/AppSettings.cs index 1f3fbe6..33da3b9 100644 --- a/Huppy/Huppy.Kernel/AppSettings.cs +++ b/src/Huppy/Huppy.Kernel/AppSettings.cs @@ -11,8 +11,9 @@ public class AppSettings public string? BetaTestingGuilds { get; set; } public string? ConnectionString { get; set; } public Logger? Logger { get; set; } - public GPT? GPT { get; set; } + public GPTSettings? GPT { get; set; } public UrbanApi? UrbanApi { get; set; } + public Microservices? Microservices { get; set; } [JsonIgnore] private readonly static string FILE_NAME = AppContext.BaseDirectory + "appsettings.json"; @@ -67,7 +68,8 @@ public static AppSettings Create() BaseUrl = "https://mashape-community-urban-dictionary.p.rapidapi.com/define", Host = "mashape-community-urban-dictionary.p.rapidapi.com", Key = "" - } + }, + Microservices = new() }; JsonSerializerOptions options = new() @@ -87,13 +89,18 @@ private static void CreateFolders() } } + public class Microservices + { + public string? HuppyCoreUrl { get; set; } + } + public class Logger { public string? LogLevel { get; set; } public string? TimeInterval { get; set; } } - public class GPT + public class GPTSettings { public string? BaseUrl { get; set; } public string? ApiKey { get; set; } diff --git a/Huppy/Huppy.Kernel/Constants/GPTEndpoints.cs b/src/Huppy/Huppy.Kernel/Constants/GPTEndpoints.cs similarity index 91% rename from Huppy/Huppy.Kernel/Constants/GPTEndpoints.cs rename to src/Huppy/Huppy.Kernel/Constants/GPTEndpoints.cs index 9167b46..e0c15c8 100644 --- a/Huppy/Huppy.Kernel/Constants/GPTEndpoints.cs +++ b/src/Huppy/Huppy.Kernel/Constants/GPTEndpoints.cs @@ -1,5 +1,6 @@ namespace Huppy.Kernel.Constants { + [Obsolete] public class GPTEndpoints { public const string TextDavinciCompletions = "text-davinci-001/completions"; diff --git a/Huppy/Huppy.Kernel/Constants/HuppyBasicMessages.cs b/src/Huppy/Huppy.Kernel/Constants/HuppyBasicMessages.cs similarity index 100% rename from Huppy/Huppy.Kernel/Constants/HuppyBasicMessages.cs rename to src/Huppy/Huppy.Kernel/Constants/HuppyBasicMessages.cs diff --git a/Huppy/Huppy.Kernel/Constants/Icons.cs b/src/Huppy/Huppy.Kernel/Constants/Icons.cs similarity index 100% rename from Huppy/Huppy.Kernel/Constants/Icons.cs rename to src/Huppy/Huppy.Kernel/Constants/Icons.cs diff --git a/Huppy/Huppy.Kernel/Constants/SerilogConstants.cs b/src/Huppy/Huppy.Kernel/Constants/SerilogConstants.cs similarity index 100% rename from Huppy/Huppy.Kernel/Constants/SerilogConstants.cs rename to src/Huppy/Huppy.Kernel/Constants/SerilogConstants.cs diff --git a/Huppy/Huppy.Kernel/Enums/StaticEmbeds.cs b/src/Huppy/Huppy.Kernel/Enums/StaticEmbeds.cs similarity index 100% rename from Huppy/Huppy.Kernel/Enums/StaticEmbeds.cs rename to src/Huppy/Huppy.Kernel/Enums/StaticEmbeds.cs diff --git a/Huppy/Huppy.Kernel/ExtendedShardedInteractionContext.cs b/src/Huppy/Huppy.Kernel/ExtendedShardedInteractionContext.cs similarity index 100% rename from Huppy/Huppy.Kernel/ExtendedShardedInteractionContext.cs rename to src/Huppy/Huppy.Kernel/ExtendedShardedInteractionContext.cs diff --git a/Huppy/Huppy.Kernel/Huppy.Kernel.csproj b/src/Huppy/Huppy.Kernel/Huppy.Kernel.csproj similarity index 100% rename from Huppy/Huppy.Kernel/Huppy.Kernel.csproj rename to src/Huppy/Huppy.Kernel/Huppy.Kernel.csproj diff --git a/Huppy/Huppy.sln b/src/Huppy/Huppy.sln similarity index 82% rename from Huppy/Huppy.sln rename to src/Huppy/Huppy.sln index 2e6fd87..e7082d9 100644 --- a/Huppy/Huppy.sln +++ b/src/Huppy/Huppy.sln @@ -1,24 +1,21 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.App", "Huppy.App\Huppy.App.csproj", "{63A53A9E-3A99-47A7-BC56-757C4C484789}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Huppy.App", "Huppy.App\Huppy.App.csproj", "{63A53A9E-3A99-47A7-BC56-757C4C484789}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.Core", "Huppy.Core\Huppy.Core.csproj", "{24DBA258-A21A-4C73-B2E4-1F095F6629D2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Huppy.Core", "Huppy.Core\Huppy.Core.csproj", "{24DBA258-A21A-4C73-B2E4-1F095F6629D2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.Infrastructure", "Huppy.Infrastructure\Huppy.Infrastructure.csproj", "{63DCCC50-7380-445C-AF41-3DBD2A7FE953}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Huppy.Infrastructure", "Huppy.Infrastructure\Huppy.Infrastructure.csproj", "{63DCCC50-7380-445C-AF41-3DBD2A7FE953}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.Kernel", "Huppy.Kernel\Huppy.Kernel.csproj", "{DFC4F96F-AAD1-406B-B9C7-21CC2FAF19DE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Huppy.Kernel", "Huppy.Kernel\Huppy.Kernel.csproj", "{DFC4F96F-AAD1-406B-B9C7-21CC2FAF19DE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {63A53A9E-3A99-47A7-BC56-757C4C484789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {63A53A9E-3A99-47A7-BC56-757C4C484789}.Debug|Any CPU.Build.0 = Debug|Any CPU @@ -37,4 +34,10 @@ Global {DFC4F96F-AAD1-406B-B9C7-21CC2FAF19DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFC4F96F-AAD1-406B-B9C7-21CC2FAF19DE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {36D3F2A9-4A13-4B10-AF99-49448449FF3D} + EndGlobalSection EndGlobal diff --git a/Huppy/scripts/CreateMigration.bat b/src/Huppy/scripts/CreateMigration.bat similarity index 100% rename from Huppy/scripts/CreateMigration.bat rename to src/Huppy/scripts/CreateMigration.bat diff --git a/src/HuppyService/HuppyService.Core/Abstraction/BaseRepository.cs b/src/HuppyService/HuppyService.Core/Abstraction/BaseRepository.cs new file mode 100644 index 0000000..e7a6140 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Abstraction/BaseRepository.cs @@ -0,0 +1,91 @@ +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Core.Abstraction; + +public class BaseRepository : IRepository + where TKeyType : IConvertible + where TEntity : DbModel, new() + where TContext : DbContext, new() +{ + protected internal readonly TContext _context; + public BaseRepository(TContext context) + { + _context = context ?? new TContext(); + } + + public virtual async Task AddAsync(TEntity? entity) + { + if (entity is null) return false; + + var doesExist = await _context.Set().AnyAsync(entry => entry.Id.Equals(entity.Id)); + + if (doesExist) return false; + + await _context.Set().AddAsync(entity); + return true; + } + + public virtual async Task AddRangeAsync(IEnumerable entities) + { + if (entities is null) return false; + + await _context.Set().AddRangeAsync(entities); + return true; + } + + public virtual Task> GetAllAsync() + { + return Task.FromResult(_context.Set().AsQueryable()); + } + + public virtual async Task GetAsync(TKeyType id) + { + return await _context.Set().FirstOrDefaultAsync(entry => entry.Id.Equals(id)); + } + + public virtual async Task RemoveAsync(TKeyType id) + { + TEntity entity = new() { Id = id }; + + var doesExist = await _context.Set().AnyAsync(entry => entry.Id.Equals(entity.Id)); + + if (!doesExist) return false; + + _context.Set().Remove(entity); + + return true; + } + + public virtual async Task RemoveAsync(TEntity? entity) + { + if (entity is null) return false; + + var doesExist = await _context.Set().AnyAsync(entry => entry.Id.Equals(entity.Id)); + if (!doesExist) return false; + + _context.Set().Remove(entity); + + return true; + } + + public virtual Task UpdateAsync(TEntity? entity) + { + if (entity is null) return Task.CompletedTask; + + _context.Set().Update(entity); + + return Task.CompletedTask; + } + + public virtual Task UpdateRange(IEnumerable entities) + { + _context.Set().UpdateRange(entities); + + return Task.CompletedTask; + } + + public virtual async Task SaveChangesAsync() + { + await _context.SaveChangesAsync(); + } +} diff --git a/src/HuppyService/HuppyService.Core/Abstraction/DbModel.cs b/src/HuppyService/HuppyService.Core/Abstraction/DbModel.cs new file mode 100644 index 0000000..ea4809e --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Abstraction/DbModel.cs @@ -0,0 +1,10 @@ +using System.ComponentModel.DataAnnotations; + +namespace HuppyService.Core.Abstraction +{ + + public class DbModel where TKey : IConvertible + { + public virtual TKey Id { get; set; } + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Abstraction/IRepository.cs b/src/HuppyService/HuppyService.Core/Abstraction/IRepository.cs new file mode 100644 index 0000000..cf043ce --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Abstraction/IRepository.cs @@ -0,0 +1,16 @@ + +namespace HuppyService.Core.Abstraction +{ + public interface IRepository where Tkey : IConvertible where TEntity : DbModel + { + Task GetAsync(Tkey id); + Task> GetAllAsync(); + Task AddAsync(TEntity? entity); + Task AddRangeAsync(IEnumerable entities); + Task RemoveAsync(Tkey id); + Task RemoveAsync(TEntity? entity); + Task UpdateAsync(TEntity? entity); + Task UpdateRange(IEnumerable entities); + Task SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Constants/GPTEndpoints.cs b/src/HuppyService/HuppyService.Core/Constants/GPTEndpoints.cs new file mode 100644 index 0000000..31a03d8 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Constants/GPTEndpoints.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Constants +{ + public class GPTEndpoints + { + public const string TextDavinciCompletions = "text-davinci-001/completions"; + } +} diff --git a/src/HuppyService/HuppyService.Core/Entities/AiUser.cs b/src/HuppyService/HuppyService.Core/Entities/AiUser.cs new file mode 100644 index 0000000..5d02e58 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Entities/AiUser.cs @@ -0,0 +1,8 @@ +namespace HuppyService.Core.Entities +{ + public class AiUser + { + public string? Username; + public int Count; + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Entities/GPTRequest.cs b/src/HuppyService/HuppyService.Core/Entities/GPTRequest.cs new file mode 100644 index 0000000..0db4cae --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Entities/GPTRequest.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace HuppyService.Core.Entities +{ + public class GPTRequest + { + [JsonPropertyName("prompt")] + public string? Prompt { get; set; } + + [JsonPropertyName("max_tokens")] + public int MaxTokens { get; set; } + + [JsonPropertyName("temperature")] + public double Temperature { get; set; } + + [JsonPropertyName("top_p")] + public int TopP { get; set; } + + [JsonPropertyName("n")] + public int N { get; set; } + + [JsonPropertyName("stream")] + public bool Stream { get; set; } + + [JsonPropertyName("logprobs")] + public object? Logprobs { get; set; } + + [JsonPropertyName("stop")] + public string[]? Stop { get; set; } + + [JsonPropertyName("frequency_penalty")] + public double FrequencyPenalty { get; set; } + + [JsonPropertyName("presence_penalty")] + public double PresencePenalty { get; set; } + } +} diff --git a/src/HuppyService/HuppyService.Core/Entities/GPTResponse.cs b/src/HuppyService/HuppyService.Core/Entities/GPTResponse.cs new file mode 100644 index 0000000..4025a1a --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Entities/GPTResponse.cs @@ -0,0 +1,37 @@ +using System.Text.Json.Serialization; + +namespace HuppyService.Core.Entities +{ + public class Choice + { + [JsonPropertyName("text")] + public string? Text { get; set; } + + [JsonPropertyName("index")] + public int Index { get; set; } + + [JsonPropertyName("logprobs")] + public object? Logprobs { get; set; } + + [JsonPropertyName("finish_reason")] + public string? FinishReason { get; set; } + } + + public class GPTResponse + { + [JsonPropertyName("id")] + public string? Id { get; set; } + + [JsonPropertyName("object")] + public string? Object { get; set; } + + [JsonPropertyName("created")] + public int Created { get; set; } + + [JsonPropertyName("model")] + public string? Model { get; set; } + + [JsonPropertyName("choices")] + public List? Choices { get; set; } + } +} diff --git a/src/HuppyService/HuppyService.Core/Entities/Options/GPTOptions.cs b/src/HuppyService/HuppyService.Core/Entities/Options/GPTOptions.cs new file mode 100644 index 0000000..0114ab1 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Entities/Options/GPTOptions.cs @@ -0,0 +1,12 @@ +namespace HuppyService.Core.Entities.Options +{ + public class GPTOptions + { + public string? BaseUrl { get; set; } + public string? ApiKey { get; set; } + public string? Orgranization { get; set; } + public int FreeMessageQuota { get; set; } + public string? AiContextMessage { get; set; } + public bool IsEnabled { get; set; } + } +} diff --git a/src/HuppyService/HuppyService.Core/Entities/Options/MiscellaneousOptions.cs b/src/HuppyService/HuppyService.Core/Entities/Options/MiscellaneousOptions.cs new file mode 100644 index 0000000..ac4f915 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Entities/Options/MiscellaneousOptions.cs @@ -0,0 +1,8 @@ +namespace HuppyService.Core.Entities.Options +{ + public class MiscellaneousOptions + { + public string? BotToken { get; set; } + public string? DatabaseConnectionString { get; set; } + } +} diff --git a/src/HuppyService/HuppyService.Core/Entities/Options/UrbanApiOptions.cs b/src/HuppyService/HuppyService.Core/Entities/Options/UrbanApiOptions.cs new file mode 100644 index 0000000..42f9efc --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Entities/Options/UrbanApiOptions.cs @@ -0,0 +1,9 @@ +namespace HuppyService.Core.Entities.Options +{ + public class UrbanApiOptions + { + public string? BaseUrl { get; set; } + public string? Host { get; set; } + public string? Key { get; set; } + } +} diff --git a/src/HuppyService/HuppyService.Core/HuppyService.Core.csproj b/src/HuppyService/HuppyService.Core/HuppyService.Core.csproj new file mode 100644 index 0000000..43b6288 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/HuppyService.Core.csproj @@ -0,0 +1,16 @@ + + + + net6.0 + enable + enable + + + + + + + + + + diff --git a/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/ICommandLogRepository.cs b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/ICommandLogRepository.cs new file mode 100644 index 0000000..a59982c --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/ICommandLogRepository.cs @@ -0,0 +1,11 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Entities; +using HuppyService.Core.Models; + +namespace HuppyService.Core.Interfaces.IRepositories +{ + public interface ICommandLogRepository : IRepository + { + + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IReminderRepository.cs b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IReminderRepository.cs new file mode 100644 index 0000000..380240c --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IReminderRepository.cs @@ -0,0 +1,11 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Models; + +namespace HuppyService.Core.Interfaces.IRepositories +{ + public interface IReminderRepository : IRepository + { + Task GetAsync(ulong userId, int id); + Task RemoveRangeAsync(ICollection reminderIds); + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IServerRepository.cs b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IServerRepository.cs new file mode 100644 index 0000000..2630b20 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IServerRepository.cs @@ -0,0 +1,9 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Models; + +namespace HuppyService.Core.Interfaces.IRepositories +{ + public interface IServerRepository : IRepository + { + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/ITicketRepository.cs b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/ITicketRepository.cs new file mode 100644 index 0000000..793824e --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/ITicketRepository.cs @@ -0,0 +1,10 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Models; + +namespace HuppyService.Core.Interfaces.IRepositories +{ + public interface ITicketRepository : IRepository + { + + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IUserRepository.cs b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IUserRepository.cs new file mode 100644 index 0000000..cf99327 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Interfaces/IRepositories/IUserRepository.cs @@ -0,0 +1,10 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Models; + +namespace HuppyService.Core.Interfaces.IRepositories +{ + //public interface IUserRepository : IRepository + //{ + // Task> GetUsersForCacheAsync(); + //} +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Models/CommandLog.cs b/src/HuppyService/HuppyService.Core/Models/CommandLog.cs new file mode 100644 index 0000000..167c4ff --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Models/CommandLog.cs @@ -0,0 +1,25 @@ +using HuppyService.Core.Abstraction; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace HuppyService.Core.Models; + +public class CommandLog : DbModel +{ + [Key] + public override int Id { get; set; } + public string? CommandName { get; set; } + public string? ErrorMessage { get; set; } + public DateTime Date { get; set; } + public bool IsSuccess { get; set; } + public long ExecutionTimeMs { get; set; } + public ulong ChannelId { get; set; } + + //[ForeignKey("UserId")] + public ulong UserId { get; set; } + //public virtual User? User { get; set; } + + [ForeignKey("GuildId")] + public ulong? GuildId { get; set; } + public virtual Server? Guild { get; set; } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Models/Reminder.cs b/src/HuppyService/HuppyService.Core/Models/Reminder.cs new file mode 100644 index 0000000..7195b68 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Models/Reminder.cs @@ -0,0 +1,17 @@ +using HuppyService.Core.Abstraction; +using System.ComponentModel.DataAnnotations.Schema; + +namespace HuppyService.Core.Models +{ + public class Reminder : DbModel + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public override int Id { get; set; } + public DateTime RemindDate { get; set; } + public string Message { get; set; } = null!; + + //[ForeignKey("UserId")] + public ulong UserId { get; set; } + //public virtual User? User { get; set; } = null!; + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Models/Server.cs b/src/HuppyService/HuppyService.Core/Models/Server.cs new file mode 100644 index 0000000..035470a --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Models/Server.cs @@ -0,0 +1,21 @@ +using HuppyService.Core.Abstraction; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace HuppyService.Core.Models +{ + public class Server : DbModel + { + [Key] + public override ulong Id { get; set; } + public string? ServerName { get; set; } + public bool UseGreet { get; set; } + public string? GreetMessage { get; set; } + public ulong RoleID { get; set; } + + [ForeignKey("ServerRoomsId")] + public ulong ServerRoomsId { get; set; } + public virtual ServerRooms? Rooms { get; set; } + public virtual IList? CommangLogs { get; set; } + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Models/ServerRooms.cs b/src/HuppyService/HuppyService.Core/Models/ServerRooms.cs new file mode 100644 index 0000000..e323684 --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Models/ServerRooms.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using HuppyService.Core.Abstraction; + +namespace HuppyService.Core.Models +{ + public class ServerRooms : DbModel + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + public override ulong Id { get; set; } + public ulong OutputRoom { get; set; } + public ulong GreetingRoom { get; set; } + + [ForeignKey("ServerId")] + public ulong ServerId { get; set; } + public virtual Server? Server { get; set; } = null!; + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Models/Ticket.cs b/src/HuppyService/HuppyService.Core/Models/Ticket.cs new file mode 100644 index 0000000..2f13a4d --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Models/Ticket.cs @@ -0,0 +1,22 @@ +using HuppyService.Core.Abstraction; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace HuppyService.Core.Models +{ + public class Ticket : DbModel + { + [Key] + public override string Id { get; set; } = null!; + public string Topic { get; set; } = null!; + public string Description { get; set; } = null!; + public bool IsClosed { get; set; } + public string? TicketAnswer { get; set; } + public DateTime CreatedDate { get; set; } + public DateTime? ClosedDate { get; set; } + + //[ForeignKey("UserId")] + public ulong UserId { get; set; } + //public virtual User User { get; set; } = null!; + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Models/User.cs b/src/HuppyService/HuppyService.Core/Models/User.cs new file mode 100644 index 0000000..687472d --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Models/User.cs @@ -0,0 +1,13 @@ +using HuppyService.Core.Abstraction; +using System.ComponentModel.DataAnnotations; + +namespace HuppyService.Core.Models +{ + //public class User : DbModel + //{ + // [Key] + // public override ulong Id { get; set; } + // public string? Username { get; set; } + // public DateTime? JoinDate { get; set; } + //} +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Core/Utilities/Miscellaneous.cs b/src/HuppyService/HuppyService.Core/Utilities/Miscellaneous.cs new file mode 100644 index 0000000..9df00dc --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Utilities/Miscellaneous.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Utilities +{ + public static class Miscellaneous + { + public static DateTime UnixTimeStampToUtcDateTime(ulong unixTimeStamp) + { + // Unix timestamp is seconds past epoch + DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dateTime = dateTime.AddSeconds(unixTimeStamp); + return dateTime; + } + + public static ulong DateTimeToUnixTimeStamp(DateTime? date) + { + date ??= default; + return (ulong)(TimeZoneInfo.ConvertTimeToUtc((DateTime)date) - + new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc)).TotalSeconds; + } + + public static bool IsDebug() + { +#if DEBUG + return true; +#else + return false; +#endif + } + } +} diff --git a/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs b/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs new file mode 100644 index 0000000..4ae5b1d --- /dev/null +++ b/src/HuppyService/HuppyService.Core/Utilities/ReflectionMapper.cs @@ -0,0 +1,81 @@ +using HuppyService.Core.Abstraction; +using Microsoft.EntityFrameworkCore.Metadata.Conventions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace HuppyService.Core.Utilities +{ + public static class ReflectionMapper + { + public static T Map(object input) where T : class, new() + { + T result = new(); + + var tProps = result.GetType().GetProperties(); + var inputProps = input.GetType().GetProperties().ToDictionary(e => e.Name, e => e); + + foreach (var prop in tProps) + { + inputProps.TryGetValue(prop.Name, out PropertyInfo? matchingProp); + + if (matchingProp is null) continue; + + var inputPropInstance = matchingProp.GetValue(input, null); + + prop.SetValue(result, GetMappedValue(prop.PropertyType, inputPropInstance)); + } + + return result; + } + + public static ICollection? Map(object[] input) where T :class, new() + { + var result = typeof(List<>).MakeGenericType(typeof(T)); + + var tProps = result.GetType().GetProperties(); + var inputProps = input.GetType().GetProperties().ToDictionary(e => e.Name, e => e); + + foreach (var inputObject in input) + { + T objectToAdd = new(); + foreach (var prop in tProps) + { + inputProps.TryGetValue(prop.Name, out PropertyInfo? matchingProp); + if (matchingProp is null) continue; + var inputPropInstance = matchingProp.GetValue(inputObject, null); + + prop.SetValue(objectToAdd, GetMappedValue(prop.PropertyType, inputPropInstance)); + } + + ((ICollection)result).Add(objectToAdd); + } + + return result as ICollection; + } + + public static object? GetMappedValue(Type newValue, object? inputValue) + { + if (inputValue is null) return default; + + switch (inputValue) + { + case DateTime: + if (newValue == typeof(ulong)) + return Miscellaneous.DateTimeToUnixTimeStamp((DateTime)inputValue); + break; + case ulong: + if (newValue == typeof(DateTime)) + return Miscellaneous.UnixTimeStampToUtcDateTime((ulong)inputValue); + break; + default: + return inputValue; + }; + + return inputValue; + } + } +} diff --git a/Huppy/Huppy.Infrastructure/HuppyDbContext.cs b/src/HuppyService/HuppyService.Infrastructure/HuppyDbContext.cs similarity index 74% rename from Huppy/Huppy.Infrastructure/HuppyDbContext.cs rename to src/HuppyService/HuppyService.Infrastructure/HuppyDbContext.cs index 07de2f8..882a6b1 100644 --- a/Huppy/Huppy.Infrastructure/HuppyDbContext.cs +++ b/src/HuppyService/HuppyService.Infrastructure/HuppyDbContext.cs @@ -1,7 +1,7 @@ -using Huppy.Core.Models; +using HuppyService.Core.Models; using Microsoft.EntityFrameworkCore; -namespace Huppy.Infrastructure +namespace HuppyService.Infrastructure { public class HuppyDbContext : DbContext { @@ -17,9 +17,14 @@ protected override void OnModelCreating(ModelBuilder builder) .WithOne(e => e.Server) .HasForeignKey(k => k.Id) .OnDelete(DeleteBehavior.Cascade); + + builder.Entity() + .HasMany(e => e.CommangLogs) + .WithOne(e => e.Guild) + .OnDelete(DeleteBehavior.Cascade); } - public DbSet Users { get; set; } + //public DbSet Users { get; set; } public DbSet CommandLogs { get; set; } public DbSet Servers { get; set; } public DbSet Reminders { get; set; } diff --git a/src/HuppyService/HuppyService.Infrastructure/HuppyService.Infrastructure.csproj b/src/HuppyService/HuppyService.Infrastructure/HuppyService.Infrastructure.csproj new file mode 100644 index 0000000..bb8f088 --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/HuppyService.Infrastructure.csproj @@ -0,0 +1,20 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + diff --git a/src/HuppyService/HuppyService.Infrastructure/Migrations/20221006172417_init.Designer.cs b/src/HuppyService/HuppyService.Infrastructure/Migrations/20221006172417_init.Designer.cs new file mode 100644 index 0000000..3ec7ce6 --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Migrations/20221006172417_init.Designer.cs @@ -0,0 +1,189 @@ +// +using System; +using HuppyService.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HuppyService.Infrastructure.Migrations +{ + [DbContext(typeof(HuppyDbContext))] + [Migration("20221006172417_init")] + partial class init + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.9"); + + modelBuilder.Entity("HuppyService.Core.Models.CommandLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("CommandName") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("ExecutionTimeMs") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("IsSuccess") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("CommandLogs"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RemindDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GreetMessage") + .HasColumnType("TEXT"); + + b.Property("RoleID") + .HasColumnType("INTEGER"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.Property("ServerRoomsId") + .HasColumnType("INTEGER"); + + b.Property("UseGreet") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.ServerRooms", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GreetingRoom") + .HasColumnType("INTEGER"); + + b.Property("OutputRoom") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerRooms"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Ticket", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClosedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("TicketAnswer") + .HasColumnType("TEXT"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.CommandLog", b => + { + b.HasOne("HuppyService.Core.Models.Server", "Guild") + .WithMany("CommangLogs") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Guild"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.ServerRooms", b => + { + b.HasOne("HuppyService.Core.Models.Server", "Server") + .WithOne("Rooms") + .HasForeignKey("HuppyService.Core.Models.ServerRooms", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Server", b => + { + b.Navigation("CommangLogs"); + + b.Navigation("Rooms"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HuppyService/HuppyService.Infrastructure/Migrations/20221006172417_init.cs b/src/HuppyService/HuppyService.Infrastructure/Migrations/20221006172417_init.cs new file mode 100644 index 0000000..6c343fb --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Migrations/20221006172417_init.cs @@ -0,0 +1,133 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HuppyService.Infrastructure.Migrations +{ + public partial class init : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Reminders", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RemindDate = table.Column(type: "TEXT", nullable: false), + Message = table.Column(type: "TEXT", nullable: false), + UserId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Reminders", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Servers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ServerName = table.Column(type: "TEXT", nullable: true), + UseGreet = table.Column(type: "INTEGER", nullable: false), + GreetMessage = table.Column(type: "TEXT", nullable: true), + RoleID = table.Column(type: "INTEGER", nullable: false), + ServerRoomsId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Servers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Tickets", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Topic = table.Column(type: "TEXT", nullable: false), + Description = table.Column(type: "TEXT", nullable: false), + IsClosed = table.Column(type: "INTEGER", nullable: false), + TicketAnswer = table.Column(type: "TEXT", nullable: true), + CreatedDate = table.Column(type: "TEXT", nullable: false), + ClosedDate = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tickets", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "CommandLogs", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CommandName = table.Column(type: "TEXT", nullable: true), + ErrorMessage = table.Column(type: "TEXT", nullable: true), + Date = table.Column(type: "TEXT", nullable: true), + IsSuccess = table.Column(type: "INTEGER", nullable: false), + ExecutionTimeMs = table.Column(type: "INTEGER", nullable: false), + ChannelId = table.Column(type: "INTEGER", nullable: false), + UserId = table.Column(type: "INTEGER", nullable: false), + GuildId = table.Column(type: "INTEGER", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_CommandLogs", x => x.Id); + table.ForeignKey( + name: "FK_CommandLogs_Servers_GuildId", + column: x => x.GuildId, + principalTable: "Servers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ServerRooms", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + OutputRoom = table.Column(type: "INTEGER", nullable: false), + GreetingRoom = table.Column(type: "INTEGER", nullable: false), + ServerId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ServerRooms", x => x.Id); + table.ForeignKey( + name: "FK_ServerRooms_Servers_Id", + column: x => x.Id, + principalTable: "Servers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_CommandLogs_GuildId", + table: "CommandLogs", + column: "GuildId"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "CommandLogs"); + + migrationBuilder.DropTable( + name: "Reminders"); + + migrationBuilder.DropTable( + name: "ServerRooms"); + + migrationBuilder.DropTable( + name: "Tickets"); + + migrationBuilder.DropTable( + name: "Servers"); + } + } +} diff --git a/src/HuppyService/HuppyService.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs b/src/HuppyService/HuppyService.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs new file mode 100644 index 0000000..082119d --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Migrations/HuppyDbContextModelSnapshot.cs @@ -0,0 +1,187 @@ +// +using System; +using HuppyService.Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace HuppyService.Infrastructure.Migrations +{ + [DbContext(typeof(HuppyDbContext))] + partial class HuppyDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "6.0.9"); + + modelBuilder.Entity("HuppyService.Core.Models.CommandLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelId") + .HasColumnType("INTEGER"); + + b.Property("CommandName") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("ExecutionTimeMs") + .HasColumnType("INTEGER"); + + b.Property("GuildId") + .HasColumnType("INTEGER"); + + b.Property("IsSuccess") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GuildId"); + + b.ToTable("CommandLogs"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Reminder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Message") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RemindDate") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Reminders"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GreetMessage") + .HasColumnType("TEXT"); + + b.Property("RoleID") + .HasColumnType("INTEGER"); + + b.Property("ServerName") + .HasColumnType("TEXT"); + + b.Property("ServerRoomsId") + .HasColumnType("INTEGER"); + + b.Property("UseGreet") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.ServerRooms", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("GreetingRoom") + .HasColumnType("INTEGER"); + + b.Property("OutputRoom") + .HasColumnType("INTEGER"); + + b.Property("ServerId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerRooms"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Ticket", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ClosedDate") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("IsClosed") + .HasColumnType("INTEGER"); + + b.Property("TicketAnswer") + .HasColumnType("TEXT"); + + b.Property("Topic") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.CommandLog", b => + { + b.HasOne("HuppyService.Core.Models.Server", "Guild") + .WithMany("CommangLogs") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Guild"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.ServerRooms", b => + { + b.HasOne("HuppyService.Core.Models.Server", "Server") + .WithOne("Rooms") + .HasForeignKey("HuppyService.Core.Models.ServerRooms", "Id") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Server"); + }); + + modelBuilder.Entity("HuppyService.Core.Models.Server", b => + { + b.Navigation("CommangLogs"); + + b.Navigation("Rooms"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/HuppyService/HuppyService.Infrastructure/Repositories/CommandLogRepository.cs b/src/HuppyService/HuppyService.Infrastructure/Repositories/CommandLogRepository.cs new file mode 100644 index 0000000..63172ea --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Repositories/CommandLogRepository.cs @@ -0,0 +1,13 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Entities; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Infrastructure.Repositories +{ + public class CommandLogRepository : BaseRepository, ICommandLogRepository + { + public CommandLogRepository(HuppyDbContext context) : base(context) { } + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Infrastructure/Repositories/ReminderRepository.cs b/src/HuppyService/HuppyService.Infrastructure/Repositories/ReminderRepository.cs new file mode 100644 index 0000000..9539278 --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Repositories/ReminderRepository.cs @@ -0,0 +1,28 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Infrastructure.Repositories; + +public class ReminderRepository : BaseRepository, IReminderRepository +{ + public ReminderRepository(HuppyDbContext context) : base(context) { } + + public async Task GetAsync(ulong userId, int id) + { + return await _context.Reminders.FirstOrDefaultAsync(e => e.Id == id && e.UserId == userId); + } + + public async Task RemoveRangeAsync(ICollection reminderIds) + { + if (reminderIds is null) return; + + var reminders = await _context.Reminders + .Where(reminder => reminderIds.Contains(reminder.Id)) + .ToListAsync(); + + _context.Reminders.RemoveRange(reminders); + await _context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Infrastructure/Repositories/ServerRepository.cs b/src/HuppyService/HuppyService.Infrastructure/Repositories/ServerRepository.cs new file mode 100644 index 0000000..9d0775b --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Repositories/ServerRepository.cs @@ -0,0 +1,16 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Infrastructure.Repositories; + +public class ServerRepository : BaseRepository, IServerRepository +{ + public ServerRepository(HuppyDbContext context) : base(context) { } + + public override async Task GetAsync(ulong id) + { + return await _context.Servers.Include(e => e.Rooms).FirstOrDefaultAsync(entry => entry.Id == id); + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Infrastructure/Repositories/TicketRepository.cs b/src/HuppyService/HuppyService.Infrastructure/Repositories/TicketRepository.cs new file mode 100644 index 0000000..77bbfa9 --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Repositories/TicketRepository.cs @@ -0,0 +1,10 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; + +namespace HuppyService.Infrastructure.Repositories; + +public class TicketRepository : BaseRepository, ITicketRepository +{ + public TicketRepository(HuppyDbContext context) : base(context) { } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Infrastructure/Repositories/UserRepository.cs b/src/HuppyService/HuppyService.Infrastructure/Repositories/UserRepository.cs new file mode 100644 index 0000000..47e9a28 --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Repositories/UserRepository.cs @@ -0,0 +1,14 @@ +using HuppyService.Core.Abstraction; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Infrastructure.Repositories; + +//public class UserRepository : BaseRepository, IUserRepository +//{ +// public UserRepository(HuppyDbContext context) : base(context) { } + +// public async Task> GetUsersForCacheAsync() => +// await _context.Users?.ToDictionaryAsync(p => p.Id, p => p.Username)!; +//} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Infrastructure/Setup.cs b/src/HuppyService/HuppyService.Infrastructure/Setup.cs new file mode 100644 index 0000000..6df950a --- /dev/null +++ b/src/HuppyService/HuppyService.Infrastructure/Setup.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace HuppyService.Infrastructure; + +public static class Setup +{ + public static void AddDbContext(this IServiceCollection services, string ConnectionString) + { + services.AddDbContext( + options => options.UseSqlite(ConnectionString, x => x.MigrationsAssembly("HuppyService.Infrastructure")) + ); + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/AppExtensions.cs b/src/HuppyService/HuppyService.Service/AppExtensions.cs new file mode 100644 index 0000000..7ed8b4b --- /dev/null +++ b/src/HuppyService/HuppyService.Service/AppExtensions.cs @@ -0,0 +1,17 @@ +using HuppyService.Service.Configuration; + +namespace HuppyService.Service +{ + public static class AppExtensions + { + public static async Task UseAppConfigurator(this WebApplication app) + { + var appConfigurator = app.Services.GetRequiredService(); + + appConfigurator.SetApp(app); + appConfigurator.MapGrpcServices(); + + await appConfigurator.UseDatabaseMigrator(); + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Configuration/AppConfigurator.cs b/src/HuppyService/HuppyService.Service/Configuration/AppConfigurator.cs new file mode 100644 index 0000000..2e8d65c --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Configuration/AppConfigurator.cs @@ -0,0 +1,47 @@ +using HuppyService.Infrastructure; +using HuppyService.Service.Services; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Service.Configuration +{ + public class AppConfigurator + { + private readonly IServiceScopeFactory _scopeFactory; + private readonly ILogger _logger; + private WebApplication? _app; + + public AppConfigurator(IServiceScopeFactory scopeFactory, ILogger logger) + { + _scopeFactory = scopeFactory; + _logger = logger; + } + + public void SetApp(WebApplication app) => _app = app; + + public async Task UseDatabaseMigrator() + { + using var scope = _scopeFactory.CreateAsyncScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + _logger.LogInformation("Creating database"); + + if (!Directory.Exists(Path.Combine(AppContext.BaseDirectory, "save"))) + Directory.CreateDirectory(Path.Combine(AppContext.BaseDirectory, "save")); + + await dbContext.Database.MigrateAsync(); + } + + public void MapGrpcServices() + { + if (_app is not null) + { + _app.MapGrpcService(); + _app.MapGrpcService(); + _app.MapGrpcService(); + _app.MapGrpcService(); + _app.MapGrpcService(); + _app.MapGrpcService(); + } + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Configuration/MappingProfile.cs b/src/HuppyService/HuppyService.Service/Configuration/MappingProfile.cs new file mode 100644 index 0000000..055979a --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Configuration/MappingProfile.cs @@ -0,0 +1,60 @@ +using AutoMapper; +using HuppyService.Core.Models; +using HuppyService.Core.Utilities; +using HuppyService.Service.Protos.Models; + +namespace HuppyService.Service.Configuration +{ + public class MappingProfile : Profile + { + public MappingProfile() + { + CreateMap() + .ForSourceMember(dest => dest.Guild, + opt => opt.DoNotValidate()) + .ForMember(dest => dest.Date, + opt => opt.MapFrom(src => Miscellaneous.DateTimeToUnixTimeStamp(src.Date))); + + CreateMap() + .ForMember(dest => dest.Guild, + opt => opt.Ignore()) + .ForMember(dest => dest.Date, + opt => opt.MapFrom(src => Miscellaneous.UnixTimeStampToUtcDateTime(src.Date))); + + CreateMap() + .ForMember(dest => dest.Server, + opt => opt.Ignore()) + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.CommangLogs, + opt => opt.Ignore()); + + CreateMap() + .ForSourceMember(dest => dest.CommangLogs, + opt => opt.DoNotValidate()); + + CreateMap() + .ForMember(dest => dest.RemindDate, + opt => opt.MapFrom(src => Miscellaneous.UnixTimeStampToUtcDateTime(src.RemindDate))); + + CreateMap() + .ForMember(dest => dest.RemindDate, + opt => opt.MapFrom(src => Miscellaneous.DateTimeToUnixTimeStamp(src.RemindDate))); + + CreateMap() + .ForMember(dest => dest.CreatedDate, + opt => opt.MapFrom(src => Miscellaneous.UnixTimeStampToUtcDateTime(src.CreatedDate))) + .ForMember(dest => dest.ClosedDate, + opt => opt.MapFrom(src => Miscellaneous.UnixTimeStampToUtcDateTime(src.ClosedDate))); + + CreateMap() + .ForMember(dest => dest.CreatedDate, + opt => opt.MapFrom(src => Miscellaneous.DateTimeToUnixTimeStamp(src.CreatedDate))) + .ForMember(dest => dest.ClosedDate, + opt => opt.MapFrom(src => Miscellaneous.DateTimeToUnixTimeStamp(src.ClosedDate))); + + AllowNullDestinationValues = false; + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Configuration/ModuleConfiguration.cs b/src/HuppyService/HuppyService.Service/Configuration/ModuleConfiguration.cs new file mode 100644 index 0000000..fd07092 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Configuration/ModuleConfiguration.cs @@ -0,0 +1,90 @@ +using AutoMapper; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using HuppyService.Core.Utilities; +using HuppyService.Infrastructure; +using HuppyService.Infrastructure.Repositories; +using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore.Scaffolding; +using Microsoft.Extensions.Configuration; + +namespace HuppyService.Service.Configuration +{ + public class ModuleConfiguration + { + private readonly IServiceCollection _services; + private readonly IConfiguration _config; + public ModuleConfiguration(IConfiguration config, IServiceCollection? services = null) + { + _services = services ?? new ServiceCollection(); + _config = config; + } + + public ModuleConfiguration AddAppConfigurator() + { + _services.AddSingleton(); + + return this; + } + + public ModuleConfiguration ConfigureMappings() + { + var mapperConfig = new MapperConfiguration(conf => + { + conf.AddProfile(new MappingProfile()); + }); + + + if(Miscellaneous.IsDebug()) mapperConfig.AssertConfigurationIsValid(); + + var mapper = mapperConfig.CreateMapper(); + + _services.AddSingleton(mapper); + + return this; + } + + public ModuleConfiguration AddGrpc() + { + _services.AddGrpc(); + return this; + } + + public ModuleConfiguration AddDatabase() + { + _services.AddDbContext(_config["MiscellaneousOptions:ConnectionString"]); + return this; + } + + public ModuleConfiguration AddRepositories() + { + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + _services.AddScoped(); + + return this; + } + + public ModuleConfiguration AddHttpClients() + { + _services.AddHttpClient("GPT", httpclient => + { + httpclient.BaseAddress = new Uri(_config["GPTOptions:BaseUrl"]); + httpclient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_config["GPTOptions:ApiKey"]}"); + + if (!string.IsNullOrEmpty(_config["GPTOptions:Organization"])) + httpclient.DefaultRequestHeaders.Add("OpenAI-Organization", _config["GPTOptions:Organization"]); + }); + + _services.AddHttpClient("Urban", httpClient => + { + httpClient.BaseAddress = new Uri(_config["UrbanApioptions:BaseUrl"]); + httpClient.DefaultRequestHeaders.Add("x-rapidapi-host", _config["UrbanApioptions:Host"]); + httpClient.DefaultRequestHeaders.Add("x-rapidapi-key", _config["UrbanApioptions:Key"]); + }); + + return this; + } + } +} diff --git a/src/HuppyService/HuppyService.Service/HuppyService.Service.csproj b/src/HuppyService/HuppyService.Service/HuppyService.Service.csproj new file mode 100644 index 0000000..e851131 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/HuppyService.Service.csproj @@ -0,0 +1,41 @@ + + + + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/src/HuppyService/HuppyService.Service/Program.cs b/src/HuppyService/HuppyService.Service/Program.cs new file mode 100644 index 0000000..23437fb --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Program.cs @@ -0,0 +1,26 @@ +using HuppyService.Service; +using HuppyService.Service.Configuration; +using HuppyService.Service.Services; + +var builder = WebApplication.CreateBuilder(args); + +builder.Host.ConfigureServices(services => +{ + _ = new ModuleConfiguration(builder.Configuration, services) + .AddAppConfigurator() + .ConfigureMappings() + .AddGrpc() + .AddHttpClients() + .AddDatabase() + .AddRepositories(); +}); + +var app = builder.Build(); + +await app.UseAppConfigurator(); + +app.UseHttpLogging(); + +app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client."); + +app.Run(); diff --git a/src/HuppyService/HuppyService.Service/Properties/launchSettings.json b/src/HuppyService/HuppyService.Service/Properties/launchSettings.json new file mode 100644 index 0000000..6824d92 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Properties/launchSettings.json @@ -0,0 +1,13 @@ +{ + "profiles": { + "HuppyService.Service": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5067;https://localhost:7067", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Protos/commandLog.proto b/src/HuppyService/HuppyService.Service/Protos/commandLog.proto new file mode 100644 index 0000000..1179350 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/commandLog.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos"; + +import "Protos/database.proto"; +import "Protos/shared.proto"; + +package CommandLogProto; + +service CommandLogProto { + rpc GetCount(shared.Void) returns (shared.Int32); + rpc GetAverageExecutionTime(shared.Void) returns (AverageTimeResponse); + rpc GetAiUsage(shared.Void) returns (AiUsageResponse); + rpc AddCommand(database.models.CommandLogModel) returns (database.models.CommandLogModel); + rpc RemoveCommand(database.models.CommandLogModel) returns (shared.CommonResponse); +} + +message AverageTimeResponse { + double AverageTime = 1; +} + +message AiUsageResponse { + map AiUsers = 1; +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Protos/database.proto b/src/HuppyService/HuppyService.Service/Protos/database.proto new file mode 100644 index 0000000..adb4b96 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/database.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos.Models"; + +package database.models; + +message CommandLogModel { + int32 Id = 1; + string CommandName = 2; + uint64 Date = 3; + bool IsSuccess = 4; + uint64 UserId = 5; + int64 ExecutionTimeMs = 6; + uint64 ChannelId = 7; + string ErrorMessage = 8; + uint64 GuildId = 9; +} + +message ServerModel { + uint64 Id = 1; + string ServerName = 2; + bool UseGreet = 3; + string GreetMessage = 4; + uint64 RoleId = 5; + uint64 ServerRoomsId = 6; + ServerRoomsModel Rooms = 7; +} + +message ServerRoomsModel { + uint64 Id = 1; + uint64 OutputRoom = 2; + uint64 GreetingRoom = 3; + uint64 ServerId = 4; +} + +message ReminderModel { + int32 Id = 1; + uint64 RemindDate = 2; + string Message = 3; + uint64 UserId = 4; +} + +message TicketModel { + string Id = 1; + string Topic = 2; + string Description = 3; + bool IsClosed = 4; + string TicketAnswer = 5; + uint64 CreatedDate = 6; + uint64 ClosedDate = 7; + uint64 UserId = 8; +} diff --git a/src/HuppyService/HuppyService.Service/Protos/gpt.proto b/src/HuppyService/HuppyService.Service/Protos/gpt.proto new file mode 100644 index 0000000..d619d14 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/gpt.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos"; + +package GPTProto; + +service GPTProto { + rpc DavinciCompletion(GPTInputRequest) returns (GPTOutputResponse); +} + +message GPTInputRequest { + string prompt = 1; +} + +message GPTOutputResponse { + string answer = 1; +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Protos/greet.proto b/src/HuppyService/HuppyService.Service/Protos/greet.proto new file mode 100644 index 0000000..583fbcd --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/greet.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service"; + +package greet; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply); +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings. +message HelloReply { + string message = 1; +} diff --git a/src/HuppyService/HuppyService.Service/Protos/reminder.proto b/src/HuppyService/HuppyService.Service/Protos/reminder.proto new file mode 100644 index 0000000..ffa2e8b --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/reminder.proto @@ -0,0 +1,46 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos"; + +import "Protos/database.proto"; +import "Protos/shared.proto"; + +package ReminderProto; + +service ReminderProto { + rpc GetReminder(GetReminderInput) returns (database.models.ReminderModel); + rpc GetReminderBatch(ReminderBatchInput) returns (ReminderModelCollection); + rpc GetUserReminders(shared.UserId) returns (ReminderModelCollection); + rpc GetSortedUserReminders(SortedUserRemindersInput) returns (ReminderModelCollection); + rpc GetRemindersCount(shared.UserId) returns (shared.Int32); + rpc AddReminder(database.models.ReminderModel) returns (database.models.ReminderModel); + rpc RemoveReminder(database.models.ReminderModel) returns (shared.CommonResponse); + rpc RemoveReminderRange(RemoveReminderRangeInput) returns (shared.CommonResponse); +} + +message SortedUserRemindersInput { + uint64 UserId = 1; + int32 Skip = 2; + int32 Take = 3; +} + +message GetReminderInput { + uint64 UserId = 1; + int32 ReminderId = 2; +} + +message ReminderBatchInput { + uint64 EndDate = 1; +} + +message ReminderModelCollection { + repeated database.models.ReminderModel ReminderModels = 1; +} + +message RemoveReminderRangeInput { + repeated int32 Ids = 1; +} + +message RemoveReminderRangeInputString { + repeated string Ids = 1; +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Protos/server.proto b/src/HuppyService/HuppyService.Service/Protos/server.proto new file mode 100644 index 0000000..bef0c0c --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/server.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos"; + +import "Protos/database.proto"; +import "Protos/shared.proto"; + +package ServerProto; + +service ServerProto { + rpc Get(shared.ServerId) returns (database.models.ServerModel); + rpc GetOrCreate(GetOrCreateServerInput) returns (database.models.ServerModel); + rpc Update(database.models.ServerModel) returns(shared.CommonResponse); + rpc GetAll(shared.Void) returns (ServerModelCollection); +} + +message ServerModelCollection { + repeated database.models.ServerModel ServerModels = 1; +} + +message GetOrCreateServerInput { + uint64 Id = 1; + string ServerName = 2; + uint64 DefaultChannel = 3; +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Protos/shared.proto b/src/HuppyService/HuppyService.Service/Protos/shared.proto new file mode 100644 index 0000000..dda9b25 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/shared.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos"; + +package shared; + +message CommonResponse { + bool IsSuccess = 1; +} + +message UserId { + uint64 Id = 1; +} + +message ServerId { + uint64 Id = 1; +} + +message Int32 { + int32 Number = 1; +} + +message StringId { + string Id = 1; +} + +message Void { } \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Protos/ticket.proto b/src/HuppyService/HuppyService.Service/Protos/ticket.proto new file mode 100644 index 0000000..774a3d1 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Protos/ticket.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +option csharp_namespace = "HuppyService.Service.Protos"; + +import "Protos/database.proto"; +import "Protos/shared.proto"; + +package TicketProto; + +service TicketProto { + rpc GetCount(shared.UserId) returns (shared.Int32); + rpc GetTickets(shared.Void) returns (TicketModelCollection); + rpc GetUserTickets(shared.UserId) returns (TicketModelCollection); + rpc GetPaginatedTickets(GetPaginatedTicketsInput) returns (TicketModelCollection); + rpc GetUserPaginatedTickets(GetUserPaginatedTicketsInput) returns (TicketModelCollection); + rpc GetTicket(GetTicketInput) returns (database.models.TicketModel); + rpc AddTicket(AddTicketInput) returns (database.models.TicketModel); + rpc RemoveTicket(shared.StringId) returns (shared.CommonResponse); + rpc UpdateTicket(TicketUpdateInput) returns (shared.CommonResponse); + rpc CloseTicket(CloseTicketInput) returns (shared.CommonResponse); +} + +message TicketUpdateInput { + string TicketId = 1; + string Description = 2; +} + +message CloseTicketInput { + string TicketId = 1; + string Answer = 2; +} + +message AddTicketInput { + uint64 UserId = 1; + string Topic = 2; + string Description = 3; +} + +message GetTicketInput { + string TicketId = 1; + uint64 UserId = 2; +} + +message GetUserPaginatedTicketsInput { + uint64 UserId = 1; + int32 Skip = 2; + int32 Take = 3; +} + +message GetPaginatedTicketsInput { + int32 Skip = 1; + int32 Take = 2; +} + +message TicketModelCollection { + repeated database.models.TicketModel TicketsModels = 1; +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs b/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs new file mode 100644 index 0000000..c59dad9 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Services/CommandLogService.cs @@ -0,0 +1,87 @@ +using AutoMapper; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using HuppyService.Core.Utilities; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Service.Services +{ + public class CommandLogService : CommandLogProto.CommandLogProtoBase + { + private readonly ICommandLogRepository _commandLogRepository; + private readonly IMapper _mapper; + public CommandLogService(ICommandLogRepository commandLogRepository, IMapper mapper) + { + _commandLogRepository = commandLogRepository; + _mapper = mapper; + } + + public override async Task GetCount(Protos.Void request, ServerCallContext context) + { + var query = await _commandLogRepository.GetAllAsync(); + var count = await query.CountAsync(); + + return new Protos.Int32() { Number = count }; + } + + public override async Task GetAiUsage(Protos.Void request, ServerCallContext context) + { + Dictionary result = new(); + + var query = await _commandLogRepository.GetAllAsync(); + var commandLogs = await query.ToListAsync(); + + var uniqueUsers = commandLogs.GroupBy(e => e.UserId) + .Select(e => e.First()) + .ToList(); + + foreach (var user in uniqueUsers) + { + result.TryAdd(user.UserId, commandLogs.Where(x => x.UserId == user.UserId).Count()); + } + + var response = new AiUsageResponse(); + response.AiUsers.Add(result); + + return response; + } + + public override async Task GetAverageExecutionTime(Protos.Void request, ServerCallContext context) + { + var query = await _commandLogRepository.GetAllAsync(); + var avgTime = await query.AverageAsync(e => e.ExecutionTimeMs); + + return new AverageTimeResponse() { AverageTime = avgTime }; + } + + public override async Task AddCommand(CommandLogModel request, ServerCallContext context) + { + var commandLog = _mapper.Map(request); + + var result = await _commandLogRepository.AddAsync(commandLog); + await _commandLogRepository.SaveChangesAsync(); + + if (result) + { + request.Id = commandLog.Id; + return request; + } + + return null!; + } + + public override async Task RemoveCommand(CommandLogModel request, ServerCallContext context) + { + var commandLog = _mapper.Map(request); + + await _commandLogRepository.RemoveAsync(commandLog); + await _commandLogRepository.SaveChangesAsync(); + + return new CommonResponse() { IsSuccess = true }; + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Services/GPTService.cs b/src/HuppyService/HuppyService.Service/Services/GPTService.cs new file mode 100644 index 0000000..68b3773 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Services/GPTService.cs @@ -0,0 +1,61 @@ +using Grpc.Core; +using HuppyService.Core.Constants; +using HuppyService.Core.Entities; +using HuppyService.Core.Entities.Options; +using HuppyService.Service.Protos; +using Microsoft.Extensions.Options; + +namespace HuppyService.Service.Services +{ + public class GPTService : GPTProto.GPTProtoBase + { + private readonly IHttpClientFactory _clientFactory; + private readonly ILogger _logger; + private readonly GPTOptions _gptConfig; + public GPTService(IHttpClientFactory clientFactory, IOptions options, ILogger logger) + { + _clientFactory = clientFactory; + _logger = logger; + _gptConfig = new GPTOptions(); + } + + public override async Task DavinciCompletion(GPTInputRequest request, ServerCallContext context) + { + if (string.IsNullOrEmpty(request.Prompt)) + throw new Exception("request.Prompt for GPT was empty"); + + var aiContext = _gptConfig.AiContextMessage; + + if (string.IsNullOrEmpty(aiContext)) aiContext = ""; + if (!(request.Prompt.EndsWith('?') || request.Prompt.EndsWith('.'))) request.Prompt += '.'; + + aiContext += request.Prompt + "\n[Huppy]:"; + + GPTRequest model = new() + { + MaxTokens = 200, + Prompt = aiContext, + Temperature = 0.6, + FrequencyPenalty = 0.5, + N = 1 + }; + + var client = _clientFactory.CreateClient("GPT"); + + var response = await client.PostAsJsonAsync(GPTEndpoints.TextDavinciCompletions, model); + + if (response.IsSuccessStatusCode) + { + var result = await response.Content!.ReadFromJsonAsync(); + return new GPTOutputResponse() { Answer = result!.Choices!.First()!.Text! }; + } + else + { + var failedResponse = await response.Content.ReadAsStringAsync(); + _logger.LogError("{response}", failedResponse); + + throw new Exception("GPT request wasn't successful"); + } + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Services/GreeterService.cs b/src/HuppyService/HuppyService.Service/Services/GreeterService.cs new file mode 100644 index 0000000..f98eef3 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Services/GreeterService.cs @@ -0,0 +1,22 @@ +using Grpc.Core; +using HuppyService.Service; + +namespace HuppyService.Service.Services +{ + public class GreeterService : Greeter.GreeterBase + { + private readonly ILogger _logger; + public GreeterService(ILogger logger) + { + _logger = logger; + } + + public override Task SayHello(HelloRequest request, ServerCallContext context) + { + return Task.FromResult(new HelloReply + { + Message = "Hello " + request.Name + }); + } + } +} \ No newline at end of file diff --git a/src/HuppyService/HuppyService.Service/Services/ReminderService.cs b/src/HuppyService/HuppyService.Service/Services/ReminderService.cs new file mode 100644 index 0000000..c944725 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Services/ReminderService.cs @@ -0,0 +1,171 @@ +using AutoMapper; +using Grpc.Core; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using HuppyService.Core.Utilities; +using HuppyService.Infrastructure.Repositories; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore; + +namespace HuppyService.Service.Services +{ + public class ReminderService : ReminderProto.ReminderProtoBase + { + private readonly ILogger _logger; + private readonly IReminderRepository _reminderRepository; + private readonly IMapper _mapper; + public ReminderService(IReminderRepository reminderRepository, ILogger logger, IMapper mapper) + { + _reminderRepository = reminderRepository; + _logger = logger; + _mapper = mapper; + } + + public override async Task GetSortedUserReminders(SortedUserRemindersInput request, ServerCallContext context) + { + var query = await _reminderRepository.GetAllAsync(); + var reminders = await query + .Where(reminder => reminder.UserId == request.UserId) + .OrderBy(e => e.RemindDate) + .Skip(request.Skip) + .Take(request.Take) + .ToListAsync(); + + ReminderModelCollection? result = new(); + result.ReminderModels.AddRange(_mapper.Map(reminders)); + + //result.ReminderModels.AddRange(reminders.Select(reminder => new ReminderModel + //{ + // Id = reminder.Id, + // Message = reminder.Message, + // UserId = reminder.UserId, + // RemindDate = Miscellaneous.DateTimeToUnixTimeStamp(reminder.RemindDate) + //}).ToList()); + + return result; + } + + public override async Task GetRemindersCount(Protos.UserId request, ServerCallContext context) + { + var query = await _reminderRepository.GetAllAsync(); + return new Protos.Int32 () { Number = await query.Where(e => e.UserId == request.Id).CountAsync() }; + } + + public override async Task GetReminder(GetReminderInput request, ServerCallContext context) + { + var remindersQuery = await _reminderRepository.GetAllAsync(); + var reminder = await remindersQuery.FirstOrDefaultAsync(r => r.Id == request.ReminderId && r.UserId == request.UserId); + + if (reminder is null) return null; + + return _mapper.Map(reminder); + + //return new ReminderModel() + //{ + // Id = reminder.Id, + // Message = reminder.Message, + // RemindDate = Miscellaneous.DateTimeToUnixTimeStamp(reminder.RemindDate), + // UserId = reminder.UserId + //}; + } + + public override async Task AddReminder(ReminderModel request, ServerCallContext context) + { + //var reminderDate = Miscellaneous.UnixTimeStampToUtcDateTime(request.RemindDate).ToUniversalTime(); + //Reminder reminder = new() + //{ + // Message = request.Message, + // RemindDate = reminderDate, + // UserId = request.UserId + //}; + + var reminder = _mapper.Map(request); + + var result = await _reminderRepository.AddAsync(reminder); + await _reminderRepository.SaveChangesAsync(); + + if (!result) throw new Exception("Failed to create reminder"); + + _logger.LogInformation("Added reminder for [{user}] at [{date}] UTC", request.UserId, reminder.RemindDate); + + //return new ReminderModel() { Id = reminder.Id, Message = reminder.Message, RemindDate = request.RemindDate, UserId = reminder.UserId }; + return _mapper.Map(reminder); + } + + public override async Task GetReminderBatch(ReminderBatchInput request, ServerCallContext context) + { + _logger.LogInformation("Registering fresh bulk of reminders"); + + var remindDateEnd = Miscellaneous.UnixTimeStampToUtcDateTime(request.EndDate); + + var remindersQueryable = await _reminderRepository.GetAllAsync(); + + var reminders = await remindersQueryable + .Where(reminder => reminder.RemindDate < remindDateEnd) + .ToListAsync(); + + if (!reminders.Any()) return new(); + + ReminderModelCollection? result = new(); + result.ReminderModels.AddRange(_mapper.Map(reminders)); + + //result.ReminderModels.AddRange(reminders.Select(reminder => new ReminderModel + //{ + // Id = reminder.Id, + // Message = reminder.Message, + // UserId = reminder.UserId, + // RemindDate = Miscellaneous.DateTimeToUnixTimeStamp(reminder.RemindDate) + //}).ToList()); + + return result; + } + + public override async Task GetUserReminders(Protos.UserId request, ServerCallContext context) + { + var remindersQuery = await _reminderRepository.GetAllAsync(); + + var reminders = await remindersQuery.Where(reminder => reminder.UserId == request.Id) + .ToListAsync(); + + ReminderModelCollection result = new(); + result.ReminderModels.AddRange(_mapper.Map(reminders)); + + //result.ReminderModels.AddRange(reminders.Select(reminder => new ReminderModel() + //{ + // Id = reminder.Id, + // Message = reminder.Message, + // UserId = reminder.UserId, + // RemindDate = Miscellaneous.DateTimeToUnixTimeStamp(reminder.RemindDate) + //})); + + return result; + } + + public override async Task RemoveReminder(ReminderModel request, ServerCallContext context) + { + var reminder = _mapper.Map(request); + //Reminder reminder = new() + //{ + // Message = request.Message, + // RemindDate = Miscellaneous.UnixTimeStampToUtcDateTime(request.RemindDate), + // UserId = request.UserId + //}; + + await _reminderRepository.RemoveAsync(reminder); + await _reminderRepository.SaveChangesAsync(); + + return new CommonResponse() { IsSuccess = true }; + } + + public override async Task RemoveReminderRange(RemoveReminderRangeInput request, ServerCallContext context) + { + if (request.Ids is null || !(request.Ids.Count > 0)) return new CommonResponse() { IsSuccess = true }; + + await _reminderRepository.RemoveRangeAsync(request.Ids); + await _reminderRepository.SaveChangesAsync(); + + return new CommonResponse() { IsSuccess = true }; + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Services/ServerService.cs b/src/HuppyService/HuppyService.Service/Services/ServerService.cs new file mode 100644 index 0000000..d07dd7d --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Services/ServerService.cs @@ -0,0 +1,155 @@ +using AutoMapper; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore; +using System.Net.WebSockets; + +namespace HuppyService.Service.Services +{ + public class ServerService : ServerProto.ServerProtoBase + { + private readonly IServerRepository _serverRepository; + private readonly IMapper _mapper; + public ServerService(IServerRepository serverRepository, IMapper mapper) + { + _serverRepository = serverRepository; + _mapper = mapper; + } + + public override async Task Get(Protos.ServerId request, ServerCallContext context) + { + var server = await _serverRepository.GetAsync(request.Id); + + return _mapper.Map(server); + + //return new ServerModel() + //{ + // Id = server.Id, + // GreetMessage = server.GreetMessage, + // RoleId = server.RoleID, + // ServerName = server.ServerName, + // UseGreet = server.UseGreet, + // Rooms = new() + // { + // GreetingRoom = server.Rooms.GreetingRoom, + // OutputRoom = server.Rooms.OutputRoom, + // ServerId = server.Id + // } + //}; + } + + public override async Task GetAll(Protos.Void request, ServerCallContext context) + { + var query = await _serverRepository.GetAllAsync(); + var servers = await query.Include(e => e.Rooms).ToListAsync(); + + ServerModelCollection result = new(); + result.ServerModels.AddRange(_mapper.Map(servers)); + + //result.ServerModels.AddRange(servers.Select(server => new ServerModel() + //{ + // Id = server.Id, + // ServerName = server.ServerName, + // UseGreet = server.UseGreet, + // GreetMessage = server.GreetMessage, + // RoleId = server.RoleID, + // ServerRoomsId = server.ServerRoomsId, + // Rooms = new() + // { + // Id = server.Rooms.Id, + // GreetingRoom = server.Rooms.GreetingRoom, + // OutputRoom = server.Rooms.OutputRoom, + // ServerId = server.Rooms.ServerId + // } + //}).ToList()); + + return result; + } + + public override async Task GetOrCreate(GetOrCreateServerInput request, ServerCallContext context) + { + var server = await _serverRepository.GetAsync(request.Id); + + if (server is not null) + { + if (server.Rooms is null) + { + server.Rooms = new() + { + OutputRoom = request.DefaultChannel, + GreetingRoom = default, + ServerId = request.Id + }; + + await _serverRepository.UpdateAsync(server); + await _serverRepository.SaveChangesAsync(); + } + + return _mapper.Map(server); + + //return new ServerModel() + //{ + // Id = server.Id, + // GreetMessage = server.GreetMessage, + // RoleId = server.RoleID, + // ServerName = server.ServerName, + // UseGreet = server.UseGreet, + // Rooms = new() + // { + // GreetingRoom = server.Rooms.GreetingRoom, + // OutputRoom = server.Rooms.OutputRoom, + // ServerId = server.Id + // } + //}; + } + + server = new() + { + Id = request.Id, + GreetMessage = "Welcome {username}!", + ServerName = request.ServerName, + RoleID = 0, + UseGreet = false, + Rooms = new() + { + OutputRoom = request.DefaultChannel, + GreetingRoom = 0, + ServerId = request.Id + } + }; + + await _serverRepository.AddAsync(server); + await _serverRepository.SaveChangesAsync(); + + return _mapper.Map(server); + } + + public override async Task Update(ServerModel request, ServerCallContext context) + { + var server = _mapper.Map(request); + //Server server = new() + //{ + // Id = request.Id, + // ServerName = request.ServerName, + // GreetMessage = request.GreetMessage, + // RoleID = request.RoleId, + // UseGreet = request.UseGreet, + // ServerRoomsId = request.ServerRoomsId, + // Rooms = new() { + // Id = request.Id, + // GreetingRoom = request.Rooms.GreetingRoom, + // OutputRoom = request.Rooms.OutputRoom, + // ServerId = request.Rooms.ServerId + // } + //}; + + await _serverRepository.UpdateAsync(server); + await _serverRepository.SaveChangesAsync(); + return new CommonResponse() { IsSuccess = true }; + } + } +} diff --git a/src/HuppyService/HuppyService.Service/Services/TicketService.cs b/src/HuppyService/HuppyService.Service/Services/TicketService.cs new file mode 100644 index 0000000..6c5328d --- /dev/null +++ b/src/HuppyService/HuppyService.Service/Services/TicketService.cs @@ -0,0 +1,250 @@ +using AutoMapper; +using Google.Protobuf; +using Grpc.Core; +using HuppyService.Core.Interfaces.IRepositories; +using HuppyService.Core.Models; +using HuppyService.Core.Utilities; +using HuppyService.Service.Protos; +using HuppyService.Service.Protos.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; +using System.Net.Sockets; +using System.Reflection.Metadata.Ecma335; + +namespace HuppyService.Service.Services +{ + public class TicketService : TicketProto.TicketProtoBase + { + private readonly ITicketRepository _ticketRepository; + private readonly IMapper _mapper; + public TicketService(ITicketRepository ticketRepository, IMapper mapper) + { + _ticketRepository = ticketRepository; + _mapper = mapper; + } + + public override async Task AddTicket(AddTicketInput request, ServerCallContext context) + { + if (string.IsNullOrEmpty(request.Description)) throw new ArgumentException("Ticket description cannot be null"); + + //var ticket = _mapper.Map(request); + //var ticket = ReflectionMapper.Map(request); + + Ticket ticket = new() + { + UserId = request.UserId, + Topic = request.Topic, + Description = request.Description, + Id = Guid.NewGuid().ToString(), + CreatedDate = DateTime.UtcNow, + TicketAnswer = null, + IsClosed = false, + ClosedDate = default + }; + + + + //Ticket ticket = new() + //{ + // Id = Guid.NewGuid().ToString(), + // Topic = request.Topic, + // Description = request.Description, + // CreatedDate = DateTime.UtcNow, + // TicketAnswer = null, + // ClosedDate = null, + // IsClosed = false, + // UserId = request.UserId, + //}; + + await _ticketRepository.AddAsync(ticket); + await _ticketRepository.SaveChangesAsync(); + + return _mapper.Map(ticket); + //return ReflectionMapper.Map(ticket); + //return new TicketModel() + //{ + // Id = ticket.Id, + // UserId = ticket.UserId, + // Description = ticket.Description, + // Topic = ticket.Topic, + // IsClosed = ticket.IsClosed, + // TicketAnswer = ticket.TicketAnswer, + // ClosedDate = ticket.ClosedDate is null ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + // CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + //}; + } + + public override async Task CloseTicket(CloseTicketInput request, ServerCallContext context) + { + if (string.IsNullOrEmpty(request.TicketId)) + throw new ArgumentException("Ticked ID cannot be empty"); + + var ticket = await _ticketRepository.GetAsync(request.TicketId); + + if (ticket is null) throw new Exception("Ticket doesn't exist"); + + ticket.IsClosed = true; + ticket.TicketAnswer = request.Answer; + ticket.ClosedDate = DateTime.UtcNow; + + await _ticketRepository.UpdateAsync(ticket); + await _ticketRepository.SaveChangesAsync(); + + return new CommonResponse() { IsSuccess = true }; + } + + public override async Task GetCount(UserId request, ServerCallContext context) + { + var tickets = await _ticketRepository.GetAllAsync(); + + // modify database query + var result = await tickets.Where(ticket => ticket.UserId == request.Id).CountAsync(); + return new Protos.Int32() { Number = result }; + } + + public override async Task GetPaginatedTickets(GetPaginatedTicketsInput request, ServerCallContext context) + { + var tickets = (await _ticketRepository.GetAllAsync()) + .OrderBy(ticket => ticket.IsClosed) + .ThenByDescending(ticket => ticket.CreatedDate) + .Skip(request.Skip) + .Take(request.Take) + .ToList(); + + // TODO: use mapper + //var result = new TicketModelCollection(); + //var temp = ReflectionMapper.Map(tickets.ToArray()); + //result.TicketsModels.AddRange(temp); + + + var result = new TicketModelCollection(); + result.TicketsModels.AddRange(_mapper.Map(tickets)); + + //result.TicketsModels.AddRange(tickets.Select(ticket => new TicketModel + //{ + // Id = ticket.Id, + // UserId = ticket.UserId, + // Description = ticket.Description, + // Topic = ticket.Topic, + // IsClosed = ticket.IsClosed, + // TicketAnswer = ticket.TicketAnswer, + // ClosedDate = ticket.ClosedDate == default ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + // CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + //})); + + return result; + } + + public override async Task GetTicket(GetTicketInput request, ServerCallContext context) + { + var ticket = await _ticketRepository.GetAsync(request.TicketId); + + if (ticket is null) return null!; + + return _mapper.Map(ticket); + //return ReflectionMapper.Map(ticket); + } + + public override async Task GetTickets(Protos.Void request, ServerCallContext context) + { + var ticketsQuery = await _ticketRepository.GetAllAsync(); + var tickets = await ticketsQuery.ToListAsync(); + + var result = new TicketModelCollection(); + result.TicketsModels.AddRange(_mapper.Map(tickets)); + + //result.TicketsModels.AddRange(tickets.Select(ticket => new TicketModel + //{ + // Id = ticket.Id, + // UserId = ticket.UserId, + // Description = ticket.Description, + // Topic = ticket.Topic, + // IsClosed = ticket.IsClosed, + // TicketAnswer = ticket.TicketAnswer, + // ClosedDate = ticket.ClosedDate == default ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + // CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + //})); + + return result; + } + + public override async Task GetUserPaginatedTickets(GetUserPaginatedTicketsInput request, ServerCallContext context) + { + var tickets = await (await _ticketRepository.GetAllAsync()) + .Where(ticket => ticket.UserId == request.UserId) + .OrderBy(ticket => ticket.IsClosed) + .ThenByDescending(ticket => ticket.CreatedDate) + .Skip(request.Skip) + .Take(request.Take) + .ToListAsync(); + + var result = new TicketModelCollection(); + result.TicketsModels.AddRange(_mapper.Map(tickets)); + + //result.TicketsModels.AddRange(tickets.Select(ticket => new TicketModel + //{ + // Id = ticket.Id, + // UserId = ticket.UserId, + // Description = ticket.Description, + // Topic = ticket.Topic, + // IsClosed = ticket.IsClosed, + // TicketAnswer = ticket.TicketAnswer, + // ClosedDate = ticket.ClosedDate == default ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + // CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + //})); + + return result; + } + + public override async Task GetUserTickets(UserId request, ServerCallContext context) + { + var tickets = await (await _ticketRepository.GetAllAsync()) + .Where(ticket => ticket.UserId == request.Id) + .ToListAsync(); + + var result = new TicketModelCollection(); + result.TicketsModels.AddRange(_mapper.Map(tickets)); + + //result.TicketsModels.AddRange(tickets.Select(ticket => new TicketModel + //{ + // Id = ticket.Id, + // UserId = ticket.UserId, + // Description = ticket.Description, + // Topic = ticket.Topic, + // IsClosed = ticket.IsClosed, + // TicketAnswer = ticket.TicketAnswer, + // ClosedDate = ticket.ClosedDate == default ? 0 : Miscellaneous.DateTimeToUnixTimeStamp((DateTime)ticket.ClosedDate), + // CreatedDate = Miscellaneous.DateTimeToUnixTimeStamp(ticket.CreatedDate) + //})); + + return result; + } + + public override async Task RemoveTicket(StringId request, ServerCallContext context) + { + if (string.IsNullOrEmpty(request.Id)) throw new ArgumentException("Ticket cannot be null or empty"); + + await _ticketRepository.RemoveAsync(request.Id); + await _ticketRepository.SaveChangesAsync(); + + return new() { IsSuccess = true }; + } + + public override async Task UpdateTicket(TicketUpdateInput request, ServerCallContext context) + { + if (string.IsNullOrEmpty(request.TicketId) || string.IsNullOrEmpty(request.Description)) + throw new ArgumentException("Both ticked ID and ticket description cannot be null or empty"); + + var ticket = await _ticketRepository.GetAsync(request.TicketId); + + if (ticket is null) throw new Exception("Ticket doesn't exist"); + + ticket.Description = request.Description; + + await _ticketRepository.UpdateAsync(ticket); + await _ticketRepository.SaveChangesAsync(); + + return new() { IsSuccess = true }; + } + } +} diff --git a/src/HuppyService/HuppyService.Service/appsettings.example.Development.json b/src/HuppyService/HuppyService.Service/appsettings.example.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/HuppyService/HuppyService.Service/appsettings.example.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/HuppyService/HuppyService.Service/appsettings.example.json b/src/HuppyService/HuppyService.Service/appsettings.example.json new file mode 100644 index 0000000..214a0c3 --- /dev/null +++ b/src/HuppyService/HuppyService.Service/appsettings.example.json @@ -0,0 +1,37 @@ +{ + "BotToken": "", + "ConnectionString": "", + "DebugGuilds": "", + "BetaTestingGuilds": "", + "Developers": "", + "Logger": { + "LogLevel": "information", + "TimeInterval": "hour" + }, + "GPT": { + "BaseUrl": "https://api.openai.com/v1/engines/", + "ApiKey": "", + "Orgranization": "", + "FreeMessageQuota": 100, + "IsEnabled": true, + "AiContextMessage": "[Huppy]: I'm Huppy, sarcastic, elegant and fun bot.\n[User]: " + }, + "UrbanApi": { + "BaseUrl": "https://mashape-community-urban-dictionary.p.rapidapi.com/define", + "Host": "mashape-community-urban-dictionary.p.rapidapi.com", + "Key": "" + }, + "urls": "https://localhost:9001", + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Kestrel": { + "EndpointDefaults": { + "Protocols": "Http2" + } + } +} diff --git a/src/HuppyService/HuppyService.sln b/src/HuppyService/HuppyService.sln new file mode 100644 index 0000000..31d407d --- /dev/null +++ b/src/HuppyService/HuppyService.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32901.215 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HuppyService.Service", "HuppyService.Service\HuppyService.Service.csproj", "{00AA8C7E-BCF8-4347-810F-C6310678D5CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {00AA8C7E-BCF8-4347-810F-C6310678D5CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00AA8C7E-BCF8-4347-810F-C6310678D5CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00AA8C7E-BCF8-4347-810F-C6310678D5CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00AA8C7E-BCF8-4347-810F-C6310678D5CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {06086C03-64DC-4169-8048-D042D48553F4} + EndGlobalSection +EndGlobal diff --git a/src/Main.sln b/src/Main.sln new file mode 100644 index 0000000..076849f --- /dev/null +++ b/src/Main.sln @@ -0,0 +1,106 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "Gateway", "{1CDD216C-64CF-4EA3-AA8C-83993337D872}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Gateway.Api", "Gateway\Gateway.Api\Gateway.Api.csproj", "{7BE0686F-2653-4947-9E0A-12AC299ACEA9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HuppyService", "HuppyService", "{478F934F-E2B9-4A07-A6AD-C482F174E80A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuppyService.Service", "HuppyService\HuppyService.Service\HuppyService.Service.csproj", "{0E63AA9F-116E-40DB-AC1F-21B51B322F94}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuppyService.Infrastructure", "HuppyService\HuppyService.Infrastructure\HuppyService.Infrastructure.csproj", "{E330C21B-3443-4DD0-8313-34777CD4BD11}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuppyService.Core", "HuppyService\HuppyService.Core\HuppyService.Core.csproj", "{E376D7A3-F2FE-4ABD-9FAD-9024E606AC29}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NetherNetAuth", "NetherNetAuth", "{703B8FE3-39E4-43E9-9F90-71F966D77365}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetherNet.Api", "NetherNetAuth\NetherNet.Api\NetherNet.Api.csproj", "{AB5A3C60-4FEB-4D4C-82D5-78132A41B597}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetherNet.Core", "NetherNetAuth\NetherNet.Core\NetherNet.Core.csproj", "{43FF5A94-F856-490E-B34F-2A6BCEC1096B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetherNet.Infrastructure", "NetherNetAuth\NetherNet.Infrastructure\NetherNet.Infrastructure.csproj", "{3ABB0FD3-00A7-4CB7-B63E-F79533E4BBC8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Huppy", "Huppy", "{C95A19F8-D1C4-4CD4-A88D-946FAA05566E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.App", "Huppy\Huppy.App\Huppy.App.csproj", "{9CDE7EF5-079E-4938-8566-976A5B2CF0C3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.Core", "Huppy\Huppy.Core\Huppy.Core.csproj", "{97B2E868-C321-46B3-B9E5-F6FF590AFF26}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.Infrastructure", "Huppy\Huppy.Infrastructure\Huppy.Infrastructure.csproj", "{E07FF2CD-265D-4148-9949-42F419393857}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Huppy.Kernel", "Huppy\Huppy.Kernel\Huppy.Kernel.csproj", "{50477284-6F5A-4E43-A1D0-850DE5C36131}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7BE0686F-2653-4947-9E0A-12AC299ACEA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BE0686F-2653-4947-9E0A-12AC299ACEA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BE0686F-2653-4947-9E0A-12AC299ACEA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BE0686F-2653-4947-9E0A-12AC299ACEA9}.Release|Any CPU.Build.0 = Release|Any CPU + {0E63AA9F-116E-40DB-AC1F-21B51B322F94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E63AA9F-116E-40DB-AC1F-21B51B322F94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E63AA9F-116E-40DB-AC1F-21B51B322F94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E63AA9F-116E-40DB-AC1F-21B51B322F94}.Release|Any CPU.Build.0 = Release|Any CPU + {E330C21B-3443-4DD0-8313-34777CD4BD11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E330C21B-3443-4DD0-8313-34777CD4BD11}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E330C21B-3443-4DD0-8313-34777CD4BD11}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E330C21B-3443-4DD0-8313-34777CD4BD11}.Release|Any CPU.Build.0 = Release|Any CPU + {E376D7A3-F2FE-4ABD-9FAD-9024E606AC29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E376D7A3-F2FE-4ABD-9FAD-9024E606AC29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E376D7A3-F2FE-4ABD-9FAD-9024E606AC29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E376D7A3-F2FE-4ABD-9FAD-9024E606AC29}.Release|Any CPU.Build.0 = Release|Any CPU + {AB5A3C60-4FEB-4D4C-82D5-78132A41B597}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AB5A3C60-4FEB-4D4C-82D5-78132A41B597}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AB5A3C60-4FEB-4D4C-82D5-78132A41B597}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AB5A3C60-4FEB-4D4C-82D5-78132A41B597}.Release|Any CPU.Build.0 = Release|Any CPU + {43FF5A94-F856-490E-B34F-2A6BCEC1096B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {43FF5A94-F856-490E-B34F-2A6BCEC1096B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {43FF5A94-F856-490E-B34F-2A6BCEC1096B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {43FF5A94-F856-490E-B34F-2A6BCEC1096B}.Release|Any CPU.Build.0 = Release|Any CPU + {3ABB0FD3-00A7-4CB7-B63E-F79533E4BBC8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3ABB0FD3-00A7-4CB7-B63E-F79533E4BBC8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3ABB0FD3-00A7-4CB7-B63E-F79533E4BBC8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3ABB0FD3-00A7-4CB7-B63E-F79533E4BBC8}.Release|Any CPU.Build.0 = Release|Any CPU + {9CDE7EF5-079E-4938-8566-976A5B2CF0C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9CDE7EF5-079E-4938-8566-976A5B2CF0C3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9CDE7EF5-079E-4938-8566-976A5B2CF0C3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9CDE7EF5-079E-4938-8566-976A5B2CF0C3}.Release|Any CPU.Build.0 = Release|Any CPU + {97B2E868-C321-46B3-B9E5-F6FF590AFF26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {97B2E868-C321-46B3-B9E5-F6FF590AFF26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {97B2E868-C321-46B3-B9E5-F6FF590AFF26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {97B2E868-C321-46B3-B9E5-F6FF590AFF26}.Release|Any CPU.Build.0 = Release|Any CPU + {E07FF2CD-265D-4148-9949-42F419393857}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E07FF2CD-265D-4148-9949-42F419393857}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E07FF2CD-265D-4148-9949-42F419393857}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E07FF2CD-265D-4148-9949-42F419393857}.Release|Any CPU.Build.0 = Release|Any CPU + {50477284-6F5A-4E43-A1D0-850DE5C36131}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50477284-6F5A-4E43-A1D0-850DE5C36131}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50477284-6F5A-4E43-A1D0-850DE5C36131}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50477284-6F5A-4E43-A1D0-850DE5C36131}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {7BE0686F-2653-4947-9E0A-12AC299ACEA9} = {1CDD216C-64CF-4EA3-AA8C-83993337D872} + {0E63AA9F-116E-40DB-AC1F-21B51B322F94} = {478F934F-E2B9-4A07-A6AD-C482F174E80A} + {E330C21B-3443-4DD0-8313-34777CD4BD11} = {478F934F-E2B9-4A07-A6AD-C482F174E80A} + {E376D7A3-F2FE-4ABD-9FAD-9024E606AC29} = {478F934F-E2B9-4A07-A6AD-C482F174E80A} + {AB5A3C60-4FEB-4D4C-82D5-78132A41B597} = {703B8FE3-39E4-43E9-9F90-71F966D77365} + {43FF5A94-F856-490E-B34F-2A6BCEC1096B} = {703B8FE3-39E4-43E9-9F90-71F966D77365} + {3ABB0FD3-00A7-4CB7-B63E-F79533E4BBC8} = {703B8FE3-39E4-43E9-9F90-71F966D77365} + {9CDE7EF5-079E-4938-8566-976A5B2CF0C3} = {C95A19F8-D1C4-4CD4-A88D-946FAA05566E} + {97B2E868-C321-46B3-B9E5-F6FF590AFF26} = {C95A19F8-D1C4-4CD4-A88D-946FAA05566E} + {E07FF2CD-265D-4148-9949-42F419393857} = {C95A19F8-D1C4-4CD4-A88D-946FAA05566E} + {50477284-6F5A-4E43-A1D0-850DE5C36131} = {C95A19F8-D1C4-4CD4-A88D-946FAA05566E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2FDA4D15-FE50-4802-8176-9F2101A52F15} + EndGlobalSection +EndGlobal diff --git a/src/NetherNetAuth/NetherNet.Api/Auth/ApiKeyAuthenticationHandler.cs b/src/NetherNetAuth/NetherNet.Api/Auth/ApiKeyAuthenticationHandler.cs new file mode 100644 index 0000000..b0d58ac --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/Auth/ApiKeyAuthenticationHandler.cs @@ -0,0 +1,153 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using System.Net.Mime; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Text.Json; + +namespace NetherNet.Api.Auth +{ + public class ApiKeyAuthenticationHandler : AuthenticationHandler + { + private enum AuthenticationFailureReason + { + NONE = 0, + API_KEY_HEADER_NOT_PROVIDED, + API_KEY_HEADER_VALUE_NULL, + API_KEY_INVALID + } + + private AuthenticationFailureReason _failureReason = AuthenticationFailureReason.NONE; + private readonly ILogger _logger; + private readonly IConfiguration _config; + private readonly IOptionsMonitor _settings; + + public ApiKeyAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory loggerFactory, + ILogger logger, + UrlEncoder encoder, + ISystemClock clock, + IConfiguration config) : base(options, loggerFactory, encoder, clock) + { + _logger = logger; + _config = config; + _settings = options; + } + + protected override async Task HandleAuthenticateAsync() + { + //ApiKey header get + if (!TryGetApiKeyHeader(out string providedApiKey, out AuthenticateResult authenticateResult)) + return authenticateResult; + + //TODO: you apikey validity check + if (await ApiKeyCheckAsync(providedApiKey)) + { + var principal = new ClaimsPrincipal(); //TODO: Create your Identity retreiving claims + var ticket = new AuthenticationTicket(principal, ApiKeyAuthenticationOptions.Scheme); + + return AuthenticateResult.Success(ticket); + } + + _failureReason = AuthenticationFailureReason.API_KEY_INVALID; + return AuthenticateResult.NoResult(); + } + + protected override async Task HandleChallengeAsync(AuthenticationProperties properties) + { + //Create response + Response.Headers.Append(HeaderNames.WWWAuthenticate, $@"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}"""); + Response.StatusCode = StatusCodes.Status401Unauthorized; + Response.ContentType = MediaTypeNames.Application.Json; + + var result = new + { + StatusCode = Response.StatusCode, + Message = _failureReason switch + { + AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED => "ApiKey not provided", + AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL => "ApiKey value is null", + AuthenticationFailureReason.NONE or AuthenticationFailureReason.API_KEY_INVALID or _ => "ApiKey is not valid" + } + }; + + using var responseStream = new MemoryStream(); + await JsonSerializer.SerializeAsync(responseStream, result); + await Response.BodyWriter.WriteAsync(responseStream.ToArray()); + } + + protected override async Task HandleForbiddenAsync(AuthenticationProperties properties) + { + //Create response + Response.Headers.Append(HeaderNames.WWWAuthenticate, $@"Authorization realm=""{ApiKeyAuthenticationOptions.DefaultScheme}"""); + Response.StatusCode = StatusCodes.Status403Forbidden; + Response.ContentType = MediaTypeNames.Application.Json; + + var result = new + { + Response.StatusCode, + Message = "Forbidden" + }; + + using var responseStream = new MemoryStream(); + await JsonSerializer.SerializeAsync(responseStream, result); + await Response.BodyWriter.WriteAsync(responseStream.ToArray()); + } + + #region Privates + private bool TryGetApiKeyHeader(out string apiKeyHeaderValue, out AuthenticateResult result) + { + apiKeyHeaderValue = null!; + if (!Request.Headers.TryGetValue("X-Api-Key", out var apiKeyHeaderValues)) + { + _logger.LogError("ApiKey header not provided"); + + _failureReason = AuthenticationFailureReason.API_KEY_HEADER_NOT_PROVIDED; + result = AuthenticateResult.Fail("ApiKey header not provided"); + + return false; + } + + apiKeyHeaderValue = apiKeyHeaderValues.FirstOrDefault()!; + if (apiKeyHeaderValues.Count == 0 || string.IsNullOrWhiteSpace(apiKeyHeaderValue)) + { + _logger.LogError("ApiKey header value null"); + + _failureReason = AuthenticationFailureReason.API_KEY_HEADER_VALUE_NULL; + result = AuthenticateResult.Fail("ApiKey header value null"); + + return false; + } + + result = null!; + return true; + } + + private Task ApiKeyCheckAsync(string apiKey) + { + //TODO: setup your validation code... + if (!(apiKey == _settings.CurrentValue.AuthKey)) + return Task.FromResult(false); + + return Task.FromResult(true); + } + #endregion + } + + public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions + { + public const string DefaultScheme = "ApiKey"; + public static string Scheme => DefaultScheme; + public static string AuthenticationType => DefaultScheme; + public string? AuthKey { get; set; } + } + + public static class AuthenticationBuilderExtensions + { + public static AuthenticationBuilder AddApiKeySupport(this AuthenticationBuilder authenticationBuilder, Action options) + => authenticationBuilder.AddScheme(ApiKeyAuthenticationOptions.DefaultScheme, options); + } +} \ No newline at end of file diff --git a/src/NetherNetAuth/NetherNet.Api/Controllers/AuthController.cs b/src/NetherNetAuth/NetherNet.Api/Controllers/AuthController.cs new file mode 100644 index 0000000..4abd491 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/Controllers/AuthController.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc; + +namespace NetherNet.Api.Controllers +{ + [ApiController] + [Route("/api/[controller]")] + public class AuthController : ControllerBase + { + public AuthController() + { + + } + } +} diff --git a/src/NetherNetAuth/NetherNet.Api/Controllers/UsersController.cs b/src/NetherNetAuth/NetherNet.Api/Controllers/UsersController.cs new file mode 100644 index 0000000..8c292f3 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/Controllers/UsersController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace NetherNet.Api.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class UsersController : ControllerBase + { + public UsersController() { } + } +} diff --git a/src/NetherNetAuth/NetherNet.Api/NetherNet.Api.csproj b/src/NetherNetAuth/NetherNet.Api/NetherNet.Api.csproj new file mode 100644 index 0000000..45a4fc2 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/NetherNet.Api.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/NetherNetAuth/NetherNet.Api/Program.cs b/src/NetherNetAuth/NetherNet.Api/Program.cs new file mode 100644 index 0000000..82f76ee --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/Program.cs @@ -0,0 +1,48 @@ +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Identity; +using NetherNet.Api.Auth; +using NetherNet.Core.Models; +using NetherNet.Infrastructure; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddControllers(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddHttpContextAccessor(); +builder.Services.AddDbContext(); + +builder.Services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + +builder.Services + .AddAuthentication(options => + { + options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options => builder.Configuration.Bind("Auth:JwtSettings", options)) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => builder.Configuration.Bind("CookieSettings", options)) + .AddApiKeySupport(opt => + { + opt.AuthKey = builder.Configuration.GetSection("Auth:ApiKey").Get(); + }); + + +var app = builder.Build(); + +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/src/NetherNetAuth/NetherNet.Api/Properties/launchSettings.json b/src/NetherNetAuth/NetherNet.Api/Properties/launchSettings.json new file mode 100644 index 0000000..694a7d6 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:17320", + "sslPort": 44347 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5268", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7025;http://localhost:5268", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/NetherNetAuth/NetherNet.Api/appsettings.example.Development.json b/src/NetherNetAuth/NetherNet.Api/appsettings.example.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/appsettings.example.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/NetherNetAuth/NetherNet.Api/appsettings.example.json b/src/NetherNetAuth/NetherNet.Api/appsettings.example.json new file mode 100644 index 0000000..895dc00 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Api/appsettings.example.json @@ -0,0 +1,20 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Auth": { + "JwtSettings": { + "Audience": "audience", + "TokenValidationParameters": { + "IssuerSigningKey": "secret", // This will not be set + "ValidateIssuerSigningKey": true, + "ValidIssuers": [ "abc", "def" ] + } + }, + "ApiKey": "This is testing auth" + } +} diff --git a/src/NetherNetAuth/NetherNet.Core/Models/DiscordAccount.cs b/src/NetherNetAuth/NetherNet.Core/Models/DiscordAccount.cs new file mode 100644 index 0000000..8081944 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Core/Models/DiscordAccount.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NetherNet.Core.Models +{ + public class DiscordAccount + { + [Key] + public ulong Id { get; set; } + public string? Username { get; set; } + } +} diff --git a/src/NetherNetAuth/NetherNet.Core/Models/User.cs b/src/NetherNetAuth/NetherNet.Core/Models/User.cs new file mode 100644 index 0000000..cea69a6 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Core/Models/User.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Identity; +using System.ComponentModel.DataAnnotations.Schema; + +namespace NetherNet.Core.Models +{ + public class User : IdentityUser + { + public DateTime CreatedDate { get; set; } + + [ForeignKey("DiscordAccountid")] + public ulong? DiscordAccountId { get; set; } + public virtual DiscordAccount? DiscordAccount { get; set; } + } +} diff --git a/src/NetherNetAuth/NetherNet.Core/NetherNet.Core.csproj b/src/NetherNetAuth/NetherNet.Core/NetherNet.Core.csproj new file mode 100644 index 0000000..b3b5438 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Core/NetherNet.Core.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + + diff --git a/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/20221128201341_init.Designer.cs b/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/20221128201341_init.Designer.cs new file mode 100644 index 0000000..cdbb7bf --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/20221128201341_init.Designer.cs @@ -0,0 +1,299 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetherNet.Infrastructure; + +#nullable disable + +namespace NetherNet.Infrastructure.Migrations +{ + [DbContext(typeof(NetherContext))] + [Migration("20221128201341_init")] + partial class init + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("NetherNet.Core.Models.DiscordAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DiscordUsers"); + }); + + modelBuilder.Entity("NetherNet.Core.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("DiscordAccountId") + .HasColumnType("INTEGER"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DiscordAccountId"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetherNet.Core.Models.User", b => + { + b.HasOne("NetherNet.Core.Models.DiscordAccount", "DiscordAccount") + .WithMany() + .HasForeignKey("DiscordAccountId"); + + b.Navigation("DiscordAccount"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/20221128201341_init.cs b/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/20221128201341_init.cs new file mode 100644 index 0000000..5133de9 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/20221128201341_init.cs @@ -0,0 +1,250 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace NetherNet.Infrastructure.Migrations +{ + /// + public partial class init : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DiscordUsers", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Username = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_DiscordUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + CreatedDate = table.Column(type: "TEXT", nullable: false), + DiscordAccountId = table.Column(type: "INTEGER", nullable: true), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUsers_DiscordUsers_DiscordAccountId", + column: x => x.DiscordAccountId, + principalTable: "DiscordUsers", + principalColumn: "Id"); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", nullable: false), + ProviderKey = table.Column(type: "TEXT", nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUsers_DiscordAccountId", + table: "AspNetUsers", + column: "DiscordAccountId"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + + migrationBuilder.DropTable( + name: "DiscordUsers"); + } + } +} diff --git a/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/NetherContextModelSnapshot.cs b/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/NetherContextModelSnapshot.cs new file mode 100644 index 0000000..8e2a213 --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Infrastructure/Migrations/NetherContextModelSnapshot.cs @@ -0,0 +1,296 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetherNet.Infrastructure; + +#nullable disable + +namespace NetherNet.Infrastructure.Migrations +{ + [DbContext(typeof(NetherContext))] + partial class NetherContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "7.0.0"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("NetherNet.Core.Models.DiscordAccount", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("DiscordUsers"); + }); + + modelBuilder.Entity("NetherNet.Core.Models.User", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("DiscordAccountId") + .HasColumnType("INTEGER"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("DiscordAccountId"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("NetherNet.Core.Models.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("NetherNet.Core.Models.User", b => + { + b.HasOne("NetherNet.Core.Models.DiscordAccount", "DiscordAccount") + .WithMany() + .HasForeignKey("DiscordAccountId"); + + b.Navigation("DiscordAccount"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/NetherNetAuth/NetherNet.Infrastructure/NetherContext.cs b/src/NetherNetAuth/NetherNet.Infrastructure/NetherContext.cs new file mode 100644 index 0000000..761b7cf --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Infrastructure/NetherContext.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Identity.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; +using NetherNet.Core.Models; +using System.Reflection; + +namespace NetherNet.Infrastructure; + +public class NetherContext : IdentityDbContext +{ + public NetherContext() { } + public NetherContext(DbContextOptions options) : base(options) { } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlite(AppContext.BaseDirectory + "save.sqlite", + x => x.MigrationsAssembly(typeof(NetherContext).Assembly.GetName().Name)); + } + + public DbSet? DiscordUsers { get; set; } +} diff --git a/src/NetherNetAuth/NetherNet.Infrastructure/NetherNet.Infrastructure.csproj b/src/NetherNetAuth/NetherNet.Infrastructure/NetherNet.Infrastructure.csproj new file mode 100644 index 0000000..d2ab9fd --- /dev/null +++ b/src/NetherNetAuth/NetherNet.Infrastructure/NetherNet.Infrastructure.csproj @@ -0,0 +1,27 @@ + + + + net7.0 + enable + enable + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + +