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
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ docker-compose --env-file .env.docker up --build -d
## Monitoring Tools
Enable real‑time metrics, logs, and health checks for the application by following the step‑by‑step setup guide. [Click for Guide](dockerfiles/monitoring/GUIDE.md)

## Migration

1. **Install the EF Core CLI tool (dotnet-ef)**
```bash
dotnet tool install --global dotnet-ef

This command adds the EF Core command-line tools to your system globally.

2. **Verify the installation**
```bash
dotnet ef --version
If you see a version number, the tool has been installed successfully.

3. **Create a new migration**
```bash
dotnet ef migrations add InitialMigration --context DataContext --project src/Infra
- --context DataContext: Specifies which DbContext to use when generating the migration.
- --project src/Infra: Specifies the project folder where the migration files will be created.

4. **Apply the migration to the database**
```bash
dotnet ef database update --context DataContext --project src/Infra
This command applies the latest migration to the database.

## Deployment
Service may be in sleep mode on the first request. You may need to wait for a few seconds. [Live Demo Url](https://api-0oqs.onrender.com/scalar)

Expand Down
4 changes: 2 additions & 2 deletions dockerfiles/postgres-cluster/pgpool/pgpool.conf
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ sr_check_user = 'admin'
# Streaming replication check user
# This is necessary even if you disable streaming
# replication delay check by sr_check_period = 0
sr_check_password = 'AESIdSY+WwYAncDcAca4aPcdg=='
sr_check_password = 'AESQwC+qXIlBxY3DhcDQ5HExQ=='
# Password for streaming replication check user
# Leaving it empty will make Pgpool-II to first look for the
# Password in pool_passwd file before using the empty password
Expand Down Expand Up @@ -564,7 +564,7 @@ health_check_timeout = '10'
# 0 means no timeout
health_check_user = 'admin'
# Health check user
health_check_password = 'AESIdSY+WwYAncDcAca4aPcdg=='
health_check_password = 'AESQwC+qXIlBxY3DhcDQ5HExQ=='
# Password for health check user
# Leaving it empty will make Pgpool-II to first look for the
# Password in pool_passwd file before using the empty password
Expand Down
117 changes: 117 additions & 0 deletions src/Api/Controllers/SampleController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
namespace Api.Controllers
{
using Asp.Versioning;
using Domain.Interfaces;
using Domain.Models.Request.Sample;
using Microsoft.AspNetCore.Mvc;

/// <summary>
/// Represents the sample controller.
/// </summary>
[ApiController]
[Route("sample")]
[Tags("sample")]
[ApiVersion("1.0")]
public class SampleController : ControllerBase
{
private readonly ISampleService sampleService;

/// <summary>
/// Initializes a new instance of the <see cref="SampleController"/> class.
/// </summary>
/// <param name="sampleService">The sample service.</param>
public SampleController(ISampleService sampleService)
{
this.sampleService = sampleService ?? throw new ArgumentNullException(nameof(sampleService));
}

/// <summary>
/// Gets a samples.
/// </summary>
/// <returns>The sample response model.</returns>
[HttpGet]
public async Task<IActionResult> Get()
{
var result = await this.sampleService.GetSamplesAsync(new GetSamplesRequestModel(), CancellationToken.None);
return this.Ok(result);
}

/// <summary>
/// Gets a sample by ID.
/// </summary>
/// <param name="id">The sample ID.</param>
/// <returns>The sample response model.</returns>
[HttpGet("{id:guid}")]
public async Task<IActionResult> Get(Guid id)
{
var result = await this.sampleService.GetSampleAsync(id, CancellationToken.None);
if (result == null)
{
return this.NotFound();
}

return this.Ok(result);
}

/// <summary>
/// Creates a new sample.
/// </summary>
/// <param name="request">The create sample request model.</param>
/// <returns>The created sample response model.</returns>
[HttpPost]
public async Task<IActionResult> Create([FromBody] CreateSampleRequestModel request)
{
if (request == null)
{
return this.BadRequest("Request cannot be null.");
}

var result = await this.sampleService.CreateSampleAsync(request, CancellationToken.None);
return this.CreatedAtAction(nameof(this.Get), new { id = result.Id }, result);
}

/// <summary>
/// Updates an existing sample.
/// </summary>
/// <param name="request">The update sample request model.</param>
/// <returns>The updated sample response model.</returns>
[HttpPut]
public async Task<IActionResult> Update([FromBody] UpdateSampleRequestModel request)
{
if (request == null || request.Id == Guid.Empty)
{
return this.BadRequest("Request cannot be null and ID must be provided.");
}

var result = await this.sampleService.UpdateSampleAsync(request, CancellationToken.None);
if (result == null)
{
return this.NotFound();
}

return this.Ok(result);
}

/// <summary>
/// Deletes a sample by ID.
/// </summary>
/// <param name="id">The sample ID.</param>
/// <returns>A boolean indicating success or failure.</returns>
[HttpDelete("{id:guid}")]
public async Task<IActionResult> Delete(Guid id)
{
if (id == Guid.Empty)
{
return this.BadRequest("ID must be provided.");
}

var result = await this.sampleService.DeleteSampleAsync(id, CancellationToken.None);
if (!result)
{
return this.NotFound();
}

return this.NoContent();
}
}
}
51 changes: 0 additions & 51 deletions src/Api/Controllers/V1/SampleController.cs

This file was deleted.

36 changes: 0 additions & 36 deletions src/Api/Controllers/V2/SampleController.cs

This file was deleted.

File renamed without changes.
25 changes: 25 additions & 0 deletions src/Api/MappingProfiles/SampleMappingProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
namespace Api.MappingProfiles
{
using AutoMapper;
using Domain.Entities;
using Domain.Models.Request.Sample;
using Domain.Models.Response.Sample;

/// <summary>
/// AutoMapper profile for mapping between domain entities and response models.
/// </summary>
public class SampleProfile : Profile
{
/// <summary>
/// Initializes a new instance of the <see cref="SampleProfile"/> class.
/// </summary>
public SampleProfile()
{
this.CreateMap<SampleEntity, GetSampleResponseModel>();
this.CreateMap<CreateSampleRequestModel, SampleEntity>();
this.CreateMap<SampleEntity, CreateSampleResponseModel>();
this.CreateMap<UpdateSampleRequestModel, SampleEntity>();
this.CreateMap<SampleEntity, UpdateSampleResponseModel>();
}
}
}
2 changes: 2 additions & 0 deletions src/Api/Microservice.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

<NoWarn>$(NoWarn);SA1516</NoWarn>

<UserSecretsId>App-Development-Secrets</UserSecretsId>

</PropertyGroup>

<ItemGroup>
Expand Down
11 changes: 8 additions & 3 deletions src/Api/Program.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma warning disable SA1200 // Using directive should appear within a namespace declaration
using Api.Configurations;
using Api.MappingProfiles;
using Api.Middlewares;
using Asp.Versioning;
using Asp.Versioning.Builder;
using Domain.Interfaces;
using Domain.Services;
using Infra.Data;
using Infra.Repositories;
using Microsoft.EntityFrameworkCore;
Expand All @@ -18,8 +20,11 @@
builder.Services.AddControllers();

builder.Services
.AddDbContext<DataContext>(opts => opts.UseInMemoryDatabase("DemoDb"))
.AddScoped(typeof(IRepository<>), typeof(Repository<>));
.AddDbContext<DataContext>(opts => opts.UseNpgsql(builder.Configuration.GetConnectionString("PgpoolDb")))
.AddScoped(typeof(IRepository<>), typeof(Repository<>))
.AddScoped(typeof(ISampleService), typeof(SampleService));

builder.Services.AddAutoMapper(typeof(SampleProfile).Assembly);

builder.Services.AddApiVersioning(options =>
{
Expand Down Expand Up @@ -82,7 +87,7 @@
.WithName("GetWeatherForecast")
.WithOpenApi()
.WithApiVersionSet(new ApiVersionSet(new ApiVersionSetBuilder(string.Empty), "weatherForecast"))
.HasApiVersion(new ApiVersion(1, 0));
.HasApiVersion(new ApiVersion(2, 0));

app.MapControllers();
app.MapMetrics();
Expand Down
5 changes: 4 additions & 1 deletion src/Api/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"Microsoft.AspNetCore": "Warning"
}
},
"UseJsonFormat": false
"UseJsonFormat": false,
"ConnectionStrings": {
"PgpoolDb": ""
}
}
3 changes: 3 additions & 0 deletions src/Domain/Domain.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="14.0.0" />
</ItemGroup>
</Project>
13 changes: 13 additions & 0 deletions src/Domain/Interfaces/ISampleService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Domain.Models.Request.Sample;
using Domain.Models.Response.Sample;

namespace Domain.Interfaces;

public interface ISampleService
{
Task<CreateSampleResponseModel> CreateSampleAsync(CreateSampleRequestModel request, CancellationToken cancellationToken = default);
Task<GetSamplesResponseModel> GetSamplesAsync(GetSamplesRequestModel request, CancellationToken cancellationToken = default);
Task<UpdateSampleResponseModel> UpdateSampleAsync(UpdateSampleRequestModel request, CancellationToken cancellationToken = default);
Task<GetSampleResponseModel> GetSampleAsync(Guid id, CancellationToken cancellationToken = default);
Task<bool> DeleteSampleAsync(Guid id, CancellationToken cancellationToken = default);
}
6 changes: 6 additions & 0 deletions src/Domain/Models/Request/Sample/CreateSampleRequestModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Domain.Models.Request.Sample;

public class CreateSampleRequestModel
{
public string? Name { get; set; }
}
6 changes: 6 additions & 0 deletions src/Domain/Models/Request/Sample/GetSamplesRequestModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Domain.Models.Request.Sample;

public class GetSamplesRequestModel
{
public string? Name { get; set; }
}
8 changes: 8 additions & 0 deletions src/Domain/Models/Request/Sample/UpdateSampleRequestModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Domain.Models.Request.Sample;

public class UpdateSampleRequestModel
{
public Guid Id { get; set; }

public string? Name { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Domain.Models.Response.Sample;

public class CreateSampleResponseModel
{
public Guid Id { get; set; }
public string? Name { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
Loading