Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 4 additions & 10 deletions backend/ExternalApi/GamificationApi.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using Microsoft.Extensions.Options;

using TaskSync.ExternalApi.Interfaces;
using TaskSync.ExternalApi.Interfaces;
using TaskSync.Infrastructure.Http.Interface;
using TaskSync.Infrastructure.Settings;
using TaskSync.Models.Dto;

using TaskStatus = TaskSync.Enums.TASK_STATUS;
Expand All @@ -11,24 +8,22 @@ namespace TaskSync.ExternalApi
{
public class GamificationApi : IGamificationApi
{
private readonly GamificationApiSettings _gamApiSettings;
private readonly IHttpContextReader _httpContextReader;
private readonly HttpClient _httpClient;
private readonly ILogger<GamificationApi> _logger;

public GamificationApi(IOptions<GamificationApiSettings> options, IHttpContextReader httpContextReader, IHttpClientFactory httpClientFactory, ILogger<GamificationApi> logger)
public GamificationApi(IHttpContextReader httpContextReader, IHttpClientFactory httpClientFactory, ILogger<GamificationApi> logger)
{
_gamApiSettings = options.Value;
_httpContextReader = httpContextReader;
_httpClient = httpClientFactory.CreateClient();
_httpClient = httpClientFactory.CreateClient("GamificationApi");
_logger = logger;
}

public async Task UpdatePoint(int taskId, TaskStatus status)
{
try
{
var httpMessage = new HttpRequestMessage(HttpMethod.Post, $"{_gamApiSettings.BaseUrl}/points")
var httpMessage = new HttpRequestMessage(HttpMethod.Post, "points")
{
Content = JsonContent.Create(new CreatePointDto()
{
Expand All @@ -37,7 +32,6 @@ public async Task UpdatePoint(int taskId, TaskStatus status)
UserId = _httpContextReader.GetUserId(),
}),
};
httpMessage.Headers.Add("x-gamapi-auth", "true");

await _httpClient.SendAsync(httpMessage);
}
Expand Down
20 changes: 12 additions & 8 deletions backend/Infrastructure/Configurations/CorsConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
namespace TaskSync.Infrastructure.Configurations
using Microsoft.Extensions.Options;

using TaskSync.Infrastructure.Settings;

namespace TaskSync.Infrastructure.Configurations
{
public static class CorsConfiguration
{
public static IServiceCollection ConfigureCors(this IServiceCollection services)
{
var provider = services.BuildServiceProvider();

services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins(
"http://localhost:3039", // Dev
"http://131.189.90.113:3039") // Production
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(10)); // Cache preflight 10 min
policy.WithOrigins(provider.GetRequiredService<IOptions<FrontendSettings>>().Value.Urls)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
.SetPreflightMaxAge(TimeSpan.FromMinutes(10)); // Cache preflight 10 min
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

using TaskSync.ExternalApi;
using TaskSync.ExternalApi.Interfaces;
using TaskSync.Infrastructure.Caching;
using TaskSync.Infrastructure.Caching.Interfaces;
using TaskSync.Infrastructure.Http;
using TaskSync.Infrastructure.Http.Interface;
using TaskSync.Infrastructure.Settings;
using TaskSync.Repositories;
using TaskSync.Repositories.Entities;
using TaskSync.Repositories.Interfaces;
Expand All @@ -18,10 +20,12 @@ namespace TaskSync.Infrastructure.Configurations
{
public static class DependencyInjectionConfiguration
{
public static IServiceCollection ConfigureDependencyInjection(this IServiceCollection services, IConfiguration configuration)
public static IServiceCollection ConfigureDependencyInjection(this IServiceCollection services)
{
var provider = services.BuildServiceProvider();

services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
options.UseNpgsql(provider.GetRequiredService<IOptions<PostgreSqlSettings>>().Value.DefaultConnection)
);

services.AddSingleton<IJwtService, JwtService>();
Expand Down
22 changes: 22 additions & 0 deletions backend/Infrastructure/Configurations/HttpClientConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using Microsoft.Extensions.Options;

using TaskSync.Infrastructure.Settings;

namespace TaskSync.Infrastructure.Configurations
{
public static class HttpClientConfiguration
{
public static IServiceCollection ConfigureHttpClient(this IServiceCollection services)
{
var provider = services.BuildServiceProvider();

services.AddHttpClient("GamificationApi", client =>
{
client.BaseAddress = new Uri(provider.GetRequiredService<IOptions<GamificationApiSettings>>().Value.BaseUrl);
client.DefaultRequestHeaders.Add("x-gamapi-auth", "true");
});

return services;
}
}
}
5 changes: 2 additions & 3 deletions backend/Infrastructure/Configurations/JwtConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ namespace TaskSync.Infrastructure.Configurations
{
public static class JwtConfiguration
{
public static IServiceCollection ConfigureJwt(this IServiceCollection services, IConfiguration configuration)
public static IServiceCollection ConfigureJwt(this IServiceCollection services)
{
var provider = services.BuildServiceProvider();
var jwtSettings = provider.GetRequiredService<IOptions<JwtSettings>>().Value;
var jwtSettings = services.BuildServiceProvider().GetRequiredService<IOptions<JwtSettings>>().Value;

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.Extensions.Options;

using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;

using TaskSync.Infrastructure.Settings;

namespace TaskSync.Infrastructure.Configurations
{
public static class OpenTelemetryConfiguration
{
public static void ConfigureTelemetry(this WebApplicationBuilder builder)
{
var provider = builder.Services.BuildServiceProvider();
var appInfo = provider.GetRequiredService<IOptions<AppInfo>>().Value;
var otelSettings = provider.GetRequiredService<IOptions<OtelCollectorSettings>>().Value;

// Shared resource attributes (customize for app)
var otelResource = ResourceBuilder.CreateDefault().AddService(appInfo.AppName, serviceVersion: "1.0.0");

// Add OpenTelemetry for Tracing and Metrics
builder.Services.AddOpenTelemetry().ConfigureResource(rb => rb.AddService(appInfo.AppName))
.WithTracing(tracing =>
{
tracing
.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSqlClientInstrumentation()
.AddOtlpExporter(opt =>
{
opt.Endpoint = new Uri(otelSettings.BaseUrl); // http://otel-collector:4317 (grpc protocol)
opt.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
});
})
.WithMetrics(metrics =>
{
metrics
.AddAspNetCoreInstrumentation() // automatic telemetry for incoming HTTP requests e.g. "/api/projects/1/tasks"
.AddHttpClientInstrumentation() // automatic telemetry for outgoing HTTP requests e.g. HttpClient usage
.AddRuntimeInstrumentation() // Garbage collection counts, Thread pool usage, Memory pressure
.AddProcessInstrumentation() // CPU usage, Memory, Thread count
.AddOtlpExporter(opt =>
{
opt.Endpoint = new Uri(otelSettings.BaseUrl);
opt.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.Grpc;
});
});

// Add OpenTelemetry Logging
builder.Logging.ClearProviders();
builder.Logging.AddOpenTelemetry(loggerOptions =>
{
loggerOptions.IncludeScopes = true;
loggerOptions.IncludeFormattedMessage = true;
loggerOptions.ParseStateValues = true;

loggerOptions.SetResourceBuilder(otelResource);

loggerOptions.AddOtlpExporter(otlpOptions =>
{
otlpOptions.Endpoint = new Uri(otelSettings.BaseUrl);
});
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ public static IServiceCollection ConfigureAppSettings(this IServiceCollection se
{
services.Configure<AppInfo>(configuration.GetSection("AppInfo"));
services.Configure<JwtSettings>(configuration.GetSection("JwtSettings"));
services.Configure<FrontendSettings>(configuration.GetSection("Frontend"));
services.Configure<PostgreSqlSettings>(configuration.GetSection("PostgreSql"));
services.Configure<OtelCollectorSettings>(configuration.GetSection("OtelCollector"));
services.Configure<MiddlewareSettings>(configuration.GetSection("MiddlewareSettings"));
services.Configure<GamificationApiSettings>(configuration.GetSection("GamificationApi"));

Expand Down
7 changes: 7 additions & 0 deletions backend/Infrastructure/Settings/FrontendSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TaskSync.Infrastructure.Settings
{
public class FrontendSettings
{
public required string[] Urls { get; set; }
}
}
7 changes: 7 additions & 0 deletions backend/Infrastructure/Settings/OtelCollectorSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TaskSync.Infrastructure.Settings
{
public class OtelCollectorSettings
{
public required string BaseUrl { get; set; }
}
}
7 changes: 7 additions & 0 deletions backend/Infrastructure/Settings/PostgreSqlSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace TaskSync.Infrastructure.Settings
{
public class PostgreSqlSettings
{
public required string DefaultConnection { get; set; }
}
}
11 changes: 8 additions & 3 deletions backend/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
builder.Services.AddRateLimiter();
builder.Services.AddMemoryCache(options => options.SizeLimit = 100);
builder.Services.ConfigureAppSettings(builder.Configuration);
builder.Services.ConfigureApiVersion();
builder.Services.ConfigureDependencyInjection();
builder.Services.ConfigureResponseCompression();
builder.Services.ConfigureHttpClient();
builder.Services.ConfigureApiVersion();
builder.Services.ConfigureCors();
builder.Services.ConfigureJwt(builder.Configuration);
builder.Services.ConfigureDependencyInjection(builder.Configuration);
builder.Services.ConfigureJwt();
if (builder.Environment.IsDevelopment())
{
builder.ConfigureTelemetry();
}
// -------------------------------------------------------------------------------

// -------------- Configure/Order the request pipeline (Middleware) --------------
Expand Down
2 changes: 1 addition & 1 deletion backend/Repositories/ProjectRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ProjectRepository : IProjectRepository

public async Task<ProjectEntity?> GetByIdAsync(int projectId)
{
return await _dbContext.Projects.FirstOrDefaultAsync(x => x.Id == projectId);
return await _dbContext.Projects.AsNoTracking().FirstOrDefaultAsync(x => x.Id == projectId);
}
}
}
2 changes: 1 addition & 1 deletion backend/Repositories/TaskRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task<IDbContextTransaction> BeginTransactionAsync()

public async Task<IList<TaskEntity>?> GetAsync(int projectId)
{
return await _dbContext.Tasks.Where(x => x.ProjectId == projectId).AsNoTracking().ToListAsync();
return await _dbContext.Tasks.AsNoTracking().Where(x => x.ProjectId == projectId).ToListAsync();
}

public async Task<TaskEntity> AddAsync(string title, int? assigneeId, int projectId, int? creatorId)
Expand Down
6 changes: 3 additions & 3 deletions backend/Repositories/UserRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,17 @@ public class UserRepository : IRepository<UserEntity>

public async Task<UserEntity?> GetAsync()
{
return await _dbContext.Users.SingleOrDefaultAsync(x => x.Id == 1);
return await _dbContext.Users.AsNoTracking().SingleOrDefaultAsync(x => x.Id == 1);
}

public async Task<UserEntity?> GetAsync(string email)
{
return await _dbContext.Users.SingleOrDefaultAsync(x => x.Email == email);
return await _dbContext.Users.AsNoTracking().SingleOrDefaultAsync(x => x.Email == email);
}

public Task<UserEntity?> GetAsync(int param1)
{
throw new NotImplementedException();
}
}
}
}
9 changes: 8 additions & 1 deletion backend/TaskSync.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Process" Version="1.12.0-beta.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.SqlClient" Version="1.12.0-beta.2" />
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
Expand Down
5 changes: 3 additions & 2 deletions backend/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
{
"AppInfo": {
"AppName": "TaskSync Core API",
"AppName": "Core.API",
"Environment": "Development"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning"
}
}
}
Loading