diff --git a/.env.example b/.env.example index 59af47d..f1f9544 100644 --- a/.env.example +++ b/.env.example @@ -3,13 +3,14 @@ DISCORD_CLIENT_SECRET=*** DISCORD_BOT_TOKEN=*** DISCORD_DEV_GUILD=0000000000000000000 -POSTGRES_USER=blink +POSTGRES_USER=blink3 POSTGRES_PASSWORD=password -REDIS_NAME=blink - BLINK_RUN_MIGRATIONS=true -API_ALLOWED_ORIGIN=http://localhost:5280 +API_ALLOWED_ORIGIN=http://localhost:8280 +API_URL=http://localhost:8288/ WORDS_API_KEY=*** + +ENCRYPTION_KEY="<>" \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4e9ff5..dc802bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,4 +25,4 @@ jobs: - name: Build run: dotnet build --no-restore - name: Test - run: dotnet test --no-build --verbosity normal + run: dotnet test --no-build --verbosity normal \ No newline at end of file diff --git a/.idea/.idea.Blink3/.idea/git_toolbox_blame.xml b/.idea/.idea.Blink3/.idea/git_toolbox_blame.xml new file mode 100644 index 0000000..7dc1249 --- /dev/null +++ b/.idea/.idea.Blink3/.idea/git_toolbox_blame.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/.idea.Blink3/Docker/docker-compose.generated.override.yml b/.idea/.idea.Blink3/Docker/docker-compose.generated.override.yml index 766171a..e2fd9d6 100644 --- a/.idea/.idea.Blink3/Docker/docker-compose.generated.override.yml +++ b/.idea/.idea.Blink3/Docker/docker-compose.generated.override.yml @@ -1,7 +1,6 @@ # This is a generated file. Not intended for manual editing. -version: "3.8" services: - blink3.api: + api: build: context: "/Users/joshua/RiderProjects/Blink3" dockerfile: "Blink3.API/Dockerfile" @@ -12,7 +11,7 @@ services: - "/app/bin/Debug/net8.0/Blink3.API.dll" environment: DOTNET_USE_POLLING_FILE_WATCHER: "true" - image: "blink3.api:dev" + image: "ghcr.io/epicofficer/blink3.api:dev" ports: [] volumes: - "/Users/joshua/.microsoft/usersecrets:/home/app/.microsoft/usersecrets" @@ -20,7 +19,7 @@ services: - "/Users/joshua/RiderProjects/Blink3/Blink3.API:/app:rw" - "/Users/joshua/RiderProjects/Blink3:/src:rw" working_dir: "/app" - blink3.bot: + bot: build: context: "/Users/joshua/RiderProjects/Blink3" dockerfile: "Blink3.Bot/Dockerfile" @@ -31,7 +30,7 @@ services: - "/app/bin/Debug/net8.0/Blink3.Bot.dll" environment: DOTNET_USE_POLLING_FILE_WATCHER: "true" - image: "blink3.bot:dev" + image: "ghcr.io/epicofficer/blink3.bot:dev" ports: [] volumes: - "/Users/joshua/.microsoft/usersecrets:/home/app/.microsoft/usersecrets" diff --git a/.run/Blink3 Backend.run.xml b/.run/Blink3 Development.run.xml similarity index 59% rename from .run/Blink3 Backend.run.xml rename to .run/Blink3 Development.run.xml index 71af7ce..eb03e5c 100644 --- a/.run/Blink3 Backend.run.xml +++ b/.run/Blink3 Development.run.xml @@ -1,21 +1,14 @@ - + - diff --git a/.run/Blink3.API_Dockerfile.run.xml b/.run/Blink3.API_Dockerfile.run.xml deleted file mode 100644 index d1d6cd6..0000000 --- a/.run/Blink3.API_Dockerfile.run.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.run/Blink3.Activity_Dockerfile.run.xml b/.run/Blink3.Activity_Dockerfile.run.xml deleted file mode 100644 index e885c5f..0000000 --- a/.run/Blink3.Activity_Dockerfile.run.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/.run/Blink3.Bot_Dockerfile.run.xml b/.run/Blink3.Bot_Dockerfile.run.xml deleted file mode 100644 index 264bb60..0000000 --- a/.run/Blink3.Bot_Dockerfile.run.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Blink3.API/Blink3.API.csproj b/Blink3.API/Blink3.API.csproj index 59ac6c5..0ac4e35 100644 --- a/Blink3.API/Blink3.API.csproj +++ b/Blink3.API/Blink3.API.csproj @@ -11,29 +11,24 @@ - - - - - - - - - + + + + + + + + - - - + + + .dockerignore - - appsettings.Development.json - Always - appsettings.json Always diff --git a/Blink3.API/Controllers/ApiControllerBase.cs b/Blink3.API/Controllers/ApiControllerBase.cs index 9218013..22407e9 100644 --- a/Blink3.API/Controllers/ApiControllerBase.cs +++ b/Blink3.API/Controllers/ApiControllerBase.cs @@ -1,11 +1,10 @@ using System.Net.Mime; +using Blink3.API.Interfaces; using Blink3.Core.Caching; using Blink3.Core.DiscordAuth.Extensions; using Blink3.Core.Models; using Discord; -using Discord.Addons.Hosting.Util; using Discord.Rest; -using Discord.WebSocket; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -24,11 +23,12 @@ namespace Blink3.API.Controllers; [Route("api/[controller]")] [ApiController] [Authorize] -public abstract class ApiControllerBase(DiscordSocketClient discordSocketClient, ICachingService cachingService) : ControllerBase +public abstract class ApiControllerBase(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService) : ControllerBase { - protected readonly ICachingService CachingService = cachingService; - protected DiscordRestClient? Client; - protected readonly DiscordSocketClient DiscordBotClient = discordSocketClient; + private readonly DiscordRestClient _userClient = userClientFactory(); /// /// Represents an Unauthorized Access message. @@ -90,28 +90,26 @@ private ObjectResult ProblemForUnauthorizedAccess() return userId != UserId ? ProblemForUnauthorizedAccess() : null; } - protected async Task InitDiscordClientAsync() + private async Task AuthenticateUserClientAsync() { - await DiscordBotClient.WaitForReadyAsync(CancellationToken.None); - if (Client is not null) return; - string? accessToken = await CachingService.GetAsync($"token:{UserId}"); - if (accessToken is null) return; - - Client = new DiscordRestClient(); - await Client.LoginAsync(TokenType.Bearer, accessToken); + string? encryptedToken = await cachingService.GetAsync($"token:{UserId}"); + string? iv = await cachingService.GetAsync($"token:{UserId}:iv"); + if (encryptedToken is null || iv is null) return; + + string accessToken = encryptionService.Decrypt(encryptedToken, iv); + await _userClient.LoginAsync(TokenType.Bearer, accessToken); } protected async Task> GetUserGuilds() { - await InitDiscordClientAsync(); + await AuthenticateUserClientAsync(); - List managedGuilds = await CachingService.GetOrAddAsync($"discord:guilds:{UserId}", + List managedGuilds = await cachingService.GetOrAddAsync($"discord:guilds:{UserId}", async () => { List manageable = []; - if (Client is null) return manageable; - IAsyncEnumerable> guilds = Client.GetGuildSummariesAsync(); + IAsyncEnumerable> guilds = _userClient.GetGuildSummariesAsync(); await foreach (IReadOnlyCollection guildCollection in guilds) { manageable.AddRange(guildCollection.Where(g => g.Permissions.ManageGuild).Select(g => @@ -126,7 +124,10 @@ protected async Task> GetUserGuilds() return manageable; }, TimeSpan.FromMinutes(5)); - List discordGuildIds = DiscordBotClient.Guilds.Select(b => b.Id).ToList(); + List discordGuildIds = await botClient.GetGuildSummariesAsync() + .SelectMany(guildCollection => guildCollection.ToAsyncEnumerable()) + .Select(guild => guild.Id) + .ToListAsync(); return managedGuilds.Where(g => discordGuildIds.Contains(g.Id)).ToList(); } @@ -145,7 +146,7 @@ protected async Task> GetUserGuilds() ~ApiControllerBase() { - Client?.Dispose(); - Client = null; + _userClient.LogoutAsync().Wait(); + _userClient.Dispose(); } } \ No newline at end of file diff --git a/Blink3.API/Controllers/BlinkGuildsController.cs b/Blink3.API/Controllers/BlinkGuildsController.cs index 1c6a2ea..ac30870 100644 --- a/Blink3.API/Controllers/BlinkGuildsController.cs +++ b/Blink3.API/Controllers/BlinkGuildsController.cs @@ -1,9 +1,9 @@ +using Blink3.API.Interfaces; using Blink3.Core.Caching; -using Blink3.Core.DTOs; using Blink3.Core.Entities; using Blink3.Core.Models; using Blink3.Core.Repositories.Interfaces; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -14,7 +14,12 @@ namespace Blink3.API.Controllers; /// Controller for performing CRUD operations on BlinkGuild items. /// [SwaggerTag("All CRUD operations for BlinkGuild items")] -public class BlinkGuildsController(DiscordSocketClient discordSocketClient, ICachingService cachingService, IBlinkGuildRepository blinkGuildRepository) : ApiControllerBase(discordSocketClient, cachingService) +public class BlinkGuildsController(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService, + IBlinkGuildRepository blinkGuildRepository) + : ApiControllerBase(botClient, userClientFactory, cachingService, encryptionService) { /// /// Retrieves all BlinkGuild items that are manageable by the logged in user. @@ -62,6 +67,7 @@ public async Task> GetBlinkGuild(ulong id) /// /// The ID of the BlinkGuild item to update. /// The updated BlinkGuild item data. + /// /// /// No content. /// diff --git a/Blink3.API/Controllers/GuildsController.cs b/Blink3.API/Controllers/GuildsController.cs index 74a5430..268a416 100644 --- a/Blink3.API/Controllers/GuildsController.cs +++ b/Blink3.API/Controllers/GuildsController.cs @@ -1,15 +1,22 @@ +using Blink3.API.Interfaces; using Blink3.Core.Caching; using Blink3.Core.Models; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; namespace Blink3.API.Controllers; [SwaggerTag("Endpoints for getting information on discord guilds")] -public class GuildsController(DiscordSocketClient discordSocketClient, ICachingService cachingService) - : ApiControllerBase(discordSocketClient, cachingService) +public class GuildsController(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService) + : ApiControllerBase(botClient, userClientFactory, cachingService, encryptionService) { + private readonly DiscordRestClient _botClient = botClient; + private readonly ICachingService _cachingService = cachingService; + [HttpGet] [SwaggerOperation( Summary = "Returns all Discord guilds", @@ -38,20 +45,28 @@ public async Task>> GetC ObjectResult? accessCheckResult = await CheckGuildAccessAsync(id); if (accessCheckResult is not null) return accessCheckResult; - return DiscordBotClient.GetGuild(id).CategoryChannels - .OrderBy(c => c.Position) - .Select(c => - new DiscordPartialChannel - { - Id = c.Id, - Name = c.Name - }) - .ToList(); + string cacheKey = $"guild_{id}_categories"; + IReadOnlyCollection categories = await _cachingService.GetOrAddAsync(cacheKey, async () => + { + RestGuild guild = await _botClient.GetGuildAsync(id); + IReadOnlyCollection? categories = await guild.GetCategoryChannelsAsync(); + return categories + .OrderBy(c => c.Position) + .Select(c => + new DiscordPartialChannel + { + Id = c.Id, + Name = c.Name + }) + .ToList(); + }, TimeSpan.FromMinutes(5)); + + return Ok(categories); } [HttpGet("{id}/channels")] [SwaggerOperation( - Summary = "Returns all chanels for a guild", + Summary = "Returns all channels for a guild", Description = "Returns a list of all Discord channels for a given guild ID", OperationId = "Guilds.GetChannels", Tags = ["Guilds"] @@ -62,14 +77,22 @@ public async Task>> GetC ObjectResult? accessCheckResult = await CheckGuildAccessAsync(id); if (accessCheckResult is not null) return accessCheckResult; - return DiscordBotClient.GetGuild(id).TextChannels - .OrderBy(c => c.Position) - .Select(c => - new DiscordPartialChannel - { - Id = c.Id, - Name = c.Name - }) - .ToList(); + string cacheKey = $"guild_{id}_channels"; + IReadOnlyCollection channels = await _cachingService.GetOrAddAsync(cacheKey, async () => + { + RestGuild guild = await _botClient.GetGuildAsync(id); + IReadOnlyCollection? channels = await guild.GetTextChannelsAsync(); + return channels + .OrderBy(c => c.Position) + .Select(c => + new DiscordPartialChannel + { + Id = c.Id, + Name = c.Name + }) + .ToList(); + }, TimeSpan.FromMinutes(5)); + + return Ok(channels); } } \ No newline at end of file diff --git a/Blink3.API/Controllers/TodoController.cs b/Blink3.API/Controllers/TodoController.cs index 376fa33..3f995d2 100644 --- a/Blink3.API/Controllers/TodoController.cs +++ b/Blink3.API/Controllers/TodoController.cs @@ -1,8 +1,9 @@ +using Blink3.API.Interfaces; using Blink3.Core.Caching; using Blink3.Core.DTOs; using Blink3.Core.Entities; using Blink3.Core.Repositories.Interfaces; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.Mvc; using Swashbuckle.AspNetCore.Annotations; @@ -12,7 +13,12 @@ namespace Blink3.API.Controllers; /// Controller for performing CRUD operations on userTodo items. /// [SwaggerTag("All CRUD operations for todo items")] -public class TodoController(DiscordSocketClient discordSocketClient, ICachingService cachingService, IUserTodoRepository todoRepository) : ApiControllerBase(discordSocketClient, cachingService) +public class TodoController(DiscordRestClient botClient, + Func userClientFactory, + ICachingService cachingService, + IEncryptionService encryptionService, + IUserTodoRepository todoRepository) + : ApiControllerBase(botClient, userClientFactory, cachingService, encryptionService) { /// /// Retrieves all userTodo items for the current user. diff --git a/Blink3.API/Extensions/ServiceCollectionExtensions.cs b/Blink3.API/Extensions/ServiceCollectionExtensions.cs index 61cd6ad..1a82df2 100644 --- a/Blink3.API/Extensions/ServiceCollectionExtensions.cs +++ b/Blink3.API/Extensions/ServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using System.Security.Claims; using System.Text.Json; using AspNet.Security.OAuth.Discord; +using Blink3.API.Interfaces; using Blink3.Core.Caching; using Blink3.Core.Configuration; using Microsoft.AspNetCore.Authentication.Cookies; @@ -59,10 +60,17 @@ public static void AddDiscordAuth(this IServiceCollection services, BlinkConfigu private static async Task SaveTokenAsync(OAuthCreatingTicketContext context) { ICachingService cachingService = context.HttpContext.RequestServices.GetRequiredService(); + IEncryptionService encryptionService = context.HttpContext.RequestServices.GetRequiredService(); + string? nameIdentifierClaim = context.Identity?.FindFirst(c => c.Type == ClaimTypes.NameIdentifier)?.Value; if (nameIdentifierClaim is not null && context.AccessToken is not null) { - await cachingService.SetAsync($"token:{nameIdentifierClaim}", context.AccessToken, context.ExpiresIn); + string encryptedToken = encryptionService.Encrypt(context.AccessToken, out string iv); + string tokenKey = $"token:{nameIdentifierClaim}"; + + // Store both the encrypted token and the IV + await cachingService.SetAsync(tokenKey, encryptedToken, context.ExpiresIn); + await cachingService.SetAsync($"{tokenKey}:iv", iv, context.ExpiresIn); } } } \ No newline at end of file diff --git a/Blink3.API/Interfaces/IEncryptionService.cs b/Blink3.API/Interfaces/IEncryptionService.cs new file mode 100644 index 0000000..518b4d2 --- /dev/null +++ b/Blink3.API/Interfaces/IEncryptionService.cs @@ -0,0 +1,7 @@ +namespace Blink3.API.Interfaces; + +public interface IEncryptionService +{ + string Encrypt(string plainText, out string iv); + string Decrypt(string cipherText, string iv); +} \ No newline at end of file diff --git a/Blink3.API/Models/EncryptionException.cs b/Blink3.API/Models/EncryptionException.cs new file mode 100644 index 0000000..777eb0d --- /dev/null +++ b/Blink3.API/Models/EncryptionException.cs @@ -0,0 +1,3 @@ +namespace Blink3.API.Models; + +public class EncryptionException(string message, Exception innerException) : Exception(message, innerException); \ No newline at end of file diff --git a/Blink3.API/Program.cs b/Blink3.API/Program.cs index b453d7b..cbdb6a1 100644 --- a/Blink3.API/Program.cs +++ b/Blink3.API/Program.cs @@ -7,8 +7,7 @@ using Blink3.Core.Helpers; using Blink3.DataAccess.Extensions; using Discord; -using Discord.Addons.Hosting; -using Discord.WebSocket; +using Discord.Rest; using Microsoft.AspNetCore.HttpOverrides; using Serilog; using Serilog.Events; @@ -44,17 +43,18 @@ builder.Services.AddAppConfiguration(builder.Configuration); BlinkConfiguration appConfig = builder.Services.GetAppConfiguration(); - // Discord socket client - builder.Services.AddDiscordHost((config, _) => + // Discord bot client + builder.Services.AddSingleton(_ => { - config.SocketConfig = new DiscordSocketConfig - { - LogLevel = LogSeverity.Verbose, - MessageCacheSize = 0, - GatewayIntents = GatewayIntents.Guilds - }; - - config.Token = appConfig.Discord.BotToken; + DiscordRestClient client = new(); + client.LoginAsync(TokenType.Bot, appConfig.Discord.BotToken).Wait(); + return client; + }); + + // Discord user client + builder.Services.AddScoped>(_ => + { + return () => new DiscordRestClient(); }); // Add forwarded headers @@ -87,7 +87,11 @@ // For getting discord tokens builder.Services.AddHttpClient(); builder.Services.AddSingleton(); - + + // Encryption service + string? encryptionKey = Environment.GetEnvironmentVariable("ENCRYPTION_KEY"); + builder.Services.AddSingleton(_ => new EncryptionService(encryptionKey)); + // Configure Authentication and Discord OAuth builder.Services.AddDiscordAuth(appConfig); diff --git a/Blink3.API/Services/EncryptionService.cs b/Blink3.API/Services/EncryptionService.cs new file mode 100644 index 0000000..536f9df --- /dev/null +++ b/Blink3.API/Services/EncryptionService.cs @@ -0,0 +1,71 @@ +using System.Security.Cryptography; +using Blink3.API.Interfaces; +using Blink3.API.Models; + +namespace Blink3.API.Services; + +public class EncryptionService : IEncryptionService +{ + private readonly byte[] _key; + + public EncryptionService(string? key) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException($"{nameof(key)} cannot be null or empty", nameof(key)); + + _key = Convert.FromBase64String(key); + } + + public string Encrypt(string plainText, out string iv) + { + try + { + using Aes aes = Aes.Create(); + aes.Key = _key; + aes.GenerateIV(); + iv = Convert.ToBase64String(aes.IV); + + ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); + + using MemoryStream memoryStream = new(); + using CryptoStream cryptoStream = new(memoryStream, encryptor, CryptoStreamMode.Write); + using StreamWriter streamWriter = new(cryptoStream); + { + streamWriter.Write(plainText); + streamWriter.Flush(); + cryptoStream.FlushFinalBlock(); // Ensure all data is written + } + + return Convert.ToBase64String(memoryStream.ToArray()); + } + catch (Exception e) + { + throw new EncryptionException("Error occurred during encryption.", e); + } + } + + public string Decrypt(string cipherText, string iv) + { + try + { + byte[] cipherBytes = Convert.FromBase64String(cipherText); + byte[] ivBytes = Convert.FromBase64String(iv); + + using Aes aes = Aes.Create(); + aes.Key = _key; + aes.IV = ivBytes; + + ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + + using MemoryStream memoryStream = new(cipherBytes); + using CryptoStream cryptoStream = new(memoryStream, decryptor, CryptoStreamMode.Read); + using StreamReader streamReader = new(cryptoStream); + + return streamReader.ReadToEnd(); + } + catch (Exception e) + { + throw new EncryptionException("Error occurred during decryption.", e); + } + } +} \ No newline at end of file diff --git a/Blink3.Bot/Blink3.Bot.csproj b/Blink3.Bot/Blink3.Bot.csproj index b29a8ea..6b4d53f 100644 --- a/Blink3.Bot/Blink3.Bot.csproj +++ b/Blink3.Bot/Blink3.Bot.csproj @@ -14,10 +14,6 @@ .dockerignore - - appsettings.Development.json - Always - appsettings.json Always @@ -30,17 +26,16 @@ - - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - + diff --git a/Blink3.Core/Blink3.Core.csproj b/Blink3.Core/Blink3.Core.csproj index 80be490..9bf1178 100644 --- a/Blink3.Core/Blink3.Core.csproj +++ b/Blink3.Core/Blink3.Core.csproj @@ -7,20 +7,20 @@ - + - + - + - - - + + + diff --git a/Blink3.DataAccess/Blink3.DataAccess.csproj b/Blink3.DataAccess/Blink3.DataAccess.csproj index 5a60c4d..6f2d12c 100644 --- a/Blink3.DataAccess/Blink3.DataAccess.csproj +++ b/Blink3.DataAccess/Blink3.DataAccess.csproj @@ -11,9 +11,9 @@ - + - + diff --git a/Blink3.Tests/Blink3.Tests.csproj b/Blink3.Tests/Blink3.Tests.csproj index 7f69a76..4beed3a 100644 --- a/Blink3.Tests/Blink3.Tests.csproj +++ b/Blink3.Tests/Blink3.Tests.csproj @@ -14,14 +14,14 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Blink3.Web/05-configure.sh b/Blink3.Web/05-configure.sh new file mode 100644 index 0000000..39ec910 --- /dev/null +++ b/Blink3.Web/05-configure.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +set -e + +echo "Replacing configuration values.." + +# Replace placeholders in appsettings.json with corresponding environment variables +sed -i "s|\${API_ADDRESS}|${API_ADDRESS}|" /usr/share/nginx/html/appsettings.json +sed -i "s|\${CLIENT_ID}|${CLIENT_ID}|" /usr/share/nginx/html/appsettings.json + +echo "Configuration values replaced!" \ No newline at end of file diff --git a/Blink3.Web/Blink3.Web.csproj b/Blink3.Web/Blink3.Web.csproj index 75d5f19..09c9a9a 100644 --- a/Blink3.Web/Blink3.Web.csproj +++ b/Blink3.Web/Blink3.Web.csproj @@ -8,12 +8,12 @@ - + - - - - + + + + diff --git a/Blink3.Web/Dockerfile b/Blink3.Web/Dockerfile index 721d8fa..9eaf564 100644 --- a/Blink3.Web/Dockerfile +++ b/Blink3.Web/Dockerfile @@ -17,4 +17,8 @@ RUN dotnet publish "Blink3.Web.csproj" -c $BUILD_CONFIGURATION -o /app/publish / FROM base AS final WORKDIR /usr/share/nginx/html COPY --from=publish /app/publish/wwwroot . -COPY Blink3.Web/nginx.conf /etc/nginx/nginx.conf \ No newline at end of file +COPY Blink3.Web/nginx.conf /etc/nginx/nginx.conf + +# Add and run the initialization script +COPY Blink3.Web/05-configure.sh /docker-entrypoint.d/05-configure.sh +RUN chmod +x /docker-entrypoint.d/05-configure.sh \ No newline at end of file diff --git a/Blink3.Web/wwwroot/appsettings.json b/Blink3.Web/wwwroot/appsettings.json index 264c6c7..6b8f51d 100644 --- a/Blink3.Web/wwwroot/appsettings.json +++ b/Blink3.Web/wwwroot/appsettings.json @@ -1,4 +1,4 @@ { - "ApiAddress": "http://localhost:8288/", - "ClientID": "1223292750766674080" + "ApiAddress": "${API_ADDRESS}", + "ClientID": "${CLIENT_ID}" } \ No newline at end of file diff --git a/Blink3.sln b/Blink3.sln index 0eeb39f..138f1e5 100644 --- a/Blink3.sln +++ b/Blink3.sln @@ -13,13 +13,14 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution Files", "{DC0DA219-4EFC-4B51-9E12-A28199354461}" ProjectSection(SolutionItems) = preProject docker-compose.development.yml = docker-compose.development.yml - docker-compose.production.yml = docker-compose.production.yml docker-compose.yml = docker-compose.yml Configuration\appsettings.json = Configuration\appsettings.json - Configuration\appsettings.Development.json = Configuration\appsettings.Development.json .github\workflows\build.yml = .github\workflows\build.yml .github\workflows\docker-image.yml = .github\workflows\docker-image.yml README.md = README.md + .dockerignore = .dockerignore + .env = .env + .env.example = .env.example EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blink3.Tests", "Blink3.Tests\Blink3.Tests.csproj", "{D8F3BEB2-9D9B-49CD-8711-D1A4BA78C689}" diff --git a/Blink3.sln.DotSettings b/Blink3.sln.DotSettings index c7c47fe..0fd241e 100644 --- a/Blink3.sln.DotSettings +++ b/Blink3.sln.DotSettings @@ -1,5 +1,6 @@  True + True True True True diff --git a/Configuration/appsettings.Development.json b/Configuration/appsettings.Development.json deleted file mode 100644 index 7a73a41..0000000 --- a/Configuration/appsettings.Development.json +++ /dev/null @@ -1,2 +0,0 @@ -{ -} \ No newline at end of file diff --git a/README.md b/README.md index 94ae416..c8bcb32 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,6 @@ Currently, the bot provides several commands, including: Blink3 includes multiple Docker images for each of the solution's parts and Docker Compose files for orchestration. The Docker Compose files included are: - `docker-compose.yml` - The base Docker Compose file. - `docker-compose.development.yml` - Overwrites the base configuration for local development. References the images built in the local solution. -- `docker-compose.production.yml` - Overwrites the base configuration for production environments. References the images hosted on GitHub Container Registry. To build and run the application using Docker Compose, you can execute the following commands: @@ -37,7 +36,7 @@ For local development: For production: - docker-compose -f docker-compose.yml -f docker-compose.production.yml up -d + docker-compose up -d An `.env.example` file has been included in the repository. Rename or copy this file to `.env` and replace the sample values with your actual environment values to set up your environment. diff --git a/docker-compose.development.yml b/docker-compose.development.yml index a34913d..b53e359 100644 --- a/docker-compose.development.yml +++ b/docker-compose.development.yml @@ -1,45 +1,46 @@ -version: '3.8' +--- + +x-common-environment: &common-env + ASPNETCORE_ENVIRONMENT: Development services: - blink3.db: + postgres: ports: - "5432:5432" - blink3.cache: + redis: ports: - "6379:6379" - blink3.bot: + bot: environment: - - ASPNETCORE_ENVIRONMENT=Development - - WordLists__en__SolutionWordsFile=/app/bin/Debug/net8.0/Words/en-solution.txt - - WordLists__en__GuessWordsFile=/app/bin/Debug/net8.0/Words/en-guess.txt - - WordLists__es__SolutionWordsFile=/app/bin/Debug/net8.0/Words/es-solution.txt + <<: *common-env + WordLists__en__SolutionWordsFile: /app/bin/Debug/net8.0/Words/en-solution.txt + WordLists__en__GuessWordsFile: /app/bin/Debug/net8.0/Words/en-guess.txt + WordLists__es__SolutionWordsFile: /app/bin/Debug/net8.0/Words/es-solution.txt build: context: . dockerfile: Blink3.Bot/Dockerfile - blink3.api: + api: environment: - - ASPNETCORE_ENVIRONMENT=Development + <<: *common-env build: context: . dockerfile: Blink3.API/Dockerfile ports: - "8288:8080" - blink3.web: + web: build: context: . dockerfile: Blink3.Web/Dockerfile - volumes: - - ./Blink3.Web/wwwroot/appsettings.json:/usr/share/nginx/html/appsettings.json ports: - "8280:80" - blink3.activity: - build: - context: . - dockerfile: Blink3.Activity/Dockerfile - ports: - - "8380:80" \ No newline at end of file + #activity: + # build: + # context: . + # dockerfile: Blink3.Activity/Dockerfile + # ports: + # - "8380:80" \ No newline at end of file diff --git a/docker-compose.production.yml b/docker-compose.production.yml deleted file mode 100644 index 604c2aa..0000000 --- a/docker-compose.production.yml +++ /dev/null @@ -1,31 +0,0 @@ -version: '3.8' - -services: - blink3.bot: - environment: - - ASPNETCORE_ENVIRONMENT=Production - image: ghcr.io/epicofficer/blink3.bot:latest - - blink3.api: - environment: - - ASPNETCORE_ENVIRONMENT=Production - image: ghcr.io/epicofficer/blink3.api:latest - networks: - - default - - blink3_public - - blink3.web: - image: ghcr.io/epicofficer/blink3.web:latest - volumes: - - ./appsettings.json:/usr/share/nginx/html/appsettings.json - networks: - - blink3_public - - blink3.activity: - image: ghcr.io/epicofficer/blink3.activity:latest - networks: - - blink3_public - -networks: - blink3_public: - external: true \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 4ad42c6..5475ae9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,12 @@ -version: '3.8' +--- + +x-common-environment: &common-env + ConnectionStrings__DefaultConnection: Server=postgres;Port=5432;Database=${POSTGRES_USER};Userid=${POSTGRES_USER};Password=${POSTGRES_PASSWORD};SslMode=Prefer;TrustServerCertificate=true + Redis__ConnectionString: redis,ssl=false,name=${REDIS_NAME} + ASPNETCORE_ENVIRONMENT: Production services: - blink3.cache: - container_name: blink3_cache + redis: image: redis:7.2-alpine restart: always healthcheck: @@ -11,20 +15,19 @@ services: timeout: 5s retries: 5 environment: - - REDIS_NAME=${REDIS_NAME} + REDIS_NAME: ${POSTGRES_USER} - blink3.db: - container_name: blink3_db + postgres: image: postgres:16.2-alpine restart: always environment: - - PGDATA=/var/lib/postgresql/data/pgdata - - POSTGRES_USER=${POSTGRES_USER} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + PGDATA: /var/lib/postgresql/data/pgdata + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes: - - ./data:/var/lib/postgresql/data + - postgres_data:/var/lib/postgresql/data depends_on: - blink3.cache: + redis: condition: service_healthy healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER}" ] @@ -32,40 +35,56 @@ services: timeout: 5s retries: 5 - blink3.bot: - container_name: blink3_bot + bot: restart: always environment: - - Discord__DevGuildId=${DISCORD_DEV_GUILD} - - Discord__BotToken=${DISCORD_BOT_TOKEN} - - ConnectionStrings__DefaultConnection=Server=blink3.db;Port=5432;Database=${POSTGRES_USER};Userid=${POSTGRES_USER};Password=${POSTGRES_PASSWORD};SslMode=Prefer;TrustServerCertificate=true - - Redis__ConnectionString=blink3.cache,ssl=false,name=${REDIS_NAME} - - RunMigrations=${BLINK_RUN_MIGRATIONS} - - WordsApiKey=${WORDS_API_KEY} + <<: *common-env + Discord__BotToken: ${DISCORD_BOT_TOKEN} + Discord__DevGuildId: ${DISCORD_DEV_GUILD} + RunMigrations: ${BLINK_RUN_MIGRATIONS} + WordsApiKey: ${WORDS_API_KEY} depends_on: - blink3.db: + postgres: condition: service_healthy + image: ghcr.io/epicofficer/blink3.bot:latest - blink3.api: - container_name: blink3_api + api: restart: always environment: - - Discord__ClientId=${DISCORD_CLIENT_ID} - - Discord__ClientSecret=${DISCORD_CLIENT_SECRET} - - Discord__BotToken=${DISCORD_BOT_TOKEN} - - ConnectionStrings__DefaultConnection=Server=blink3.db;Port=5432;Database=${POSTGRES_USER};Userid=${POSTGRES_USER};Password=${POSTGRES_PASSWORD};SslMode=Prefer;TrustServerCertificate=true - - Redis__ConnectionString=blink3.cache,ssl=false,name=${REDIS_NAME} - - ApiAllowedOrigins__0=${API_ALLOWED_ORIGIN} + <<: *common-env + Discord__ClientId: ${DISCORD_CLIENT_ID} + Discord__ClientSecret: ${DISCORD_CLIENT_SECRET} + ApiAllowedOrigins__0: ${API_ALLOWED_ORIGIN} + ENCRYPTION_KEY: ${ENCRYPTION_KEY} depends_on: - blink3.db: + postgres: condition: service_healthy + image: ghcr.io/epicofficer/blink3.api:latest + networks: + - default + - public - blink3.web: - container_name: blink3_web + web: restart: always + image: ghcr.io/epicofficer/blink3.web:latest + environment: + API_ADDRESS: ${API_URL} + CLIENT_ID: ${DISCORD_CLIENT_ID} depends_on: - - blink3.api + - api + networks: + - public + + # Not implemented, yet... + #activity: + # restart: always + # image: ghcr.io/epicofficer/blink3.activity:latest + # networks: + # - public + +networks: + public: + driver: bridge - blink3.activity: - container_name: blink3_activity - restart: always \ No newline at end of file +volumes: + postgres_data: \ No newline at end of file