diff --git a/.github/workflows/auth.yml b/.github/workflows/auth.yml index 2b83d9d..602d3d6 100644 --- a/.github/workflows/auth.yml +++ b/.github/workflows/auth.yml @@ -47,7 +47,7 @@ jobs: IMAGE_VERSION=${GITHUB_REF#refs/tags/} fi docker buildx build \ - -t ${{ secrets.DOCKER_HUB_USERNAME }}/post-service:${IMAGE_VERSION} \ + -t ${{ secrets.DOCKER_HUB_USERNAME }}/auth-service:${IMAGE_VERSION} \ -f ./backend/src/AuthService/Dockerfile \ ./backend/src diff --git a/.github/workflows/compose-build.yml b/.github/workflows/compose-build.yml index d76aff6..827277b 100644 --- a/.github/workflows/compose-build.yml +++ b/.github/workflows/compose-build.yml @@ -29,6 +29,14 @@ jobs: curl --fail http://0.0.0.0:8000/auth/health && break || sleep 5 done curl --fail http://0.0.0.0:8000/auth/health || exit 1 + + - name: Health Check FileService + run: | + echo "Checking health..." + for i in {1..10}; do + curl --fail http://0.0.0.0:8000/file/health && break || sleep 5 + done + curl --fail http://0.0.0.0:8000/file/health || exit 1 - name: Health Check PostService run: | diff --git a/.github/workflows/file.yml b/.github/workflows/file.yml new file mode 100644 index 0000000..d8084e0 --- /dev/null +++ b/.github/workflows/file.yml @@ -0,0 +1,58 @@ +name: File Service CI/CD Pipeline + +on: + push: + paths: + - backend/src/** + - .github/workflows/file.yml + branches: + - master + - develop + tags: + - 'v*' + pull_request: + types: [opened, synchronize, reopened] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run reusable Build and Test action + uses: ./.github/actions/build-and-test + with: + backendCsprojPath: ./backend/src/FileService/FileService.Api/FileService.Api.csproj +# unitTestsProjectPath: ./backend/tests/FileService/FileService.Domain.Tests/FileService.Domain.Tests.csproj + buildConfiguration: Release + serviceName: FileService + + docker: + needs: build-and-test + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Log in to Docker Hub + run: echo "${{ secrets.DOCKER_HUB_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_HUB_USERNAME }}" --password-stdin + + - name: Build Docker image + run: | + IMAGE_VERSION="latest" + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + IMAGE_VERSION=${GITHUB_REF#refs/tags/} + fi + docker buildx build \ + -t ${{ secrets.DOCKER_HUB_USERNAME }}/file-service:${IMAGE_VERSION} \ + -f ./backend/src/FileService/Dockerfile \ + ./backend/src + + - name: Push Docker image to Docker Hub + if: startsWith(github.ref, 'refs/tags/') + run: | + IMAGE_VERSION=${GITHUB_REF#refs/tags/} + docker push ${{ secrets.DOCKER_HUB_USERNAME }}/file-service:${IMAGE_VERSION} \ No newline at end of file diff --git a/FitnessApp.sln b/FitnessApp.sln index 457ef88..7e4585a 100644 --- a/FitnessApp.sln +++ b/FitnessApp.sln @@ -65,6 +65,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\compose-build.yml = .github\workflows\compose-build.yml .github\workflows\frontend.yml = .github\workflows\frontend.yml .github\workflows\post.yml = .github\workflows\post.yml + .github\workflows\file.yml = .github\workflows\file.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "actions", "actions", "{499BA3B4-A81F-4079-B256-CC04927BB06D}" @@ -83,6 +84,21 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gateway", "backend\src\Gate EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gateway", "backend\src\Gateway\Gateway\Gateway.csproj", "{830B4F05-7C89-4C0F-B27D-B1008B605E79}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "FileService", "FileService", "{43F219C3-D6F8-4BA4-890D-4411CCEA3034}" + ProjectSection(SolutionItems) = preProject + backend\src\FileService\Dockerfile = backend\src\FileService\Dockerfile + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Api", "backend\src\FileService\FileService.Api\FileService.Api.csproj", "{A42D3D0A-750D-4E69-ACD5-751D64BC50E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Domain", "backend\src\FileService\FileService.Domain\FileService.Domain.csproj", "{757BB459-C6FF-4286-BF77-BBA57C201731}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Application", "backend\src\FileService\FileService.Application\FileService.Application.csproj", "{222CCDC4-3D79-4954-B7E0-736D474270D6}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Persistence", "backend\src\FileService\FileService.Persistence\FileService.Persistence.csproj", "{2F748171-33D6-484F-924C-21539C87483E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileService.Infrastructure", "backend\src\FileService\FileService.Infrastructure\FileService.Infrastructure.csproj", "{4938EBCD-F47E-4ABF-AD79-A5DEB42341DA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -153,6 +169,26 @@ Global {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Debug|Any CPU.Build.0 = Debug|Any CPU {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Release|Any CPU.ActiveCfg = Release|Any CPU {830B4F05-7C89-4C0F-B27D-B1008B605E79}.Release|Any CPU.Build.0 = Release|Any CPU + {A42D3D0A-750D-4E69-ACD5-751D64BC50E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A42D3D0A-750D-4E69-ACD5-751D64BC50E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A42D3D0A-750D-4E69-ACD5-751D64BC50E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A42D3D0A-750D-4E69-ACD5-751D64BC50E9}.Release|Any CPU.Build.0 = Release|Any CPU + {757BB459-C6FF-4286-BF77-BBA57C201731}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {757BB459-C6FF-4286-BF77-BBA57C201731}.Debug|Any CPU.Build.0 = Debug|Any CPU + {757BB459-C6FF-4286-BF77-BBA57C201731}.Release|Any CPU.ActiveCfg = Release|Any CPU + {757BB459-C6FF-4286-BF77-BBA57C201731}.Release|Any CPU.Build.0 = Release|Any CPU + {222CCDC4-3D79-4954-B7E0-736D474270D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {222CCDC4-3D79-4954-B7E0-736D474270D6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {222CCDC4-3D79-4954-B7E0-736D474270D6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {222CCDC4-3D79-4954-B7E0-736D474270D6}.Release|Any CPU.Build.0 = Release|Any CPU + {2F748171-33D6-484F-924C-21539C87483E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F748171-33D6-484F-924C-21539C87483E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F748171-33D6-484F-924C-21539C87483E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F748171-33D6-484F-924C-21539C87483E}.Release|Any CPU.Build.0 = Release|Any CPU + {4938EBCD-F47E-4ABF-AD79-A5DEB42341DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4938EBCD-F47E-4ABF-AD79-A5DEB42341DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4938EBCD-F47E-4ABF-AD79-A5DEB42341DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4938EBCD-F47E-4ABF-AD79-A5DEB42341DA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {2E44C019-ED30-4671-A878-D2F7B6071024} = {84AE3F00-9A59-4A8D-A737-67AB74E5D1FD} @@ -180,5 +216,11 @@ Global {334DF17C-BD69-47B9-B4D0-ACB5AD6223CF} = {112E9C63-BF62-49C1-AD19-03855535225B} {DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7} = {B7E4E4CF-E7A9-4DAC-807E-DFF17553BAE8} {830B4F05-7C89-4C0F-B27D-B1008B605E79} = {DC9B9F00-24BF-4AD3-B45A-38BFCFC309D7} + {43F219C3-D6F8-4BA4-890D-4411CCEA3034} = {B7E4E4CF-E7A9-4DAC-807E-DFF17553BAE8} + {A42D3D0A-750D-4E69-ACD5-751D64BC50E9} = {43F219C3-D6F8-4BA4-890D-4411CCEA3034} + {757BB459-C6FF-4286-BF77-BBA57C201731} = {43F219C3-D6F8-4BA4-890D-4411CCEA3034} + {222CCDC4-3D79-4954-B7E0-736D474270D6} = {43F219C3-D6F8-4BA4-890D-4411CCEA3034} + {2F748171-33D6-484F-924C-21539C87483E} = {43F219C3-D6F8-4BA4-890D-4411CCEA3034} + {4938EBCD-F47E-4ABF-AD79-A5DEB42341DA} = {43F219C3-D6F8-4BA4-890D-4411CCEA3034} EndGlobalSection EndGlobal diff --git a/backend/src/FileService/Dockerfile b/backend/src/FileService/Dockerfile new file mode 100644 index 0000000..de42a93 --- /dev/null +++ b/backend/src/FileService/Dockerfile @@ -0,0 +1,34 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +WORKDIR /app +EXPOSE 8004 + +RUN apt-get update && apt-get install -y curl && apt-get clean + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build + +WORKDIR /src + +COPY FileService/FileService.Api/FileService.Api.csproj FileService/FileService.Api/ +COPY FileService/FileService.Application/FileService.Application.csproj FileService/FileService.Application/ +COPY FileService/FileService.Domain/FileService.Domain.csproj FileService/FileService.Domain/ +COPY FileService/FileService.Infrastructure/FileService.Infrastructure.csproj FileService/FileService.Infrastructure/ +COPY FileService/FileService.Persistence/FileService.Persistence.csproj FileService/FileService.Persistence/ +COPY Shared/Shared.Application/Shared.Application.csproj Shared/Shared.Application/ +COPY Shared/Shared.DTO/Shared.DTO.csproj Shared/Shared.DTO/ + +RUN dotnet restore FileService/FileService.Api/FileService.Api.csproj + +COPY . . + +WORKDIR /src/FileService/FileService.Api + +RUN dotnet build FileService.Api.csproj -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish FileService.Api.csproj -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . + +ENTRYPOINT ["dotnet", "FileService.Api.dll"] \ No newline at end of file diff --git a/backend/src/FileService/FileService.Api/FileService.Api.csproj b/backend/src/FileService/FileService.Api/FileService.Api.csproj new file mode 100644 index 0000000..a202dea --- /dev/null +++ b/backend/src/FileService/FileService.Api/FileService.Api.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/backend/src/FileService/FileService.Api/Program.cs b/backend/src/FileService/FileService.Api/Program.cs new file mode 100644 index 0000000..77a7909 --- /dev/null +++ b/backend/src/FileService/FileService.Api/Program.cs @@ -0,0 +1,28 @@ +using FileService.Application; +using FileService.Domain.Constants; +using FileService.Infrastructure; +using FileService.Persistence; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +var hostingUrl = builder.Configuration[AppSettingsConstants.WebHostUrl]; + +builder.WebHost.UseUrls(hostingUrl ?? throw new ArgumentNullException(nameof(hostingUrl), "Hosting URL is not configured.")); + +builder.Services + .AddPersistenceServices(builder.Configuration) + .AddInfrastructureServices(builder.Configuration) + .AddApplicationServices(); + +var app = builder.Build(); + +using (var scope = app.Services.CreateScope()) +{ + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.Migrate(); +} + +app.MapGet("/health", () => Results.Ok("Healthy")); + +app.Run(); \ No newline at end of file diff --git a/backend/src/FileService/FileService.Api/Properties/launchSettings.json b/backend/src/FileService/FileService.Api/Properties/launchSettings.json new file mode 100644 index 0000000..d9cad42 --- /dev/null +++ b/backend/src/FileService/FileService.Api/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:13401", + "sslPort": 44319 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "graphql", + "applicationUrl": "http://localhost:8004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "graphql", + "applicationUrl": "https://localhost:7144;http://localhost:8004", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/src/FileService/FileService.Api/appsettings.json b/backend/src/FileService/FileService.Api/appsettings.json new file mode 100644 index 0000000..37d4433 --- /dev/null +++ b/backend/src/FileService/FileService.Api/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "WebHostUrl": "http://0.0.0.0:8004", + "ConnectionStrings": { + "DatabaseConnectionString": "Host=localhost;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=FileDb", + "AzureStorageConnectionString": "AccountName=sentemon;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=https;BlobEndpoint=https://localhost:10000/sentemon" + } +} diff --git a/backend/src/FileService/FileService.Application/DependencyInjection.cs b/backend/src/FileService/FileService.Application/DependencyInjection.cs new file mode 100644 index 0000000..7f2379f --- /dev/null +++ b/backend/src/FileService/FileService.Application/DependencyInjection.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace FileService.Application; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplicationServices(this IServiceCollection services) + { + return services; + } +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Application/FileService.Application.csproj b/backend/src/FileService/FileService.Application/FileService.Application.csproj new file mode 100644 index 0000000..83fc3e1 --- /dev/null +++ b/backend/src/FileService/FileService.Application/FileService.Application.csproj @@ -0,0 +1,13 @@ + + + + net8.0 + enable + enable + + + + + + + diff --git a/backend/src/FileService/FileService.Domain/Constants/AppSettingsConstants.cs b/backend/src/FileService/FileService.Domain/Constants/AppSettingsConstants.cs new file mode 100644 index 0000000..0559b7a --- /dev/null +++ b/backend/src/FileService/FileService.Domain/Constants/AppSettingsConstants.cs @@ -0,0 +1,8 @@ +namespace FileService.Domain.Constants; + +public static class AppSettingsConstants +{ + public const string WebHostUrl = "WebHostUrl"; + public const string DatabaseConnectionString = "DatabaseConnectionString"; + public const string AzureStorageConnectionString = "AzureStorageConnectionString"; +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Domain/Entities/File.cs b/backend/src/FileService/FileService.Domain/Entities/File.cs new file mode 100644 index 0000000..27aa399 --- /dev/null +++ b/backend/src/FileService/FileService.Domain/Entities/File.cs @@ -0,0 +1,24 @@ +namespace FileService.Domain.Entities; + +public class File +{ + public Guid Id { get; private set; } + public string Name { get; private set; } + public string BlobName { get; private set; } + public long Size { get; private set; } + public string OwnerId { get; private set; } + public DateTime CreatedAt { get; private set; } + + private File(string name, string blobName, long size, string ownerId) + { + Name = name; + BlobName = blobName; + Size = size; + OwnerId = ownerId; + } + + public static File CreateFile(string name, string blobName, long size, string ownerId) + { + return new File(name, blobName, size, ownerId); + } +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Domain/FileService.Domain.csproj b/backend/src/FileService/FileService.Domain/FileService.Domain.csproj new file mode 100644 index 0000000..3a63532 --- /dev/null +++ b/backend/src/FileService/FileService.Domain/FileService.Domain.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/backend/src/FileService/FileService.Infrastructure/DependencyInjection.cs b/backend/src/FileService/FileService.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..6573da4 --- /dev/null +++ b/backend/src/FileService/FileService.Infrastructure/DependencyInjection.cs @@ -0,0 +1,18 @@ +using Azure.Storage.Blobs; +using FileService.Domain.Constants; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace FileService.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructureServices(this IServiceCollection services, IConfiguration configuration) + { + var azureStorageConnectionString = configuration.GetConnectionString(AppSettingsConstants.AzureStorageConnectionString); + + services.AddSingleton(new BlobServiceClient(azureStorageConnectionString)); + + return services; + } +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Infrastructure/FileService.Infrastructure.csproj b/backend/src/FileService/FileService.Infrastructure/FileService.Infrastructure.csproj new file mode 100644 index 0000000..18989d2 --- /dev/null +++ b/backend/src/FileService/FileService.Infrastructure/FileService.Infrastructure.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/backend/src/FileService/FileService.Persistence/Configurations/FileConfiguration.cs b/backend/src/FileService/FileService.Persistence/Configurations/FileConfiguration.cs new file mode 100644 index 0000000..7bdd9e3 --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/Configurations/FileConfiguration.cs @@ -0,0 +1,34 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using File = FileService.Domain.Entities.File; + +namespace FileService.Persistence.Configurations; + +public class FileConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(f => f.Id); + + builder.Property(f => f.Id) + .ValueGeneratedOnAdd(); + + builder.Property(f => f.Name) + .HasMaxLength(512) + .IsRequired(); + + builder.Property(f => f.BlobName) + .HasMaxLength(32) + .IsRequired(); + + builder.Property(f => f.Size) + .IsRequired(); + + builder.Property(f => f.OwnerId) + .IsRequired(); + + builder.Property(f => f.CreatedAt) + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .ValueGeneratedOnAdd(); + } +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Persistence/DependencyInjection.cs b/backend/src/FileService/FileService.Persistence/DependencyInjection.cs new file mode 100644 index 0000000..ec44858 --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/DependencyInjection.cs @@ -0,0 +1,23 @@ +using FileService.Domain.Constants; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace FileService.Persistence; + +public static class DependencyInjection +{ + public static IServiceCollection AddPersistenceServices(this IServiceCollection services, IConfiguration configuration) + { + var databaseConnectionString = configuration.GetConnectionString(AppSettingsConstants.DatabaseConnectionString); + + if (string.IsNullOrEmpty(databaseConnectionString)) + { + throw new ArgumentNullException(nameof(databaseConnectionString), "Connection String cannot be empty."); + } + + services.AddDbContext(options => + options.UseNpgsql(databaseConnectionString)); + return services; + } +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Persistence/FileDbContext.cs b/backend/src/FileService/FileService.Persistence/FileDbContext.cs new file mode 100644 index 0000000..95f37da --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/FileDbContext.cs @@ -0,0 +1,19 @@ +using FileService.Persistence.Configurations; +using Microsoft.EntityFrameworkCore; +using File = FileService.Domain.Entities.File; + +namespace FileService.Persistence; + +public class FileDbContext : DbContext +{ + public FileDbContext(DbContextOptions options) : base(options) + { + } + + public DbSet Files { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new FileConfiguration()); + } +} \ No newline at end of file diff --git a/backend/src/FileService/FileService.Persistence/FileService.Persistence.csproj b/backend/src/FileService/FileService.Persistence/FileService.Persistence.csproj new file mode 100644 index 0000000..020bc81 --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/FileService.Persistence.csproj @@ -0,0 +1,19 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + diff --git a/backend/src/FileService/FileService.Persistence/Migrations/20250110090701_InitialMigration.Designer.cs b/backend/src/FileService/FileService.Persistence/Migrations/20250110090701_InitialMigration.Designer.cs new file mode 100644 index 0000000..6a79bad --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/Migrations/20250110090701_InitialMigration.Designer.cs @@ -0,0 +1,63 @@ +// +using System; +using FileService.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FileService.Persistence.Migrations +{ + [DbContext(typeof(FileDbContext))] + [Migration("20250110090701_InitialMigration")] + partial class InitialMigration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("FileService.Domain.Entities.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/FileService/FileService.Persistence/Migrations/20250110090701_InitialMigration.cs b/backend/src/FileService/FileService.Persistence/Migrations/20250110090701_InitialMigration.cs new file mode 100644 index 0000000..f3b41ad --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/Migrations/20250110090701_InitialMigration.cs @@ -0,0 +1,38 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FileService.Persistence.Migrations +{ + /// + public partial class InitialMigration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Files", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Name = table.Column(type: "character varying(512)", maxLength: 512, nullable: false), + BlobName = table.Column(type: "character varying(32)", maxLength: 32, nullable: false), + Size = table.Column(type: "bigint", nullable: false), + OwnerId = table.Column(type: "text", nullable: false), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP") + }, + constraints: table => + { + table.PrimaryKey("PK_Files", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Files"); + } + } +} diff --git a/backend/src/FileService/FileService.Persistence/Migrations/FileDbContextModelSnapshot.cs b/backend/src/FileService/FileService.Persistence/Migrations/FileDbContextModelSnapshot.cs new file mode 100644 index 0000000..55cbd43 --- /dev/null +++ b/backend/src/FileService/FileService.Persistence/Migrations/FileDbContextModelSnapshot.cs @@ -0,0 +1,60 @@ +// +using System; +using FileService.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FileService.Persistence.Migrations +{ + [DbContext(typeof(FileDbContext))] + partial class FileDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("FileService.Domain.Entities.File", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("BlobName") + .IsRequired() + .HasMaxLength(32) + .HasColumnType("character varying(32)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(512) + .HasColumnType("character varying(512)"); + + b.Property("OwnerId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("Files"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/src/Gateway/Gateway/appsettings.json b/backend/src/Gateway/Gateway/appsettings.json index f51fe42..bb8d9d2 100644 --- a/backend/src/Gateway/Gateway/appsettings.json +++ b/backend/src/Gateway/Gateway/appsettings.json @@ -21,6 +21,17 @@ } ] }, + "FileService": { + "ClusterId": "file-service", + "Match": { + "Path": "/file/{endpoint:regex(^graphql$|^health$)}" + }, + "Transforms": [ + { + "PathRemovePrefix": "/file" + } + ] + }, "PostService": { "ClusterId": "post-service", "Match": { @@ -42,6 +53,13 @@ } } }, + "file-service": { + "Destinations": { + "destination1": { + "Address": "http://localhost:8004" + } + } + }, "post-service": { "Destinations": { "destination1": { diff --git a/docker-compose.yml b/docker-compose.yml index b3e7801..d6eabeb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,6 +25,7 @@ services: - "8000:8000" environment: ReverseProxy__Clusters__auth-service__Destinations__destination1__Address: http://auth-service:8001 + ReverseProxy__Clusters__file-service__Destinations__destination1__Address: http://file-service:8004 ReverseProxy__Clusters__post-service__Destinations__destination1__Address: http://post-service:8002 depends_on: postgres: @@ -61,6 +62,27 @@ services: networks: - microservices + file-service: + container_name: file-service + build: + context: ./backend/src + dockerfile: FileService/Dockerfile + ports: + - "8004:8004" + environment: + ConnectionStrings__DatabaseConnectionString: Host=postgres;Port=5432;Username=postgres;Password=mysecretpasswordfordevelopment;Database=FileDb + ConnectionStrings__AzureStorageConnectionString: AccountName=sentemon;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=https;BlobEndpoint=https://azurite:10000/sentemon + depends_on: + gateway: + condition: service_healthy + healthcheck: + test: [ "CMD-SHELL", "curl -f http://file-service:8004/health || exit 1" ] + interval: 10s + timeout: 5s + retries: 3 + networks: + - microservices + post-service: container_name: post-service build: @@ -115,6 +137,18 @@ services: restart: always networks: - microservices + + azurite: + image: mcr.microsoft.com/azure-storage/azurite + container_name: azurite + ports: + - "10000:10000" + command: "azurite --blobHost 0.0.0.0 --blobPort 10000" + restart: always + environment: + - AZURITE_ACCOUNTS=sentemon:Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw== + networks: + - microservices rabittmq: image: rabbitmq:4-management