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