Phase 2: Backend API Implementation - DTOs, Services, and Controllers

This commit is contained in:
steinhelge
2025-11-23 15:55:03 +01:00
parent 0661bebd87
commit 9ed5adbfb5
22 changed files with 1066 additions and 33 deletions
@@ -0,0 +1,110 @@
using Hospitality.Backend.DTOs;
using Hospitality.Domain.Entities;
using Hospitality.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Hospitality.Backend.Services;
public class EventService : IEventService
{
private readonly HospitalityDbContext _context;
public EventService(HospitalityDbContext context)
{
_context = context;
}
public async Task<IEnumerable<EventListResponse>> GetAllEventsAsync()
{
return await _context.Events
.Select(e => new EventListResponse(
e.Id,
e.Name,
e.StartDate,
e.EndDate,
e.Location
))
.ToListAsync();
}
public async Task<EventResponse?> GetEventByIdAsync(Guid id)
{
var evt = await _context.Events
.Include(e => e.Groups)
.Include(e => e.Products)
.FirstOrDefaultAsync(e => e.Id == id);
if (evt == null) return null;
return new EventResponse(
evt.Id,
evt.Name,
evt.StartDate,
evt.EndDate,
evt.Location,
evt.Groups.Count,
evt.Products.Count
);
}
public async Task<EventResponse> CreateEventAsync(CreateEventRequest request)
{
var evt = new Event
{
Id = Guid.NewGuid(),
Name = request.Name,
StartDate = request.StartDate,
EndDate = request.EndDate,
Location = request.Location
};
_context.Events.Add(evt);
await _context.SaveChangesAsync();
return new EventResponse(
evt.Id,
evt.Name,
evt.StartDate,
evt.EndDate,
evt.Location,
0,
0
);
}
public async Task<EventResponse?> UpdateEventAsync(Guid id, UpdateEventRequest request)
{
var evt = await _context.Events.FindAsync(id);
if (evt == null) return null;
evt.Name = request.Name;
evt.StartDate = request.StartDate;
evt.EndDate = request.EndDate;
evt.Location = request.Location;
await _context.SaveChangesAsync();
var groupCount = await _context.Groups.CountAsync(g => g.EventId == id);
var productCount = await _context.Products.CountAsync(p => p.EventId == id);
return new EventResponse(
evt.Id,
evt.Name,
evt.StartDate,
evt.EndDate,
evt.Location,
groupCount,
productCount
);
}
public async Task<bool> DeleteEventAsync(Guid id)
{
var evt = await _context.Events.FindAsync(id);
if (evt == null) return false;
_context.Events.Remove(evt);
await _context.SaveChangesAsync();
return true;
}
}
@@ -0,0 +1,227 @@
using Hospitality.Backend.DTOs;
using Hospitality.Domain.Entities;
using Hospitality.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Hospitality.Backend.Services;
public class GroupService : IGroupService
{
private readonly HospitalityDbContext _context;
public GroupService(HospitalityDbContext context)
{
_context = context;
}
public async Task<IEnumerable<GroupResponse>> GetGroupsByEventIdAsync(Guid eventId)
{
return await _context.Groups
.Where(g => g.EventId == eventId)
.Select(g => new GroupResponse(
g.Id,
g.EventId,
g.Name,
g.ContactPersonName,
g.ContactEmail,
g.People.Count
))
.ToListAsync();
}
public async Task<GroupDetailResponse?> GetGroupByIdAsync(Guid id)
{
var group = await _context.Groups
.Include(g => g.People)
.FirstOrDefaultAsync(g => g.Id == id);
if (group == null) return null;
return new GroupDetailResponse(
group.Id,
group.EventId,
group.Name,
group.ContactPersonName,
group.ContactEmail,
group.People.Select(p => new PersonResponse(
p.Id,
p.GroupId,
p.QrCode,
p.Name,
p.Email,
p.PhoneNumber
)).ToList()
);
}
public async Task<GroupResponse> CreateGroupAsync(Guid eventId, CreateGroupRequest request)
{
var group = new Group
{
Id = Guid.NewGuid(),
EventId = eventId,
Name = request.Name,
ContactPersonName = request.ContactPersonName,
ContactEmail = request.ContactEmail
};
_context.Groups.Add(group);
await _context.SaveChangesAsync();
return new GroupResponse(
group.Id,
group.EventId,
group.Name,
group.ContactPersonName,
group.ContactEmail,
0
);
}
public async Task<GroupResponse?> UpdateGroupAsync(Guid id, UpdateGroupRequest request)
{
var group = await _context.Groups.FindAsync(id);
if (group == null) return null;
group.Name = request.Name;
group.ContactPersonName = request.ContactPersonName;
group.ContactEmail = request.ContactEmail;
await _context.SaveChangesAsync();
var peopleCount = await _context.People.CountAsync(p => p.GroupId == id);
return new GroupResponse(
group.Id,
group.EventId,
group.Name,
group.ContactPersonName,
group.ContactEmail,
peopleCount
);
}
public async Task<bool> DeleteGroupAsync(Guid id)
{
var group = await _context.Groups.FindAsync(id);
if (group == null) return false;
_context.Groups.Remove(group);
await _context.SaveChangesAsync();
return true;
}
public async Task<PersonResponse> AddPersonToGroupAsync(Guid groupId, CreatePersonRequest request)
{
var person = new Person
{
Id = Guid.NewGuid(),
GroupId = groupId,
QrCode = Guid.NewGuid(),
Name = request.Name,
Email = request.Email,
PhoneNumber = request.PhoneNumber
};
_context.People.Add(person);
await _context.SaveChangesAsync();
return new PersonResponse(
person.Id,
person.GroupId,
person.QrCode,
person.Name,
person.Email,
person.PhoneNumber
);
}
public async Task<PersonDetailResponse?> GetPersonByIdAsync(Guid id)
{
var person = await _context.People
.Include(p => p.Quotas)
.ThenInclude(q => q.Product)
.FirstOrDefaultAsync(p => p.Id == id);
if (person == null) return null;
return new PersonDetailResponse(
person.Id,
person.GroupId,
person.QrCode,
person.Name,
person.Email,
person.PhoneNumber,
person.Quotas.Select(q => new QuotaResponse(
q.ProductId,
q.Product.Name,
q.Product.Type.ToString(),
q.InitialAmount,
q.UsedAmount,
q.RemainingAmount
)).ToList()
);
}
public async Task<PersonResponse?> UpdatePersonAsync(Guid id, UpdatePersonRequest request)
{
var person = await _context.People.FindAsync(id);
if (person == null) return null;
person.Name = request.Name;
person.Email = request.Email;
person.PhoneNumber = request.PhoneNumber;
await _context.SaveChangesAsync();
return new PersonResponse(
person.Id,
person.GroupId,
person.QrCode,
person.Name,
person.Email,
person.PhoneNumber
);
}
public async Task<bool> DeletePersonAsync(Guid id)
{
var person = await _context.People.FindAsync(id);
if (person == null) return false;
_context.People.Remove(person);
await _context.SaveChangesAsync();
return true;
}
public async Task<bool> AssignQuotaAsync(Guid personId, Guid productId, int initialAmount)
{
var person = await _context.People.FindAsync(personId);
var product = await _context.Products.FindAsync(productId);
if (person == null || product == null) return false;
var existingQuota = await _context.PersonQuotas
.FirstOrDefaultAsync(q => q.PersonId == personId && q.ProductId == productId);
if (existingQuota != null)
{
existingQuota.InitialAmount = initialAmount;
}
else
{
var quota = new PersonQuota
{
Id = Guid.NewGuid(),
PersonId = personId,
ProductId = productId,
InitialAmount = initialAmount,
UsedAmount = 0
};
_context.PersonQuotas.Add(quota);
}
await _context.SaveChangesAsync();
return true;
}
}
@@ -0,0 +1,12 @@
using Hospitality.Backend.DTOs;
namespace Hospitality.Backend.Services;
public interface IEventService
{
Task<IEnumerable<EventListResponse>> GetAllEventsAsync();
Task<EventResponse?> GetEventByIdAsync(Guid id);
Task<EventResponse> CreateEventAsync(CreateEventRequest request);
Task<EventResponse?> UpdateEventAsync(Guid id, UpdateEventRequest request);
Task<bool> DeleteEventAsync(Guid id);
}
@@ -0,0 +1,18 @@
using Hospitality.Backend.DTOs;
namespace Hospitality.Backend.Services;
public interface IGroupService
{
Task<IEnumerable<GroupResponse>> GetGroupsByEventIdAsync(Guid eventId);
Task<GroupDetailResponse?> GetGroupByIdAsync(Guid id);
Task<GroupResponse> CreateGroupAsync(Guid eventId, CreateGroupRequest request);
Task<GroupResponse?> UpdateGroupAsync(Guid id, UpdateGroupRequest request);
Task<bool> DeleteGroupAsync(Guid id);
Task<PersonResponse> AddPersonToGroupAsync(Guid groupId, CreatePersonRequest request);
Task<PersonDetailResponse?> GetPersonByIdAsync(Guid id);
Task<PersonResponse?> UpdatePersonAsync(Guid id, UpdatePersonRequest request);
Task<bool> DeletePersonAsync(Guid id);
Task<bool> AssignQuotaAsync(Guid personId, Guid productId, int initialAmount);
}
@@ -0,0 +1,11 @@
using Hospitality.Backend.DTOs;
namespace Hospitality.Backend.Services;
public interface IProductService
{
Task<IEnumerable<ProductResponse>> GetProductsByEventIdAsync(Guid eventId);
Task<ProductResponse> CreateProductAsync(Guid eventId, CreateProductRequest request);
Task<ProductResponse?> UpdateProductAsync(Guid id, UpdateProductRequest request);
Task<bool> DeleteProductAsync(Guid id);
}
@@ -0,0 +1,9 @@
using Hospitality.Backend.DTOs;
namespace Hospitality.Backend.Services;
public interface IQrCodeService
{
Task<PersonDetailResponse?> GetPersonByQrCodeAsync(Guid qrCode);
Task<IEnumerable<QuotaResponse>> GetQuotasByQrCodeAsync(Guid qrCode);
}
@@ -0,0 +1,10 @@
using Hospitality.Backend.DTOs;
namespace Hospitality.Backend.Services;
public interface ITransactionService
{
Task<TransactionResponse?> RecordTransactionAsync(CreateTransactionRequest request);
Task<IEnumerable<TransactionResponse>> GetTransactionsByPersonIdAsync(Guid personId);
Task<IEnumerable<TransactionResponse>> GetTransactionsByEventIdAsync(Guid eventId);
}
@@ -0,0 +1,78 @@
using Hospitality.Backend.DTOs;
using Hospitality.Domain.Entities;
using Hospitality.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Hospitality.Backend.Services;
public class ProductService : IProductService
{
private readonly HospitalityDbContext _context;
public ProductService(HospitalityDbContext context)
{
_context = context;
}
public async Task<IEnumerable<ProductResponse>> GetProductsByEventIdAsync(Guid eventId)
{
return await _context.Products
.Where(p => p.EventId == eventId)
.Select(p => new ProductResponse(
p.Id,
p.EventId,
p.Name,
p.Type
))
.ToListAsync();
}
public async Task<ProductResponse> CreateProductAsync(Guid eventId, CreateProductRequest request)
{
var product = new Product
{
Id = Guid.NewGuid(),
EventId = eventId,
Name = request.Name,
Type = request.Type
};
_context.Products.Add(product);
await _context.SaveChangesAsync();
return new ProductResponse(
product.Id,
product.EventId,
product.Name,
product.Type
);
}
public async Task<ProductResponse?> UpdateProductAsync(Guid id, UpdateProductRequest request)
{
var product = await _context.Products.FindAsync(id);
if (product == null) return null;
product.Name = request.Name;
product.Type = request.Type;
await _context.SaveChangesAsync();
return new ProductResponse(
product.Id,
product.EventId,
product.Name,
product.Type
);
}
public async Task<bool> DeleteProductAsync(Guid id)
{
var product = await _context.Products.FindAsync(id);
if (product == null) return false;
_context.Products.Remove(product);
await _context.SaveChangesAsync();
return true;
}
}
@@ -0,0 +1,61 @@
using Hospitality.Backend.DTOs;
using Hospitality.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Hospitality.Backend.Services;
public class QrCodeService : IQrCodeService
{
private readonly HospitalityDbContext _context;
public QrCodeService(HospitalityDbContext context)
{
_context = context;
}
public async Task<PersonDetailResponse?> GetPersonByQrCodeAsync(Guid qrCode)
{
var person = await _context.People
.Include(p => p.Quotas)
.ThenInclude(q => q.Product)
.FirstOrDefaultAsync(p => p.QrCode == qrCode);
if (person == null) return null;
return new PersonDetailResponse(
person.Id,
person.GroupId,
person.QrCode,
person.Name,
person.Email,
person.PhoneNumber,
person.Quotas.Select(q => new QuotaResponse(
q.ProductId,
q.Product.Name,
q.Product.Type.ToString(),
q.InitialAmount,
q.UsedAmount,
q.RemainingAmount
)).ToList()
);
}
public async Task<IEnumerable<QuotaResponse>> GetQuotasByQrCodeAsync(Guid qrCode)
{
var quotas = await _context.PersonQuotas
.Include(q => q.Product)
.Include(q => q.Person)
.Where(q => q.Person.QrCode == qrCode)
.Select(q => new QuotaResponse(
q.ProductId,
q.Product.Name,
q.Product.Type.ToString(),
q.InitialAmount,
q.UsedAmount,
q.RemainingAmount
))
.ToListAsync();
return quotas;
}
}
@@ -0,0 +1,102 @@
using Hospitality.Backend.DTOs;
using Hospitality.Domain.Entities;
using Hospitality.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
namespace Hospitality.Backend.Services;
public class TransactionService : ITransactionService
{
private readonly HospitalityDbContext _context;
public TransactionService(HospitalityDbContext context)
{
_context = context;
}
public async Task<TransactionResponse?> RecordTransactionAsync(CreateTransactionRequest request)
{
var person = await _context.People
.FirstOrDefaultAsync(p => p.QrCode == request.QrCode);
if (person == null) return null;
var quota = await _context.PersonQuotas
.Include(q => q.Product)
.FirstOrDefaultAsync(q => q.PersonId == person.Id && q.ProductId == request.ProductId);
if (quota == null) return null;
if (quota.RemainingAmount < request.Amount)
{
throw new InvalidOperationException($"Insufficient quota. Remaining: {quota.RemainingAmount}, Requested: {request.Amount}");
}
quota.UsedAmount += request.Amount;
var transaction = new Transaction
{
Id = Guid.NewGuid(),
PersonId = person.Id,
ProductId = request.ProductId,
Amount = request.Amount,
Timestamp = DateTime.UtcNow,
StaffId = null
};
_context.Transactions.Add(transaction);
await _context.SaveChangesAsync();
return new TransactionResponse(
transaction.Id,
person.Id,
person.Name,
quota.Product.Id,
quota.Product.Name,
transaction.Amount,
transaction.Timestamp,
transaction.StaffId
);
}
public async Task<IEnumerable<TransactionResponse>> GetTransactionsByPersonIdAsync(Guid personId)
{
return await _context.Transactions
.Include(t => t.Person)
.Include(t => t.Product)
.Where(t => t.PersonId == personId)
.OrderByDescending(t => t.Timestamp)
.Select(t => new TransactionResponse(
t.Id,
t.PersonId,
t.Person.Name,
t.ProductId,
t.Product.Name,
t.Amount,
t.Timestamp,
t.StaffId
))
.ToListAsync();
}
public async Task<IEnumerable<TransactionResponse>> GetTransactionsByEventIdAsync(Guid eventId)
{
return await _context.Transactions
.Include(t => t.Person)
.ThenInclude(p => p.Group)
.Include(t => t.Product)
.Where(t => t.Product.EventId == eventId)
.OrderByDescending(t => t.Timestamp)
.Select(t => new TransactionResponse(
t.Id,
t.PersonId,
t.Person.Name,
t.ProductId,
t.Product.Name,
t.Amount,
t.Timestamp,
t.StaffId
))
.ToListAsync();
}
}