FEAT: DB basics

This commit is contained in:
2026-04-16 14:21:58 +02:00
parent 5794c3fbb5
commit dc6043ed13
16 changed files with 286 additions and 42 deletions

4
.gitignore vendored
View File

@@ -22,4 +22,6 @@ Thumbs.db
# Environment / secrets
.env
appsettings.Development.json
appsettings.json
appsettings.Development.json
.fake

View File

@@ -0,0 +1,49 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using TicketAppIncrArchi.Infrastructure.Persistence;
#nullable disable
namespace TicketAppIncrArchi.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260416121807_Init")]
partial class Init
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("TicketAppIncrArchi.Domain.Entities.Ticket", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Tickets");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace TicketAppIncrArchi.Migrations
{
/// <inheritdoc />
public partial class Init : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Tickets",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
Title = table.Column<string>(type: "text", nullable: false),
Description = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Tickets", x => x.Id);
});
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Tickets");
}
}
}

View File

@@ -0,0 +1,46 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using TicketAppIncrArchi.Infrastructure.Persistence;
#nullable disable
namespace TicketAppIncrArchi.Migrations
{
[DbContext(typeof(AppDbContext))]
partial class AppDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.5")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("TicketAppIncrArchi.Domain.Entities.Ticket", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<string>("Description")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.ToTable("Tickets");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,7 +1,8 @@
using TicketAppIncrArchi.API.Controllers;
using TicketAppIncrArchi.Application.Interfaces;
using TicketAppIncrArchi.Application.Services;
using TicketAppIncrArchi.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
@@ -13,7 +14,10 @@ builder.Services.AddOpenApi();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddScoped<ITicketRepository, TicketRepository>();
builder.Services.AddScoped<ITicketService, TicketService>();
var app = builder.Build();

View File

@@ -17,36 +17,29 @@ public class TicketsController : ControllerBase
{
_service = service;
}
//-----------------------------------------------------------
[HttpGet]
public IEnumerable<TicketResponse> Get()
{
return _service.GetAll();
return _service.GetAll().Result;
}
[HttpGet("{id}")]
public ActionResult<TicketResponse> Get(Guid id)
{
var result = _service.GetById(id);
var result = _service.GetById(id).Result;
if (result is FailureResult<TicketResponse> fail)
return NotFound(fail.Error);
var success = (SuccessResult<TicketResponse>) result;
return Ok(success.Value);
/*
if (ticket == null) return NotFound();
return Ok(ticket);
*/
}
[HttpPost]
public IActionResult Create(CreateTicketRequest request)
{
var result = _service.Create(request);
var result = _service.Create(request).Result;
if (result is FailureResult<CreateTicketResponse> fail)
return BadRequest(fail.Error);

View File

@@ -0,0 +1,11 @@
using TicketAppIncrArchi.Domain.Entities;
public interface ITicketRepository
{
Task<Ticket?> GetByIdAsync(Guid id);
Task<List<Ticket>> GetAllAsync();
Task<Ticket> AddAsync(Ticket ticket);
Task DeleteAsync(Guid id);
}

View File

@@ -1,14 +1,13 @@
using System.Data.Entity.Infrastructure;
using TicketAppIncrArchi.Application.DTO;
using TicketAppIncrArchi.Domain.Entities;
namespace TicketAppIncrArchi.Application.Interfaces;
public interface ITicketService
{
{
//TODO: implement Repository
IEnumerable<TicketResponse> GetAll();
Result<TicketResponse> GetById(Guid id);
Result<CreateTicketResponse> Create(CreateTicketRequest request);
Task<IEnumerable<TicketResponse>> GetAll();
Task<Result<TicketResponse>> GetById(Guid id);
Task<Result<CreateTicketResponse>> Create(CreateTicketRequest request);
}

View File

@@ -11,10 +11,19 @@ namespace TicketAppIncrArchi.Application.Services;
public class TicketService : ITicketService
{
private readonly List<Ticket> _tickets = new();
public IEnumerable<TicketResponse> GetAll()
private readonly ITicketRepository _repo;
//private readonly List<Ticket> _tickets = new();
public TicketService(ITicketRepository repo)
{
var result =_tickets.Select(t => new TicketResponse
_repo = repo;
}
public async Task<IEnumerable<TicketResponse>> GetAll()
{
var tickets = await _repo.GetAllAsync();
var result =tickets.Select(t => new TicketResponse
{
Id = t.Id,
Title = t.Title,
@@ -23,29 +32,27 @@ public class TicketService : ITicketService
return result;
}
public Result<TicketResponse> GetById(Guid id)
public async Task<Result<TicketResponse>> GetById(Guid id)
{
//TODO: should use result
var found = _tickets.FirstOrDefault(ticket => ticket.Id == id);
if (found == null)
var ticket = await _repo.GetByIdAsync(id);
if (ticket == null)
{
return Result<TicketResponse>.Fail("No Ticket Found");
}
var ticketResponse = new TicketResponse
{
Id = found.Id,
Title = found.Title,
Description = found.Description,
Id = ticket.Id,
Title = ticket.Title,
Description = ticket.Description,
};
return Result<TicketResponse>.Ok(found);
return Result<TicketResponse>.Ok(ticketResponse);
}
public Result<CreateTicketResponse> Create(CreateTicketRequest request)
public async Task<Result<CreateTicketResponse>> Create(CreateTicketRequest request)
{
if (string.IsNullOrWhiteSpace(request.Title))
@@ -53,25 +60,21 @@ public class TicketService : ITicketService
var ticket = new Ticket
{
Id = Guid.NewGuid(),
Title = request.Title,
Description = request.Description
};
//send creation to repo
_tickets.Add(ticket);
Ticket resultTicket = await _repo.AddAsync(ticket);
var ticketResponse = new CreateTicketResponse
{
Id = ticket.Id,
Title = ticket.Title,
Description = ticket.Description
Id = resultTicket.Id,
Title = resultTicket.Title,
Description = resultTicket.Description
};
return Result<CreateTicketResponse>.Ok(ticketResponse);
return Result<CreateTicketResponse>.Ok(ticketResponse);
}
}

View File

@@ -1,6 +1,9 @@
using System.ComponentModel.DataAnnotations;
namespace TicketAppIncrArchi.Domain.Entities;
public class Ticket
{
[Key]
public Guid Id {get;set;}
public string Title {get;set;} = "";
public string Description {get;set;} = "";

View File

@@ -0,0 +1,18 @@
using Microsoft.EntityFrameworkCore;
namespace TicketAppIncrArchi.Infrastructure.Persistence;
using TicketAppIncrArchi.Domain.Entities;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
public DbSet <Ticket> Tickets {get;set;}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore;
using TicketAppIncrArchi.Domain.Entities;
using TicketAppIncrArchi.Application.Interfaces;
using TicketAppIncrArchi.Infrastructure.Persistence;
public class TicketRepository : ITicketRepository
{
private readonly AppDbContext _context;
public TicketRepository(AppDbContext context)
{
_context = context;
}
public async Task<Ticket?> GetByIdAsync(Guid id)
=> await _context.Tickets.FindAsync(id);
public async Task<List<Ticket>> GetAllAsync()
=> await _context.Tickets.ToListAsync();
public async Task<Ticket> AddAsync(Ticket ticket)
{
_context.Tickets.Add(ticket);
await _context.SaveChangesAsync();
return(ticket);
}
public async Task DeleteAsync(Guid id)
{
var ticket = await _context.Tickets.FindAsync(id);
if (ticket is null) return;
_context.Tickets.Remove(ticket);
await _context.SaveChangesAsync();
}
}

View File

@@ -7,7 +7,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EntityFramework" Version="6.5.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.5" />
</ItemGroup>

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.5" />
</ItemGroup>
</Project>

View File

@@ -5,5 +5,8 @@
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
"AllowedHosts": "*",
"ConnectionStrings":{
"DefaultConnection": "Host=localhost;Port=45738;Database=mydb;Username=postgres;Password=password"
}
}

20
docker-compose.yml Normal file
View File

@@ -0,0 +1,20 @@
services:
db:
image: postgres:14.5
restart: always
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
ports:
- "45738:5432"
volumes:
- pgdata:/var/lib/postgresql/data
adminer:
image: adminer
restart: always
ports:
- 8080:8080
volumes:
pgdata: