refactor(api): enforce service and data-service boundaries

This commit is contained in:
Alexandre Brandizzi 2026-07-24 10:27:35 -03:00
parent 4b4ab04615
commit aa59399b78
38 changed files with 1924 additions and 506 deletions

View file

@ -3,34 +3,16 @@ using Api.SeaHavenIndustries.Controllers;
using Api.SeaHavenIndustries.DTOs; using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries; using Data.SeaHavenIndustries;
using FluentAssertions; using FluentAssertions;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Moq; using Moq;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
using Xunit; using Xunit;
namespace Api.SeaHavenIndustries.Tests; namespace Api.SeaHavenIndustries.Tests;
public class CalendarControllerTests public class CalendarControllerTests
{ {
private static ApplicationDbContext NewContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
private static UserManager<ApplicationUser> MockUserManager()
{
var store = new Mock<IUserStore<ApplicationUser>>();
return new Mock<UserManager<ApplicationUser>>(
store.Object, null!, null!, null!, null!, null!, null!, null!, null!).Object;
}
private static CalendarController NewController(ApplicationDbContext ctx) =>
new(MockUserManager(), ctx);
private static EditEvent_DTO ValidDto(int? id = null, string title = "Standup") => new() private static EditEvent_DTO ValidDto(int? id = null, string title = "Standup") => new()
{ {
Id = id, Id = id,
@ -43,164 +25,166 @@ public class CalendarControllerTests
EndTime = "08:30 AM" EndTime = "08:30 AM"
}; };
private static Events SeedEvent(ApplicationDbContext ctx, string title, DateTime startDate, bool? isDeleted = false) private static CalendarController NewController(Mock<ICalendarService> service) =>
{ new(service.Object);
var ev = new Events
{
Title = title,
EventColor = "primary",
ReminderMinutes = 15,
StartDate = startDate,
EndDate = startDate,
IsDeleted = isDeleted,
CreatedDate = DateTime.Now
};
ctx.Events.Add(ev);
ctx.SaveChanges();
return ev;
}
private static object Prop(object source, string name) => private static object Prop(object source, string name) =>
source.GetType().GetProperty(name)!.GetValue(source)!; source.GetType().GetProperty(name)!.GetValue(source)!;
[Fact] [Fact]
public async Task AddEvent_WhenValid_PersistsEventAndReturnsOk() public async Task AddEvent_WhenServiceSucceeds_ReturnsOkWithCreatedMessage()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var result = await NewController(ctx).AddEvent(ValidDto()); var result = await NewController(service).AddEvent(ValidDto(), CancellationToken.None);
var ok = result.Should().BeOfType<OkObjectResult>().Subject; var ok = result.Should().BeOfType<OkObjectResult>().Subject;
ok.Value.Should().BeOfType<DataResponse>().Which.Status.Should().Be("200"); var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
response.Status.Should().Be("200");
var saved = ctx.Events.Single(); response.Message.Should().Be("Event Created Successfully");
saved.Title.Should().Be("Standup"); service.Verify(s => s.CreateEventAsync(It.IsAny<EditEventInput>(), It.IsAny<CancellationToken>()), Times.Once);
saved.IsDeleted.Should().Be(false);
saved.CreatedDate.Should().NotBeNull();
} }
[Fact] [Fact]
public async Task EditEvent_WhenIdMissing_ReturnsBadRequest() public async Task AddEvent_WhenServiceThrows_ReturnsBadRequestWithMessage()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var result = await NewController(ctx).EditEvent(ValidDto(id: null)); service.Setup(s => s.CreateEventAsync(It.IsAny<EditEventInput>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("boom"));
result.Should().BeOfType<BadRequestObjectResult>(); var result = await NewController(service).AddEvent(ValidDto(), CancellationToken.None);
var bad = result.Should().BeOfType<BadRequestObjectResult>().Subject;
var response = bad.Value.Should().BeOfType<Response>().Subject;
response.Message.Should().Be("boom");
} }
[Fact] [Fact]
public async Task EditEvent_WhenEventNotFound_ReturnsNotFound() public async Task EditEvent_WhenIdMissing_ReturnsBadRequestWithoutCallingService()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var result = await NewController(ctx).EditEvent(ValidDto(id: 999)); var result = await NewController(service).EditEvent(ValidDto(id: null), CancellationToken.None);
var bad = result.Should().BeOfType<BadRequestObjectResult>().Subject;
var response = bad.Value.Should().BeOfType<Response>().Subject;
response.Message.Should().Be("Event ID is required for editing");
service.Verify(s => s.UpdateEventAsync(It.IsAny<EditEventInput>(), It.IsAny<CancellationToken>()), Times.Never);
}
[Fact]
public async Task EditEvent_WhenServiceReturnsFalse_ReturnsNotFound()
{
var service = new Mock<ICalendarService>();
service.Setup(s => s.UpdateEventAsync(It.IsAny<EditEventInput>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
var result = await NewController(service).EditEvent(ValidDto(id: 5), CancellationToken.None);
var nf = result.Should().BeOfType<NotFoundObjectResult>().Subject;
var response = nf.Value.Should().BeOfType<Response>().Subject;
response.Message.Should().Be("Event not found");
}
[Fact]
public async Task EditEvent_WhenServiceReturnsTrue_ReturnsOkWithUpdatedMessage()
{
var service = new Mock<ICalendarService>();
service.Setup(s => s.UpdateEventAsync(It.IsAny<EditEventInput>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
var result = await NewController(service).EditEvent(ValidDto(id: 5, title: "New"), CancellationToken.None);
var ok = result.Should().BeOfType<OkObjectResult>().Subject;
ok.Value.Should().BeOfType<DataResponse>().Which.Message.Should().Be("Event Updated Successfully");
}
[Fact]
public async Task GetEventById_WhenServiceReturnsNull_ReturnsNotFound()
{
var service = new Mock<ICalendarService>();
service.Setup(s => s.GetEventByIdAsync(7, It.IsAny<CancellationToken>())).ReturnsAsync((EventDTO?)null);
var result = await NewController(service).GetEventById(7, CancellationToken.None);
result.Should().BeOfType<NotFoundObjectResult>(); result.Should().BeOfType<NotFoundObjectResult>();
} }
[Fact] [Fact]
public async Task EditEvent_WhenEventExists_UpdatesFieldsAndStampsModified() public async Task GetEventById_WhenServiceReturnsDto_ReturnsOkWithEventDto()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1)); service.Setup(s => s.GetEventByIdAsync(7, It.IsAny<CancellationToken>()))
.ReturnsAsync(new EventDTO { Id = 7, Title = "Review", createdby = "abc" });
var result = await NewController(ctx).EditEvent(ValidDto(id: existing.Id, title: "New")); var result = await NewController(service).GetEventById(7, CancellationToken.None);
result.Should().BeOfType<OkObjectResult>();
var updated = ctx.Events.Single();
updated.Title.Should().Be("New");
updated.LastModificationTime.Should().NotBeNull();
}
[Fact]
public async Task GetEventById_WhenExists_ReturnsEventDto()
{
using var ctx = NewContext();
var existing = SeedEvent(ctx, "Review", new DateTime(2026, 6, 1));
var result = await NewController(ctx).GetEventById(existing.Id);
var ok = result.Should().BeOfType<OkObjectResult>().Subject; var ok = result.Should().BeOfType<OkObjectResult>().Subject;
var dto = ok.Value.Should().BeOfType<DataResponse>().Subject.Data as Event_DTO; var response = ok.Value.Should().BeOfType<DataResponse>().Subject;
var dto = response.Data as Event_DTO;
dto.Should().NotBeNull();
dto!.Title.Should().Be("Review"); dto!.Title.Should().Be("Review");
dto!.createdby.Should().Be("abc");
} }
[Fact] [Fact]
public async Task GetEventById_WhenSoftDeleted_ReturnsNotFound() public async Task DeleteEvent_WhenServiceReturnsFalse_ReturnsNotFound()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var deleted = SeedEvent(ctx, "Gone", new DateTime(2026, 6, 1), isDeleted: true); service.Setup(s => s.DeleteEventAsync(9, It.IsAny<CancellationToken>())).ReturnsAsync(false);
var result = await NewController(ctx).GetEventById(deleted.Id); var result = await NewController(service).DeleteEvent(9, CancellationToken.None);
result.Should().BeOfType<NotFoundObjectResult>(); result.Should().BeOfType<NotFoundObjectResult>();
} }
[Fact] [Fact]
public async Task DeleteEvent_WhenExists_SoftDeletesAndKeepsRow() public async Task DeleteEvent_WhenServiceReturnsTrue_ReturnsOkWithDeletedMessage()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var existing = SeedEvent(ctx, "Cancel", new DateTime(2026, 6, 1)); service.Setup(s => s.DeleteEventAsync(9, It.IsAny<CancellationToken>())).ReturnsAsync(true);
var result = await NewController(ctx).DeleteEvent(existing.Id); var result = await NewController(service).DeleteEvent(9, CancellationToken.None);
result.Should().BeOfType<OkObjectResult>(); var ok = result.Should().BeOfType<OkObjectResult>().Subject;
var row = ctx.Events.Single(); ok.Value.Should().BeOfType<DataResponse>().Which.Message.Should().Be("Event Deleted Successfully");
row.IsDeleted.Should().Be(true);
row.DeletionTime.Should().NotBeNull();
} }
[Fact] [Fact]
public async Task DeleteEvent_WhenNotFound_ReturnsNotFound() public async Task GetEventList_ReturnsOkWithRowsAndTotalCount()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
var result = await NewController(ctx).DeleteEvent(12345); service.Setup(s => s.GetEventListAsync("alpha", 1, 10, It.IsAny<CancellationToken>()))
.ReturnsAsync(new PagedEventsResult
{
TotalCount = 2,
Rows = new List<EventDTO>
{
new() { Id = 1, Title = "Alpha meeting" },
new() { Id = 2, Title = "Alpha review" }
}
});
result.Should().BeOfType<NotFoundObjectResult>(); var result = await NewController(service).GetEventList(search: "alpha", page: 1, pageSize: 10);
}
[Fact]
public void GetEventList_ExcludesDeletedAndCountsAfterFilter()
{
using var ctx = NewContext();
SeedEvent(ctx, "Alpha meeting", new DateTime(2026, 6, 1));
SeedEvent(ctx, "Beta meeting", new DateTime(2026, 6, 2));
SeedEvent(ctx, "Alpha review", new DateTime(2026, 6, 3));
SeedEvent(ctx, "Alpha archived", new DateTime(2026, 6, 4), isDeleted: true);
var result = NewController(ctx).GetEventList(search: "alpha", page: 1, pageSize: 10);
var ok = result.Should().BeOfType<OkObjectResult>().Subject; var ok = result.Should().BeOfType<OkObjectResult>().Subject;
var data = ok.Value.Should().BeOfType<DataResponse>().Subject.Data!; var data = ok.Value.Should().BeOfType<DataResponse>().Subject.Data!;
((int)Prop(data, "totalCount")).Should().Be(2); ((int)Prop(data, "totalCount")).Should().Be(2);
}
[Fact]
public void GetEventList_OrdersByStartDateDescending()
{
using var ctx = NewContext();
SeedEvent(ctx, "Earliest", new DateTime(2026, 6, 1));
SeedEvent(ctx, "Latest", new DateTime(2026, 6, 10));
SeedEvent(ctx, "Middle", new DateTime(2026, 6, 5));
var result = NewController(ctx).GetEventList(page: 1, pageSize: 10);
var data = (result as OkObjectResult)!.Value.As<DataResponse>().Data!;
var rows = ((IEnumerable)Prop(data, "rows")).Cast<Event_DTO>().ToList(); var rows = ((IEnumerable)Prop(data, "rows")).Cast<Event_DTO>().ToList();
rows.Select(r => r.Title).Should().ContainInOrder("Latest", "Middle", "Earliest"); rows.Should().HaveCount(2);
} }
[Fact] [Fact]
public void GetEventsForCalendar_ReturnsOnlyEventsOverlappingRange() public async Task GetEventsForCalendar_ReturnsOkWithEvents()
{ {
using var ctx = NewContext(); var service = new Mock<ICalendarService>();
SeedEvent(ctx, "InRange", new DateTime(2026, 6, 15)); service.Setup(s => s.GetEventsForCalendarAsync(It.IsAny<DateTime?>(), It.IsAny<DateTime?>(), It.IsAny<CancellationToken>()))
SeedEvent(ctx, "BeforeRange", new DateTime(2026, 5, 1)); .ReturnsAsync(new List<EventDTO> { new() { Id = 1, Title = "InRange" } });
SeedEvent(ctx, "AfterRange", new DateTime(2026, 7, 1));
var result = NewController(ctx).GetEventsForCalendar( var result = await NewController(service).GetEventsForCalendar(
start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30)); start: new DateTime(2026, 6, 1), end: new DateTime(2026, 6, 30), CancellationToken.None);
var ok = result.Should().BeOfType<OkObjectResult>().Subject; var ok = result.Should().BeOfType<OkObjectResult>().Subject;
var events = ok.Value.As<DataResponse>().Data as IEnumerable<Event_DTO>; var data = ok.Value.Should().BeOfType<DataResponse>().Subject.Data;
events!.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" }); var events = ((IEnumerable)data!).Cast<Event_DTO>().ToList();
events.Single().Title.Should().Be("InRange");
} }
} }

View file

@ -0,0 +1,188 @@
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Implementation;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Implementation;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class CalendarServiceTests
{
private static ApplicationDbContext NewContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
private static CalendarService NewService(ApplicationDbContext ctx) =>
new(new CalendarDataService(ctx));
private static EditEventInput ValidInput(int? id = null, string title = "Standup") => new()
{
Id = id,
Title = title,
ReminderMinutes = 15,
EventColor = "primary",
StartDate = new DateTime(2026, 6, 1),
StartTime = "08:00 AM",
EndDate = new DateTime(2026, 6, 1),
EndTime = "08:30 AM"
};
private static Events SeedEvent(ApplicationDbContext ctx, string title, DateTime startDate, bool? isDeleted = false)
{
var ev = new Events
{
Title = title,
EventColor = "primary",
ReminderMinutes = 15,
StartDate = startDate,
EndDate = startDate,
IsDeleted = isDeleted,
CreatedDate = DateTime.Now
};
ctx.Events.Add(ev);
ctx.SaveChanges();
return ev;
}
private static object Prop(object source, string name) =>
source.GetType().GetProperty(name)!.GetValue(source)!;
[Fact]
public async Task CreateEventAsync_PersistsEventWithMappedFieldsAndCreatedDate()
{
using var ctx = NewContext();
await NewService(ctx).CreateEventAsync(ValidInput(), CancellationToken.None);
var saved = ctx.Events.Single();
saved.Title.Should().Be("Standup");
saved.IsDeleted.Should().Be(false);
saved.CreatedDate.Should().NotBeNull();
}
[Fact]
public async Task UpdateEventAsync_WhenNotFound_ReturnsFalse()
{
using var ctx = NewContext();
var result = await NewService(ctx).UpdateEventAsync(ValidInput(id: 999), CancellationToken.None);
result.Should().BeFalse();
}
[Fact]
public async Task UpdateEventAsync_WhenSoftDeleted_ReturnsFalse()
{
using var ctx = NewContext();
var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1), isDeleted: true);
var result = await NewService(ctx).UpdateEventAsync(ValidInput(id: existing.Id, title: "New"), CancellationToken.None);
result.Should().BeFalse();
}
[Fact]
public async Task UpdateEventAsync_WhenEventExists_UpdatesFieldsAndStampsModified()
{
using var ctx = NewContext();
var existing = SeedEvent(ctx, "Old", new DateTime(2026, 6, 1));
var result = await NewService(ctx).UpdateEventAsync(ValidInput(id: existing.Id, title: "New"), CancellationToken.None);
result.Should().BeTrue();
var updated = ctx.Events.Single();
updated.Title.Should().Be("New");
updated.LastModificationTime.Should().NotBeNull();
}
[Fact]
public async Task GetEventByIdAsync_WhenExists_ReturnsMappedDto()
{
using var ctx = NewContext();
var existing = SeedEvent(ctx, "Review", new DateTime(2026, 6, 1));
var dto = await NewService(ctx).GetEventByIdAsync(existing.Id, CancellationToken.None);
dto.Should().NotBeNull();
dto!.Title.Should().Be("Review");
}
[Fact]
public async Task GetEventByIdAsync_WhenSoftDeleted_ReturnsNull()
{
using var ctx = NewContext();
var deleted = SeedEvent(ctx, "Gone", new DateTime(2026, 6, 1), isDeleted: true);
var dto = await NewService(ctx).GetEventByIdAsync(deleted.Id, CancellationToken.None);
dto.Should().BeNull();
}
[Fact]
public async Task DeleteEventAsync_WhenExists_SoftDeletesAndKeepsRow()
{
using var ctx = NewContext();
var existing = SeedEvent(ctx, "Cancel", new DateTime(2026, 6, 1));
var result = await NewService(ctx).DeleteEventAsync(existing.Id, CancellationToken.None);
result.Should().BeTrue();
var row = ctx.Events.Single();
row.IsDeleted.Should().Be(true);
row.DeletionTime.Should().NotBeNull();
}
[Fact]
public async Task DeleteEventAsync_WhenNotFound_ReturnsFalse()
{
using var ctx = NewContext();
var result = await NewService(ctx).DeleteEventAsync(12345, CancellationToken.None);
result.Should().BeFalse();
}
[Fact]
public async Task GetEventListAsync_ExcludesDeletedAndCountsAfterFilter()
{
using var ctx = NewContext();
SeedEvent(ctx, "Alpha meeting", new DateTime(2026, 6, 1));
SeedEvent(ctx, "Beta meeting", new DateTime(2026, 6, 2));
SeedEvent(ctx, "Alpha review", new DateTime(2026, 6, 3));
SeedEvent(ctx, "Alpha archived", new DateTime(2026, 6, 4), isDeleted: true);
var result = await NewService(ctx).GetEventListAsync("alpha", 1, 10, CancellationToken.None);
result.TotalCount.Should().Be(2);
result.Rows.Should().HaveCount(2);
}
[Fact]
public async Task GetEventListAsync_OrdersByStartDateDescending()
{
using var ctx = NewContext();
SeedEvent(ctx, "Earliest", new DateTime(2026, 6, 1));
SeedEvent(ctx, "Latest", new DateTime(2026, 6, 10));
SeedEvent(ctx, "Middle", new DateTime(2026, 6, 5));
var result = await NewService(ctx).GetEventListAsync(null, 1, 10, CancellationToken.None);
result.Rows.Select(r => r.Title).Should().ContainInOrder("Latest", "Middle", "Earliest");
}
[Fact]
public async Task GetEventsForCalendarAsync_ReturnsOnlyEventsOverlappingRange()
{
using var ctx = NewContext();
SeedEvent(ctx, "InRange", new DateTime(2026, 6, 15));
SeedEvent(ctx, "BeforeRange", new DateTime(2026, 5, 1));
SeedEvent(ctx, "AfterRange", new DateTime(2026, 7, 1));
var events = await NewService(ctx).GetEventsForCalendarAsync(
new DateTime(2026, 6, 1), new DateTime(2026, 6, 30), CancellationToken.None);
events.Select(e => e.Title).Should().BeEquivalentTo(new[] { "InRange" });
}
}

View file

@ -0,0 +1,49 @@
using Data.SeaHavenIndustries;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Implementation;
using SeaHaven.Services.Implementation;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class DashboardServiceTests
{
private static ApplicationDbContext NewContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
private static DashboardService NewService(ApplicationDbContext ctx) =>
new(new DashboardDataService(ctx));
private static WorkOrder Wo(string? status, bool? isTemplate = false, string? assignTo = null) =>
new() { Status = status, istemplate = isTemplate, AssignTo = assignTo };
[Fact]
public async Task GetStatsAsync_CountsByStatusExcludingTemplates()
{
using var ctx = NewContext();
ctx.workOrders.AddRange(
Wo("Open", isTemplate: false),
Wo("Open", isTemplate: false, assignTo: "u1"),
Wo("In Progress"),
Wo("On Hold"),
Wo("Done"),
Wo("Done"),
Wo("Open", isTemplate: true),
Wo("Cancelled", isTemplate: false)
);
ctx.SaveChanges();
var stats = await NewService(ctx).GetStatsAsync(CancellationToken.None);
stats.Total.Should().Be(7);
stats.Open.Should().Be(4);
stats.NotDispatched.Should().Be(1);
stats.Completed.Should().Be(2);
}
}

View file

@ -0,0 +1,146 @@
using Data.SeaHavenIndustries;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Implementation;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Implementation;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class DropdownOptionsServiceTests
{
private static ApplicationDbContext NewContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
private static DropdownOptionsService NewService(ApplicationDbContext ctx) =>
new(new DropdownOptionsDataService(ctx));
private static DropdownOption Option(string category, string value, string? parent = null, int sort = 0, bool active = true) =>
new() { Category = category, Value = value, ParentValue = parent, SortOrder = sort, IsActive = active };
[Fact]
public async Task GetAllAsync_FiltersActiveAndOrdersBySortThenValue()
{
using var ctx = NewContext();
ctx.DropdownOptions.AddRange(
Option("Color", "Red", sort: 2),
Option("Color", "Blue", sort: 1),
Option("Color", "Green", sort: 1, active: false),
Option("Shape", "Circle")
);
ctx.SaveChanges();
var result = (await NewService(ctx).GetAllAsync(null, CancellationToken.None)).ToList();
result.Should().HaveCount(3);
result.Select(d => d.Value).Should().ContainInOrder("Blue", "Red", "Circle");
}
[Fact]
public async Task GetByCategoryAsync_FiltersByCategoryAndParent()
{
using var ctx = NewContext();
ctx.DropdownOptions.AddRange(
Option("State", "TX", parent: "US"),
Option("State", "CA", parent: "US"),
Option("State", "ON", parent: "CA")
);
ctx.SaveChanges();
var result = (await NewService(ctx).GetByCategoryAsync("State", "US", CancellationToken.None)).ToList();
result.Select(d => d.Value).Should().BeEquivalentTo(new[] { "TX", "CA" });
}
[Fact]
public async Task CreateAsync_WhenCategoryOrValueMissing_Throws()
{
using var ctx = NewContext();
var act = () => NewService(ctx).CreateAsync(new DropdownOptionDTO { Category = "", Value = "X" }, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>();
}
[Fact]
public async Task CreateAsync_WhenDuplicate_Throws()
{
using var ctx = NewContext();
ctx.DropdownOptions.Add(Option("Color", "Red", parent: "Primary"));
ctx.SaveChanges();
var act = () => NewService(ctx).CreateAsync(new DropdownOptionDTO { Category = "Color", Value = "Red", ParentValue = "Primary" }, CancellationToken.None);
await act.Should().ThrowAsync<InvalidOperationException>();
}
[Fact]
public async Task CreateAsync_ForcesActiveTrueAndReturnsDtoWithId()
{
using var ctx = NewContext();
var dto = await NewService(ctx).CreateAsync(
new DropdownOptionDTO { Category = "Color", Value = "Red", IsActive = false }, CancellationToken.None);
dto.Id.Should().BeGreaterThan(0);
dto.IsActive.Should().BeTrue();
ctx.DropdownOptions.Single().IsActive.Should().BeTrue();
}
[Fact]
public async Task UpdateAsync_WhenNotFound_ThrowsKeyNotFound()
{
using var ctx = NewContext();
var act = () => NewService(ctx).UpdateAsync(404, new DropdownOptionDTO { Category = "Color", Value = "Red" }, CancellationToken.None);
await act.Should().ThrowAsync<KeyNotFoundException>();
}
[Fact]
public async Task UpdateAsync_UpdatesValueParentSortActiveButNotCategory()
{
using var ctx = NewContext();
var existing = Option("Color", "Red");
ctx.DropdownOptions.Add(existing);
ctx.SaveChanges();
await NewService(ctx).UpdateAsync(existing.Id, new DropdownOptionDTO
{
Category = "Ignored",
Value = "Crimson",
ParentValue = "Warm",
SortOrder = 5,
IsActive = false
}, CancellationToken.None);
var row = ctx.DropdownOptions.Single();
row.Category.Should().Be("Color");
row.Value.Should().Be("Crimson");
row.ParentValue.Should().Be("Warm");
row.SortOrder.Should().Be(5);
row.IsActive.Should().BeFalse();
}
[Fact]
public async Task DeleteAsync_WhenNotFound_ThrowsKeyNotFound()
{
using var ctx = NewContext();
var act = () => NewService(ctx).DeleteAsync(404, CancellationToken.None);
await act.Should().ThrowAsync<KeyNotFoundException>();
}
[Fact]
public async Task DeleteAsync_RemovesOption()
{
using var ctx = NewContext();
var existing = Option("Color", "Red");
ctx.DropdownOptions.Add(existing);
ctx.SaveChanges();
await NewService(ctx).DeleteAsync(existing.Id, CancellationToken.None);
ctx.DropdownOptions.Should().BeEmpty();
}
}

View file

@ -0,0 +1,57 @@
using Data.SeaHavenIndustries;
using FluentAssertions;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SeaHaven.DataServices.DependencyInjection;
using SeaHaven.DataServices.Implementation;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DependencyInjection;
using SeaHaven.Services.Implementation;
using SeaHaven.Services.Interfaces;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class ServiceRegistrationTests
{
private static ServiceProvider BuildProvider()
{
var services = new ServiceCollection();
services.AddLogging();
services.AddDbContext<ApplicationDbContext>(options =>
options.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddDataServices();
services.AddBusinessServices();
return services.BuildServiceProvider();
}
[Fact]
public void ConventionDi_RegistersAllFiveFeatureServices()
{
using var provider = BuildProvider();
provider.GetRequiredService<ICalendarService>().Should().BeOfType<CalendarService>();
provider.GetRequiredService<ICommonService>().Should().BeOfType<CommonService>();
provider.GetRequiredService<IDashboardService>().Should().BeOfType<DashboardService>();
provider.GetRequiredService<IDropdownOptionsService>().Should().BeOfType<DropdownOptionsService>();
provider.GetRequiredService<ITaskListTemplateService>().Should().BeOfType<TaskListTemplateService>();
}
[Fact]
public void ConventionDi_RegistersFeatureDataServices()
{
using var provider = BuildProvider();
provider.GetRequiredService<ICalendarDataService>().Should().BeOfType<CalendarDataService>();
provider.GetRequiredService<ICommonDataService>().Should().BeOfType<CommonDataService>();
provider.GetRequiredService<IDashboardDataService>().Should().BeOfType<DashboardDataService>();
provider.GetRequiredService<IDropdownOptionsDataService>().Should().BeOfType<DropdownOptionsDataService>();
}
}

View file

@ -0,0 +1,146 @@
using Data.SeaHavenIndustries;
using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Implementation;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Implementation;
using Xunit;
namespace Api.SeaHavenIndustries.Tests;
public class TaskListTemplateServiceTests
{
private static ApplicationDbContext NewContext()
{
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
.Options;
return new ApplicationDbContext(options);
}
private static TaskListTemplateService NewService(ApplicationDbContext ctx) =>
new(new TaskListTemplateDataService(ctx));
private static TaskListTemplate Template(string name, bool active, params (string text, int sort)[] items)
{
var t = new TaskListTemplate { Name = name, IsActive = active };
t.Items = items.Select(i => new TaskListTemplateItem { ItemText = i.text, SortOrder = i.sort }).ToList();
return t;
}
[Fact]
public async Task GetAllAsync_ReturnsOnlyActiveOrderedByNameWithOrderedItems()
{
using var ctx = NewContext();
ctx.TaskListTemplates.AddRange(
Template("Zeta", true, ("b", 2), ("a", 1)),
Template("Alpha", true, ("first", 0)),
Template("Hidden", false)
);
ctx.SaveChanges();
var result = (await NewService(ctx).GetAllAsync(CancellationToken.None)).ToList();
result.Select(t => t.Name).Should().ContainInOrder("Alpha", "Zeta");
result.Single(t => t.Name == "Zeta").Items.Select(i => i.ItemText).Should().ContainInOrder("a", "b");
}
[Fact]
public async Task GetByIdAsync_WhenMissing_ReturnsNull()
{
using var ctx = NewContext();
var result = await NewService(ctx).GetByIdAsync(99, CancellationToken.None);
result.Should().BeNull();
}
[Fact]
public async Task GetByIdAsync_ReturnsTemplateWithOrderedItems()
{
using var ctx = NewContext();
var t = Template("Alpha", true, ("b", 2), ("a", 1));
ctx.TaskListTemplates.Add(t);
ctx.SaveChanges();
var result = await NewService(ctx).GetByIdAsync(t.Id, CancellationToken.None);
result.Should().NotBeNull();
result!.Items.Select(i => i.ItemText).Should().ContainInOrder("a", "b");
}
[Fact]
public async Task CreateAsync_PersistsTemplateAndItemsAndReturnsId()
{
using var ctx = NewContext();
var input = new TaskListTemplateInput
{
Name = "New",
Description = "desc",
IsActive = true,
Items = new()
{
new() { ItemText = "two", SortOrder = 0 },
new() { ItemText = "one", SortOrder = 0 }
}
};
var id = await NewService(ctx).CreateAsync(input, CancellationToken.None);
id.Should().BeGreaterThan(0);
var row = ctx.TaskListTemplates.Include(t => t.Items).Single();
row.Name.Should().Be("New");
row.Items.Should().HaveCount(2);
row.Items!.Select(i => i.SortOrder).Should().ContainInOrder(0, 1);
}
[Fact]
public async Task UpdateAsync_WhenNotFound_ReturnsFalse()
{
using var ctx = NewContext();
var result = await NewService(ctx).UpdateAsync(404, new TaskListTemplateInput { Name = "x" }, CancellationToken.None);
result.Should().BeFalse();
}
[Fact]
public async Task UpdateAsync_ReplacesItems()
{
using var ctx = NewContext();
var t = Template("Alpha", true, ("old", 0));
ctx.TaskListTemplates.Add(t);
ctx.SaveChanges();
var updated = await NewService(ctx).UpdateAsync(t.Id, new TaskListTemplateInput
{
Name = "Renamed",
IsActive = false,
Items = new() { new() { ItemText = "new1", SortOrder = 1 }, new() { ItemText = "new2", SortOrder = 2 } }
}, CancellationToken.None);
updated.Should().BeTrue();
var row = ctx.TaskListTemplates.Include(x => x.Items).Single();
row.Name.Should().Be("Renamed");
row.IsActive.Should().BeFalse();
row.Items!.Select(i => i.ItemText).Should().BeEquivalentTo(new[] { "new1", "new2" });
}
[Fact]
public async Task DeleteAsync_SoftDeletesByDeactivating()
{
using var ctx = NewContext();
var t = Template("Alpha", true);
ctx.TaskListTemplates.Add(t);
ctx.SaveChanges();
var result = await NewService(ctx).DeleteAsync(t.Id, CancellationToken.None);
result.Should().BeTrue();
ctx.TaskListTemplates.Single().IsActive.Should().BeFalse();
}
[Fact]
public async Task DeleteAsync_WhenNotFound_ReturnsFalse()
{
using var ctx = NewContext();
var result = await NewService(ctx).DeleteAsync(404, CancellationToken.None);
result.Should().BeFalse();
}
}

View file

@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Api.SeaHavenIndustries.DTOs; using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries; using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace Api.SeaHavenIndustries.Controllers namespace Api.SeaHavenIndustries.Controllers
{ {
@ -16,13 +12,11 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("api/Calendar")] [Route("api/Calendar")]
public class CalendarController : Controller public class CalendarController : Controller
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly ICalendarService _calendarService;
private readonly ApplicationDbContext _db;
public CalendarController(UserManager<ApplicationUser> userManager, ApplicationDbContext db) public CalendarController(ICalendarService calendarService)
{ {
_userManager = userManager; _calendarService = calendarService;
_db = db;
} }
// =================================================================================== // ===================================================================================
@ -30,31 +24,11 @@ namespace Api.SeaHavenIndustries.Controllers
// =================================================================================== // ===================================================================================
[HttpPost] [HttpPost]
[Route("AddEvent")] [Route("AddEvent")]
public async Task<IActionResult> AddEvent(EditEvent_DTO eventDto) public async Task<IActionResult> AddEvent(EditEvent_DTO eventDto, CancellationToken cancellationToken)
{ {
try try
{ {
var model = new Events await _calendarService.CreateEventAsync(MapToInput(eventDto), cancellationToken);
{
Title = eventDto.Title,
Description = eventDto.Description,
Location = eventDto.Location,
RecordID = eventDto.RecordID,
RecordName = eventDto.RecordName,
RecordTargetType = eventDto.RecordTargetType,
ReminderMinutes = eventDto.ReminderMinutes,
EventColor = eventDto.EventColor,
StartDate = eventDto.StartDate,
StartTime = eventDto.StartTime,
EndDate = eventDto.EndDate,
EndTime = eventDto.EndTime,
AllDay = eventDto.AllDay,
CreatedDate = DateTime.Now,
IsDeleted = false
};
_db.Events.Add(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" }); return Ok(new DataResponse { Message = "Event Created Successfully", Status = "200" });
} }
@ -69,7 +43,7 @@ namespace Api.SeaHavenIndustries.Controllers
// =================================================================================== // ===================================================================================
[HttpPost] [HttpPost]
[Route("EditEvent")] [Route("EditEvent")]
public async Task<IActionResult> EditEvent(EditEvent_DTO eventDto) public async Task<IActionResult> EditEvent(EditEvent_DTO eventDto, CancellationToken cancellationToken)
{ {
try try
{ {
@ -78,30 +52,12 @@ namespace Api.SeaHavenIndustries.Controllers
return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" }); return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" });
} }
var model = await _db.Events.FindAsync(eventDto.Id.Value); var updated = await _calendarService.UpdateEventAsync(MapToInput(eventDto), cancellationToken);
if (model == null || model.IsDeleted == true) if (!updated)
{ {
return NotFound(new Response { Status = "Error", Message = "Event not found" }); return NotFound(new Response { Status = "Error", Message = "Event not found" });
} }
model.Title = eventDto.Title;
model.Description = eventDto.Description;
model.Location = eventDto.Location;
model.RecordID = eventDto.RecordID;
model.RecordName = eventDto.RecordName;
model.RecordTargetType = eventDto.RecordTargetType;
model.ReminderMinutes = eventDto.ReminderMinutes;
model.EventColor = eventDto.EventColor;
model.StartDate = eventDto.StartDate;
model.StartTime = eventDto.StartTime;
model.EndDate = eventDto.EndDate;
model.EndTime = eventDto.EndTime;
model.AllDay = eventDto.AllDay;
model.LastModificationTime = DateTime.Now;
_db.Events.Update(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" }); return Ok(new DataResponse { Message = "Event Updated Successfully", Status = "200" });
} }
catch (Exception ex) catch (Exception ex)
@ -115,40 +71,18 @@ namespace Api.SeaHavenIndustries.Controllers
// =================================================================================== // ===================================================================================
[HttpGet] [HttpGet]
[Route("GetEventById/{id}")] [Route("GetEventById/{id}")]
public async Task<IActionResult> GetEventById(int id) public async Task<IActionResult> GetEventById(int id, CancellationToken cancellationToken)
{ {
try try
{ {
var model = await _db.Events var dto = await _calendarService.GetEventByIdAsync(id, cancellationToken);
.Where(e => e.Id == id && e.IsDeleted != true)
.FirstOrDefaultAsync();
if (model == null) if (dto == null)
{ {
return NotFound(new Response { Status = "Error", Message = "Event not found" }); return NotFound(new Response { Status = "Error", Message = "Event not found" });
} }
var dto = new Event_DTO return Ok(new DataResponse { Message = "Success", Status = "200", Data = MapToResponse(dto) });
{
Id = model.Id,
Title = model.Title,
Description = model.Description,
Location = model.Location,
RecordID = model.RecordID,
RecordName = model.RecordName,
RecordTargetType = model.RecordTargetType,
ReminderMinutes = model.ReminderMinutes,
EventColor = model.EventColor,
StartDate = model.StartDate,
StartTime = model.StartTime,
EndDate = model.EndDate,
EndTime = model.EndTime,
AllDay = model.AllDay,
CreatedDate = model.CreatedDate,
createdby = model.createdby
};
return Ok(new DataResponse { Message = "Success", Status = "200", Data = dto });
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -161,23 +95,16 @@ namespace Api.SeaHavenIndustries.Controllers
// =================================================================================== // ===================================================================================
[HttpDelete] [HttpDelete]
[Route("DeleteEvent/{id}")] [Route("DeleteEvent/{id}")]
public async Task<IActionResult> DeleteEvent(int id) public async Task<IActionResult> DeleteEvent(int id, CancellationToken cancellationToken)
{ {
try try
{ {
var model = await _db.Events.FindAsync(id); var deleted = await _calendarService.DeleteEventAsync(id, cancellationToken);
if (model == null) if (!deleted)
{ {
return NotFound(new Response { Status = "Error", Message = "Event not found" }); return NotFound(new Response { Status = "Error", Message = "Event not found" });
} }
// Soft delete
model.IsDeleted = true;
model.DeletionTime = DateTime.Now;
_db.Events.Update(model);
await _db.SaveChangesAsync();
return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" }); return Ok(new DataResponse { Message = "Event Deleted Successfully", Status = "200" });
} }
catch (Exception ex) catch (Exception ex)
@ -191,58 +118,19 @@ namespace Api.SeaHavenIndustries.Controllers
// =================================================================================== // ===================================================================================
[HttpGet] [HttpGet]
[Route("GetEventList")] [Route("GetEventList")]
public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10) public async Task<IActionResult> GetEventList(string? search = "", int page = 1, int pageSize = 10, CancellationToken cancellationToken = default)
{ {
try try
{ {
var query = _db.Events.Where(e => e.IsDeleted != true); var result = await _calendarService.GetEventListAsync(search, page, pageSize, cancellationToken);
// Apply search filter if provided var events = result.Rows.Select(MapToResponse).ToList();
if (!string.IsNullOrWhiteSpace(search))
{
var searchLower = search.ToLower();
query = query.Where(e =>
e.Title.ToLower().Contains(searchLower) ||
(e.Description != null && e.Description.ToLower().Contains(searchLower)) ||
(e.Location != null && e.Location.ToLower().Contains(searchLower))
);
}
// IMPORTANT: Calculate totalCount AFTER applying filters
var totalCount = query.Count();
// Apply pagination
var events = query
.OrderByDescending(e => e.StartDate)
.ThenByDescending(e => e.CreatedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.Select(e => new Event_DTO
{
Id = e.Id,
Title = e.Title,
Description = e.Description,
Location = e.Location,
RecordID = e.RecordID,
RecordName = e.RecordName,
RecordTargetType = e.RecordTargetType,
ReminderMinutes = e.ReminderMinutes,
EventColor = e.EventColor,
StartDate = e.StartDate,
StartTime = e.StartTime,
EndDate = e.EndDate,
EndTime = e.EndTime,
AllDay = e.AllDay,
CreatedDate = e.CreatedDate,
createdby = e.createdby
})
.ToList();
return Ok(new DataResponse return Ok(new DataResponse
{ {
Message = "Success", Message = "Success",
Status = "200", Status = "200",
Data = new { rows = events, totalCount } Data = new { rows = events, totalCount = result.TotalCount }
}); });
} }
catch (Exception ex) catch (Exception ex)
@ -256,51 +144,62 @@ namespace Api.SeaHavenIndustries.Controllers
// =================================================================================== // ===================================================================================
[HttpGet] [HttpGet]
[Route("GetEventsForCalendar")] [Route("GetEventsForCalendar")]
public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end) public async Task<IActionResult> GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end, CancellationToken cancellationToken)
{ {
try try
{ {
var query = _db.Events.Where(e => e.IsDeleted != true); var events = await _calendarService.GetEventsForCalendarAsync(start, end, cancellationToken);
// Filter by date range if provided return Ok(new DataResponse { Message = "Success", Status = "200", Data = events.Select(MapToResponse).ToList() });
if (start.HasValue && end.HasValue)
{
query = query.Where(e =>
(e.StartDate >= start.Value && e.StartDate <= end.Value) ||
(e.EndDate >= start.Value && e.EndDate <= end.Value) ||
(e.StartDate <= start.Value && e.EndDate >= end.Value)
);
}
var events = query
.Select(e => new Event_DTO
{
Id = e.Id,
Title = e.Title,
Description = e.Description,
Location = e.Location,
RecordID = e.RecordID,
RecordName = e.RecordName,
RecordTargetType = e.RecordTargetType,
ReminderMinutes = e.ReminderMinutes,
EventColor = e.EventColor,
StartDate = e.StartDate,
StartTime = e.StartTime,
EndDate = e.EndDate,
EndTime = e.EndTime,
AllDay = e.AllDay,
CreatedDate = e.CreatedDate,
createdby = e.createdby
})
.ToList();
return Ok(new DataResponse { Message = "Success", Status = "200", Data = events });
} }
catch (Exception ex) catch (Exception ex)
{ {
return BadRequest(new Response { Status = "Error", Message = ex.Message }); return BadRequest(new Response { Status = "Error", Message = ex.Message });
} }
} }
private static EditEventInput MapToInput(EditEvent_DTO eventDto)
{
return new EditEventInput
{
Id = eventDto.Id,
Title = eventDto.Title,
Description = eventDto.Description,
Location = eventDto.Location,
RecordID = eventDto.RecordID,
RecordName = eventDto.RecordName,
RecordTargetType = eventDto.RecordTargetType,
ReminderMinutes = eventDto.ReminderMinutes,
EventColor = eventDto.EventColor,
StartDate = eventDto.StartDate,
StartTime = eventDto.StartTime,
EndDate = eventDto.EndDate,
EndTime = eventDto.EndTime,
AllDay = eventDto.AllDay
};
}
private static Event_DTO MapToResponse(EventDTO dto)
{
return new Event_DTO
{
Id = dto.Id,
Title = dto.Title,
Description = dto.Description,
Location = dto.Location,
RecordID = dto.RecordID,
RecordName = dto.RecordName,
RecordTargetType = dto.RecordTargetType,
ReminderMinutes = dto.ReminderMinutes,
EventColor = dto.EventColor,
StartDate = dto.StartDate,
StartTime = dto.StartTime,
EndDate = dto.EndDate,
EndTime = dto.EndTime,
AllDay = dto.AllDay,
CreatedDate = dto.CreatedDate,
createdby = dto.createdby
};
}
} }
} }

View file

@ -1,11 +1,8 @@
using Api.SeaHavenIndustries.DTOs; using Api.SeaHavenIndustries.DTOs;
using Api.SeaHavenIndustries.Helper.ViewModel; using Api.SeaHavenIndustries.Helper.ViewModel;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using SeaHaven.Services.Interfaces;
using Microsoft.Extensions.Configuration;
namespace Api.SeaHavenIndustries.Controllers namespace Api.SeaHavenIndustries.Controllers
{ {
@ -15,59 +12,38 @@ namespace Api.SeaHavenIndustries.Controllers
public class CommonController : Controller public class CommonController : Controller
{ {
private readonly UserManager<ApplicationUser> _userManager; private readonly ICommonService _commonService;
private readonly ApplicationDbContext _db;
public CommonController(UserManager<ApplicationUser> userManager, ApplicationDbContext db) public CommonController(ICommonService commonService)
{ {
_userManager = userManager; _commonService = commonService;
_db = db;
} }
[HttpGet] [HttpGet]
[Route("GetUsers")] [Route("GetUsers")]
public async Task<IActionResult> GetUsers(string? search = "", int page = 1, int pageSize = 10) public async Task<IActionResult> GetUsers(string? search = "", int page = 1, int pageSize = 10, CancellationToken cancellationToken = default)
{ {
try try
{ {
var allUsersQuery = _db.Users var result = await _commonService.GetUsersPagedAsync(search, page, pageSize, cancellationToken);
.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower()))
.OrderByDescending(d => d.Id);
var totalCount = allUsersQuery.Count(); var filteredUsers = result.Users.Select(u => new UserRoleViewModel
var paginatedUsers = allUsersQuery
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToList();
// Calculate the number of pages
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
var filteredUsers = new List<UserRoleViewModel>();
foreach (var user in paginatedUsers)
{ {
var roles = await _userManager.GetRolesAsync(user); RoleName = u.RoleName,
if (!roles.Contains("Admin")) Email = u.Email,
{ UserName = u.UserName,
filteredUsers.Add(new UserRoleViewModel Date = u.Date,
{ Status = u.Status,
RoleName = roles.Count()>0?roles.First():"", Id = u.Id
Email = user.Email, }).ToList();
UserName = user.FirstName,
Date = user.CreatedDate?.Date.ToString(),
Status = user.UniqueName,
Id = user.Id
});
}
}
// Create a view model containing the paged items and pagination metadata
var viewModel = new Pagination_DTO var viewModel = new Pagination_DTO
{ {
Data = filteredUsers, Data = filteredUsers,
PageNumber = page, PageNumber = result.PageNumber,
PageSize = pageSize, PageSize = result.PageSize,
TotalCount = totalCount, TotalCount = result.TotalCount,
TotalPages = totalPages TotalPages = result.TotalPages
}; };
return Ok(viewModel); return Ok(viewModel);
} }
@ -79,11 +55,11 @@ namespace Api.SeaHavenIndustries.Controllers
[HttpGet] [HttpGet]
[Route("GetRoles")] [Route("GetRoles")]
public async Task<IActionResult> GetRoles() public async Task<IActionResult> GetRoles(CancellationToken cancellationToken)
{ {
try try
{ {
var data = await _db.Roles.Select(s => new {Id=s.Name,s.Name}).ToListAsync(); var data = await _commonService.GetRolesAsync(cancellationToken);
return Ok(data); return Ok(data);
} }
@ -96,40 +72,25 @@ namespace Api.SeaHavenIndustries.Controllers
[HttpGet] [HttpGet]
[Route("GetSitesName")] [Route("GetSitesName")]
public async Task<IActionResult> GetSiteName() public async Task<IActionResult> GetSiteName(CancellationToken cancellationToken)
{ {
var data =await _db.Locations.Select(n=> new var data = await _commonService.GetSiteNamesAsync(cancellationToken);
{
n.Id,
n.Name,
n.Title
}).ToListAsync();
return Ok(data); return Ok(data);
} }
[HttpGet] [HttpGet]
[Route("GetContacts")] [Route("GetContacts")]
public async Task<IActionResult> GetContacts() public async Task<IActionResult> GetContacts(CancellationToken cancellationToken)
{ {
var data =await _db.Contacts.Select(n=> new var data = await _commonService.GetContactsAsync(cancellationToken);
{
n.Id,
Name = (n.FirstName + " " + n.LastName).Trim(),
email=n.Email,
phone=n.PhoneNumber
}).ToListAsync();
return Ok(data); return Ok(data);
} }
[HttpGet] [HttpGet]
[Route("GetCategories")] [Route("GetCategories")]
public async Task<IActionResult> GetCategories() public async Task<IActionResult> GetCategories(CancellationToken cancellationToken)
{ {
var data = await _db.Categories.Select(s=> new var data = await _commonService.GetCategoriesAsync(cancellationToken);
{
s.Id,
s.Name
}).ToListAsync();
return Ok(data); return Ok(data);
} }
} }

View file

@ -1,7 +1,6 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using SeaHaven.Services.Interfaces;
namespace Api.SeaHavenIndustries.Controllers namespace Api.SeaHavenIndustries.Controllers
{ {
@ -10,29 +9,24 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("api/[controller]")] [Route("api/[controller]")]
public class DashboardController : Controller public class DashboardController : Controller
{ {
private readonly ApplicationDbContext _db; private readonly IDashboardService _dashboardService;
public DashboardController(ApplicationDbContext db) public DashboardController(IDashboardService dashboardService)
{ {
_db = db; _dashboardService = dashboardService;
} }
[HttpGet("Stats")] [HttpGet("Stats")]
public async Task<IActionResult> GetStats() public async Task<IActionResult> GetStats(CancellationToken cancellationToken)
{ {
var query = _db.workOrders.Where(w => w.istemplate != true); var stats = await _dashboardService.GetStatsAsync(cancellationToken);
var total = await query.CountAsync();
var open = await query.CountAsync(w => w.Status == "Open" || w.Status == "In Progress" || w.Status == "On Hold");
var notDispatched = await query.CountAsync(w => (w.Status == "Open") && (w.AssignTo == null || w.AssignTo == ""));
var completed = await query.CountAsync(w => w.Status == "Done");
return Ok(new return Ok(new
{ {
total, total = stats.Total,
open, open = stats.Open,
notDispatched, notDispatched = stats.NotDispatched,
completed completed = stats.Completed
}); });
} }
} }

View file

@ -1,7 +1,8 @@
using Data.SeaHavenIndustries; using Api.SeaHavenIndustries.DTOs;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace Api.SeaHavenIndustries.Controllers namespace Api.SeaHavenIndustries.Controllers
{ {
@ -10,78 +11,80 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("api/[controller]")] [Route("api/[controller]")]
public class DropdownOptionsController : Controller public class DropdownOptionsController : Controller
{ {
private readonly ApplicationDbContext _db; private readonly IDropdownOptionsService _dropdownOptionsService;
public DropdownOptionsController(ApplicationDbContext db) public DropdownOptionsController(IDropdownOptionsService dropdownOptionsService)
{ {
_db = db; _dropdownOptionsService = dropdownOptionsService;
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> GetAll([FromQuery] string? category = null) public async Task<IActionResult> GetAll([FromQuery] string? category = null, CancellationToken cancellationToken = default)
{ {
var query = _db.DropdownOptions.Where(d => d.IsActive); var data = await _dropdownOptionsService.GetAllAsync(category, cancellationToken);
if (!string.IsNullOrWhiteSpace(category))
query = query.Where(d => d.Category == category);
var data = await query.OrderBy(d => d.Category).ThenBy(d => d.SortOrder).ThenBy(d => d.Value).ToListAsync();
return Ok(data); return Ok(data);
} }
[HttpGet("ByCategory/{category}")] [HttpGet("ByCategory/{category}")]
public async Task<IActionResult> GetByCategory(string category, [FromQuery] string? parent = null) public async Task<IActionResult> GetByCategory(string category, [FromQuery] string? parent = null, CancellationToken cancellationToken = default)
{ {
var query = _db.DropdownOptions.Where(d => d.IsActive && d.Category == category); var data = await _dropdownOptionsService.GetByCategoryAsync(category, parent, cancellationToken);
if (!string.IsNullOrWhiteSpace(parent))
query = query.Where(d => d.ParentValue == parent);
var data = await query.OrderBy(d => d.SortOrder).ThenBy(d => d.Value).ToListAsync();
return Ok(data); return Ok(data);
} }
[HttpPost] [HttpPost]
public async Task<IActionResult> Create([FromBody] DropdownOption model) public async Task<IActionResult> Create([FromBody] DropdownOption_DTO model, CancellationToken cancellationToken)
{ {
if (string.IsNullOrWhiteSpace(model.Category) || string.IsNullOrWhiteSpace(model.Value)) try
return BadRequest(new { message = "Category and Value are required" }); {
var created = await _dropdownOptionsService.CreateAsync(MapToService(model), cancellationToken);
var existing = await _db.DropdownOptions return Ok(created);
.AnyAsync(d => d.Category == model.Category && d.Value == model.Value && d.ParentValue == model.ParentValue); }
if (existing) catch (InvalidOperationException ex)
return BadRequest(new { message = "This option already exists" }); {
return BadRequest(new { message = ex.Message });
model.Id = 0; }
model.IsActive = true;
_db.DropdownOptions.Add(model);
await _db.SaveChangesAsync();
return Ok(model);
} }
[HttpPut("{id}")] [HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] DropdownOption model) public async Task<IActionResult> Update(int id, [FromBody] DropdownOption_DTO model, CancellationToken cancellationToken)
{ {
var item = await _db.DropdownOptions.FindAsync(id); try
if (item == null) {
var updated = await _dropdownOptionsService.UpdateAsync(id, MapToService(model), cancellationToken);
return Ok(updated);
}
catch (KeyNotFoundException)
{
return NotFound(new { message = "Option not found" }); return NotFound(new { message = "Option not found" });
}
item.Value = model.Value;
item.ParentValue = model.ParentValue;
item.SortOrder = model.SortOrder;
item.IsActive = model.IsActive;
await _db.SaveChangesAsync();
return Ok(item);
} }
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id) public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
{ {
var item = await _db.DropdownOptions.FindAsync(id); try
if (item == null) {
await _dropdownOptionsService.DeleteAsync(id, cancellationToken);
return Ok(new { message = "Deleted" });
}
catch (KeyNotFoundException)
{
return NotFound(new { message = "Option not found" }); return NotFound(new { message = "Option not found" });
}
}
_db.DropdownOptions.Remove(item); private static DropdownOptionDTO MapToService(DropdownOption_DTO model)
await _db.SaveChangesAsync(); {
return Ok(new { message = "Deleted" }); return new DropdownOptionDTO
{
Id = model.Id,
Category = model.Category,
Value = model.Value,
ParentValue = model.ParentValue,
SortOrder = model.SortOrder,
IsActive = model.IsActive
};
} }
} }
} }

View file

@ -2,7 +2,8 @@ using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries; using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore; using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace Api.SeaHavenIndustries.Controllers namespace Api.SeaHavenIndustries.Controllers
{ {
@ -12,57 +13,24 @@ namespace Api.SeaHavenIndustries.Controllers
[Route("api/tasklisttemplates")] [Route("api/tasklisttemplates")]
public class TaskListTemplateController : Controller public class TaskListTemplateController : Controller
{ {
private readonly ApplicationDbContext _db; private readonly ITaskListTemplateService _taskListTemplateService;
public TaskListTemplateController(ApplicationDbContext db) public TaskListTemplateController(ITaskListTemplateService taskListTemplateService)
{ {
_db = db; _taskListTemplateService = taskListTemplateService;
} }
[HttpGet] [HttpGet]
public async Task<IActionResult> GetAll() public async Task<IActionResult> GetAll(CancellationToken cancellationToken)
{ {
var data = await _db.TaskListTemplates var data = await _taskListTemplateService.GetAllAsync(cancellationToken);
.Where(t => t.IsActive)
.Include(t => t.Items.OrderBy(i => i.SortOrder))
.OrderBy(t => t.Name)
.Select(t => new
{
t.Id,
t.Name,
t.Description,
t.IsActive,
items = t.Items.OrderBy(i => i.SortOrder).Select(i => new
{
i.Id,
i.ItemText,
i.SortOrder
}).ToList()
})
.ToListAsync();
return Ok(data); return Ok(data);
} }
[HttpGet("{id}")] [HttpGet("{id}")]
public async Task<IActionResult> GetById(int id) public async Task<IActionResult> GetById(int id, CancellationToken cancellationToken)
{ {
var template = await _db.TaskListTemplates var template = await _taskListTemplateService.GetByIdAsync(id, cancellationToken);
.Include(t => t.Items.OrderBy(i => i.SortOrder))
.Where(t => t.Id == id)
.Select(t => new
{
t.Id,
t.Name,
t.Description,
t.IsActive,
items = t.Items.OrderBy(i => i.SortOrder).Select(i => new
{
i.Id,
i.ItemText,
i.SortOrder
}).ToList()
})
.FirstOrDefaultAsync();
if (template == null) if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" }); return NotFound(new Response { Status = "Error", Message = "Template not found" });
@ -71,25 +39,13 @@ namespace Api.SeaHavenIndustries.Controllers
} }
[HttpPost] [HttpPost]
public async Task<IActionResult> Create([FromBody] TaskListTemplate_DTO model) public async Task<IActionResult> Create([FromBody] TaskListTemplate_DTO model, CancellationToken cancellationToken)
{ {
try try
{ {
var template = new TaskListTemplate var input = MapToInput(model);
{ var id = await _taskListTemplateService.CreateAsync(input, cancellationToken);
Name = model.Name, return Ok(new { message = "Template created", id });
Description = model.Description,
IsActive = model.IsActive,
Items = model.Items.Select((item, idx) => new TaskListTemplateItem
{
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList()
};
_db.TaskListTemplates.Add(template);
await _db.SaveChangesAsync();
return Ok(new { message = "Template created", id = template.Id });
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -98,31 +54,15 @@ namespace Api.SeaHavenIndustries.Controllers
} }
[HttpPut("{id}")] [HttpPut("{id}")]
public async Task<IActionResult> Update(int id, [FromBody] TaskListTemplate_DTO model) public async Task<IActionResult> Update(int id, [FromBody] TaskListTemplate_DTO model, CancellationToken cancellationToken)
{ {
try try
{ {
var template = await _db.TaskListTemplates var input = MapToInput(model);
.Include(t => t.Items) var updated = await _taskListTemplateService.UpdateAsync(id, input, cancellationToken);
.FirstOrDefaultAsync(t => t.Id == id); if (!updated)
if (template == null)
return NotFound(new Response { Status = "Error", Message = "Template not found" }); return NotFound(new Response { Status = "Error", Message = "Template not found" });
template.Name = model.Name;
template.Description = model.Description;
template.IsActive = model.IsActive;
_db.TaskListTemplateItems.RemoveRange(template.Items);
template.Items = model.Items.Select((item, idx) => new TaskListTemplateItem
{
TaskListTemplateId = id,
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList();
await _db.SaveChangesAsync();
return Ok(new { message = "Template updated" }); return Ok(new { message = "Template updated" });
} }
catch (Exception ex) catch (Exception ex)
@ -132,15 +72,28 @@ namespace Api.SeaHavenIndustries.Controllers
} }
[HttpDelete("{id}")] [HttpDelete("{id}")]
public async Task<IActionResult> Delete(int id) public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
{ {
var template = await _db.TaskListTemplates.FindAsync(id); var deleted = await _taskListTemplateService.DeleteAsync(id, cancellationToken);
if (template == null) if (!deleted)
return NotFound(new Response { Status = "Error", Message = "Template not found" }); return NotFound(new Response { Status = "Error", Message = "Template not found" });
template.IsActive = false;
await _db.SaveChangesAsync();
return Ok(new { message = "Template deleted" }); return Ok(new { message = "Template deleted" });
} }
private static TaskListTemplateInput MapToInput(TaskListTemplate_DTO model)
{
return new TaskListTemplateInput
{
Name = model.Name,
Description = model.Description,
IsActive = model.IsActive,
Items = model.Items.Select(item => new TaskListTemplateInputItem
{
ItemText = item.ItemText,
SortOrder = item.SortOrder
}).ToList()
};
}
} }
} }

View file

@ -0,0 +1,12 @@
namespace Api.SeaHavenIndustries.DTOs
{
public class DropdownOption_DTO
{
public int Id { get; set; }
public string Category { get; set; } = "";
public string Value { get; set; } = "";
public string? ParentValue { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
}
}

View file

@ -0,0 +1,85 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class CalendarDataService : ICalendarDataService
{
private readonly ApplicationDbContext _context;
public CalendarDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<Events?> GetEventByIdAsync(int id, CancellationToken cancellationToken)
{
return await _context.Events
.AsNoTracking()
.Where(e => e.Id == id && e.IsDeleted != true)
.FirstOrDefaultAsync(cancellationToken);
}
public async Task<Events?> GetEventForEditAsync(int id, CancellationToken cancellationToken)
{
return await _context.Events.FindAsync(new object[] { id }, cancellationToken);
}
public async Task<Events> AddEventAsync(Events ev, CancellationToken cancellationToken)
{
await _context.Events.AddAsync(ev, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return ev;
}
public async Task UpdateEventAsync(Events ev, CancellationToken cancellationToken)
{
_context.Events.Update(ev);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task<(IEnumerable<Events> Items, int TotalCount)> GetEventListPagedAsync(int page, int pageSize, string? search, CancellationToken cancellationToken)
{
var query = _context.Events.Where(e => e.IsDeleted != true);
if (!string.IsNullOrWhiteSpace(search))
{
var searchLower = search.ToLower();
query = query.Where(e =>
e.Title.ToLower().Contains(searchLower) ||
(e.Description != null && e.Description.ToLower().Contains(searchLower)) ||
(e.Location != null && e.Location.ToLower().Contains(searchLower)));
}
var totalCount = await query.CountAsync(cancellationToken);
var events = await query
.OrderByDescending(e => e.StartDate)
.ThenByDescending(e => e.CreatedDate)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.AsNoTracking()
.ToListAsync(cancellationToken);
return (events, totalCount);
}
public async Task<IEnumerable<Events>> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken)
{
var query = _context.Events.Where(e => e.IsDeleted != true);
if (start.HasValue && end.HasValue)
{
query = query.Where(e =>
(e.StartDate >= start.Value && e.StartDate <= end.Value) ||
(e.EndDate >= start.Value && e.EndDate <= end.Value) ||
(e.StartDate <= start.Value && e.EndDate >= end.Value));
}
return await query
.AsNoTracking()
.ToListAsync(cancellationToken);
}
}
}

View file

@ -0,0 +1,81 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class CommonDataService : ICommonDataService
{
private readonly ApplicationDbContext _context;
public CommonDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<(IEnumerable<ApplicationUser> Items, int TotalCount)> GetUsersPagedAsync(int page, int pageSize, string search, CancellationToken cancellationToken)
{
var query = _context.Users
.Where(w => (w.FirstName ?? "").ToLower().Contains((search ?? "").ToLower()))
.OrderByDescending(d => d.Id);
var totalCount = await query.CountAsync(cancellationToken);
var paginatedUsers = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.AsNoTracking()
.ToListAsync(cancellationToken);
return (paginatedUsers, totalCount);
}
public async Task<IEnumerable<string?>> GetRoleNamesAsync(CancellationToken cancellationToken)
{
return await _context.Roles
.AsNoTracking()
.Select(s => s.Name)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<Locations>> GetSiteNamesAsync(CancellationToken cancellationToken)
{
return await _context.Locations
.AsNoTracking()
.Select(n => new Locations
{
Id = n.Id,
Name = n.Name,
Title = n.Title
})
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<Contacts>> GetContactsAsync(CancellationToken cancellationToken)
{
return await _context.Contacts
.AsNoTracking()
.Select(n => new Contacts
{
Id = n.Id,
FirstName = n.FirstName,
LastName = n.LastName,
Email = n.Email,
PhoneNumber = n.PhoneNumber
})
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<Category>> GetCategoriesAsync(CancellationToken cancellationToken)
{
return await _context.Categories
.AsNoTracking()
.Select(s => new Category
{
Id = s.Id,
Name = s.Name
})
.ToListAsync(cancellationToken);
}
}
}

View file

@ -0,0 +1,34 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class DashboardDataService : IDashboardDataService
{
private readonly ApplicationDbContext _context;
public DashboardDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<DashboardCounts> GetWorkOrderCountsAsync(CancellationToken cancellationToken)
{
var query = _context.workOrders.Where(w => w.istemplate != true);
var total = await query.CountAsync(cancellationToken);
var open = await query.CountAsync(w => w.Status == "Open" || w.Status == "In Progress" || w.Status == "On Hold", cancellationToken);
var notDispatched = await query.CountAsync(w => (w.Status == "Open") && (w.AssignTo == null || w.AssignTo == ""), cancellationToken);
var completed = await query.CountAsync(w => w.Status == "Done", cancellationToken);
return new DashboardCounts
{
Total = total,
Open = open,
NotDispatched = notDispatched,
Completed = completed
};
}
}
}

View file

@ -0,0 +1,71 @@
using Data.SeaHavenIndustries;
using Microsoft.EntityFrameworkCore;
using SeaHaven.DataServices.Interfaces;
namespace SeaHaven.DataServices.Implementation
{
public class DropdownOptionsDataService : IDropdownOptionsDataService
{
private readonly ApplicationDbContext _context;
public DropdownOptionsDataService(ApplicationDbContext context)
{
_context = context;
}
public async Task<IEnumerable<DropdownOption>> GetAllActiveAsync(string? category, CancellationToken cancellationToken)
{
var query = _context.DropdownOptions.Where(d => d.IsActive);
if (!string.IsNullOrWhiteSpace(category))
query = query.Where(d => d.Category == category);
return await query
.AsNoTracking()
.OrderBy(d => d.Category).ThenBy(d => d.SortOrder).ThenBy(d => d.Value)
.ToListAsync(cancellationToken);
}
public async Task<IEnumerable<DropdownOption>> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken)
{
var query = _context.DropdownOptions.Where(d => d.IsActive && d.Category == category);
if (!string.IsNullOrWhiteSpace(parent))
query = query.Where(d => d.ParentValue == parent);
return await query
.AsNoTracking()
.OrderBy(d => d.SortOrder).ThenBy(d => d.Value)
.ToListAsync(cancellationToken);
}
public async Task<bool> ExistsAsync(string category, string value, string? parentValue, CancellationToken cancellationToken)
{
return await _context.DropdownOptions
.AsNoTracking()
.AnyAsync(d => d.Category == category && d.Value == value && d.ParentValue == parentValue, cancellationToken);
}
public async Task<DropdownOption> AddAsync(DropdownOption option, CancellationToken cancellationToken)
{
await _context.DropdownOptions.AddAsync(option, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return option;
}
public async Task<DropdownOption?> GetByIdAsync(int id, CancellationToken cancellationToken)
{
return await _context.DropdownOptions.FindAsync(new object[] { id }, cancellationToken);
}
public async Task UpdateAsync(DropdownOption option, CancellationToken cancellationToken)
{
_context.DropdownOptions.Update(option);
await _context.SaveChangesAsync(cancellationToken);
}
public async Task RemoveAsync(DropdownOption option, CancellationToken cancellationToken)
{
_context.DropdownOptions.Remove(option);
await _context.SaveChangesAsync(cancellationToken);
}
}
}

View file

@ -57,5 +57,58 @@ namespace SeaHaven.DataServices.Implementation
{ {
return await _context.TaskListTemplates.AnyAsync(t => t.Id == id); return await _context.TaskListTemplates.AnyAsync(t => t.Id == id);
} }
public async Task<IEnumerable<TaskListTemplate>> GetActiveTemplatesWithItemsAsync(CancellationToken cancellationToken)
{
return await _context.TaskListTemplates
.Include(t => t.Items)
.Where(t => t.IsActive)
.OrderBy(t => t.Name)
.AsNoTracking()
.ToListAsync(cancellationToken);
}
public async Task<TaskListTemplate?> GetTemplateWithItemsReadOnlyAsync(int id, CancellationToken cancellationToken)
{
return await _context.TaskListTemplates
.Include(t => t.Items)
.Where(t => t.Id == id)
.AsNoTracking()
.FirstOrDefaultAsync(cancellationToken);
}
public async Task<TaskListTemplate?> GetTemplateWithItemsForUpdateAsync(int id, CancellationToken cancellationToken)
{
return await _context.TaskListTemplates
.Include(t => t.Items)
.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);
}
public async Task<TaskListTemplate> AddTemplateAsync(TaskListTemplate template, CancellationToken cancellationToken)
{
await _context.TaskListTemplates.AddAsync(template, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
return template;
}
public async Task ReplaceTemplateItemsAsync(TaskListTemplate template, IEnumerable<TaskListTemplateItem> newItems, CancellationToken cancellationToken)
{
if (template.Items != null)
_context.TaskListTemplateItems.RemoveRange(template.Items);
template.Items = newItems.ToList();
await _context.SaveChangesAsync(cancellationToken);
}
public async Task<bool> DeactivateTemplateAsync(int id, CancellationToken cancellationToken)
{
var template = await _context.TaskListTemplates.FindAsync(new object[] { id }, cancellationToken);
if (template == null)
return false;
template.IsActive = false;
await _context.SaveChangesAsync(cancellationToken);
return true;
}
} }
} }

View file

@ -0,0 +1,14 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface ICalendarDataService
{
Task<Events?> GetEventByIdAsync(int id, CancellationToken cancellationToken);
Task<Events?> GetEventForEditAsync(int id, CancellationToken cancellationToken);
Task<Events> AddEventAsync(Events ev, CancellationToken cancellationToken);
Task UpdateEventAsync(Events ev, CancellationToken cancellationToken);
Task<(IEnumerable<Events> Items, int TotalCount)> GetEventListPagedAsync(int page, int pageSize, string? search, CancellationToken cancellationToken);
Task<IEnumerable<Events>> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,13 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface ICommonDataService
{
Task<(IEnumerable<ApplicationUser> Items, int TotalCount)> GetUsersPagedAsync(int page, int pageSize, string search, CancellationToken cancellationToken);
Task<IEnumerable<string?>> GetRoleNamesAsync(CancellationToken cancellationToken);
Task<IEnumerable<Locations>> GetSiteNamesAsync(CancellationToken cancellationToken);
Task<IEnumerable<Contacts>> GetContactsAsync(CancellationToken cancellationToken);
Task<IEnumerable<Category>> GetCategoriesAsync(CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,15 @@
namespace SeaHaven.DataServices.Interfaces
{
public interface IDashboardDataService
{
Task<DashboardCounts> GetWorkOrderCountsAsync(CancellationToken cancellationToken);
}
public class DashboardCounts
{
public int Total { get; set; }
public int Open { get; set; }
public int NotDispatched { get; set; }
public int Completed { get; set; }
}
}

View file

@ -0,0 +1,15 @@
using Data.SeaHavenIndustries;
namespace SeaHaven.DataServices.Interfaces
{
public interface IDropdownOptionsDataService
{
Task<IEnumerable<DropdownOption>> GetAllActiveAsync(string? category, CancellationToken cancellationToken);
Task<IEnumerable<DropdownOption>> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken);
Task<bool> ExistsAsync(string category, string value, string? parentValue, CancellationToken cancellationToken);
Task<DropdownOption> AddAsync(DropdownOption option, CancellationToken cancellationToken);
Task<DropdownOption?> GetByIdAsync(int id, CancellationToken cancellationToken);
Task UpdateAsync(DropdownOption option, CancellationToken cancellationToken);
Task RemoveAsync(DropdownOption option, CancellationToken cancellationToken);
}
}

View file

@ -11,5 +11,13 @@ namespace SeaHaven.DataServices.Interfaces
Task UpdateAsync(TaskListTemplate template); Task UpdateAsync(TaskListTemplate template);
Task DeleteAsync(int id); Task DeleteAsync(int id);
Task<bool> ExistsAsync(int id); Task<bool> ExistsAsync(int id);
// Cancelable query/command operations used by the TaskListTemplate service layer
Task<IEnumerable<TaskListTemplate>> GetActiveTemplatesWithItemsAsync(CancellationToken cancellationToken);
Task<TaskListTemplate?> GetTemplateWithItemsReadOnlyAsync(int id, CancellationToken cancellationToken);
Task<TaskListTemplate?> GetTemplateWithItemsForUpdateAsync(int id, CancellationToken cancellationToken);
Task<TaskListTemplate> AddTemplateAsync(TaskListTemplate template, CancellationToken cancellationToken);
Task ReplaceTemplateItemsAsync(TaskListTemplate template, IEnumerable<TaskListTemplateItem> newItems, CancellationToken cancellationToken);
Task<bool> DeactivateTemplateAsync(int id, CancellationToken cancellationToken);
} }
} }

View file

@ -0,0 +1,50 @@
namespace SeaHaven.Services.DTOs
{
public class EventDTO
{
public int Id { get; set; }
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Location { get; set; }
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
public int ReminderMinutes { get; set; }
public string EventColor { get; set; } = null!;
public DateTime StartDate { get; set; }
public string? StartTime { get; set; }
public DateTime EndDate { get; set; }
public string? EndTime { get; set; }
public bool AllDay { get; set; }
public DateTime? CreatedDate { get; set; }
public string? createdby { get; set; }
}
public class PagedEventsResult
{
public List<EventDTO> Rows { get; set; } = new();
public int TotalCount { get; set; }
}
public class EditEventInput
{
public int? Id { get; set; }
public string Title { get; set; } = null!;
public string? Description { get; set; }
public string? Location { get; set; }
public int? RecordID { get; set; }
public string? RecordName { get; set; }
public string? RecordTargetType { get; set; }
public int ReminderMinutes { get; set; }
public string EventColor { get; set; } = null!;
public DateTime StartDate { get; set; }
public string? StartTime { get; set; }
public DateTime EndDate { get; set; }
public string? EndTime { get; set; }
public bool AllDay { get; set; }
}
}

View file

@ -0,0 +1,48 @@
namespace SeaHaven.Services.DTOs
{
public class UserListItemDTO
{
public string? Id { get; set; }
public string? UserName { get; set; }
public string? Email { get; set; }
public string? RoleName { get; set; }
public string? Status { get; set; }
public string? Date { get; set; }
}
public class PagedUsersResult
{
public List<UserListItemDTO> Users { get; set; } = new();
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public int TotalPages { get; set; }
}
public class RoleListItemDTO
{
public string? Id { get; set; }
public string? Name { get; set; }
}
public class SiteNameDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Title { get; set; }
}
public class ContactListItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Phone { get; set; }
}
public class CategoryListItemDTO
{
public int Id { get; set; }
public string? Name { get; set; }
}
}

View file

@ -0,0 +1,10 @@
namespace SeaHaven.Services.DTOs
{
public class DashboardStatsDTO
{
public int Total { get; set; }
public int Open { get; set; }
public int NotDispatched { get; set; }
public int Completed { get; set; }
}
}

View file

@ -0,0 +1,12 @@
namespace SeaHaven.Services.DTOs
{
public class DropdownOptionDTO
{
public int Id { get; set; }
public string Category { get; set; } = "";
public string Value { get; set; } = "";
public string? ParentValue { get; set; }
public int SortOrder { get; set; }
public bool IsActive { get; set; } = true;
}
}

View file

@ -0,0 +1,32 @@
namespace SeaHaven.Services.DTOs
{
public class TaskListTemplateDTO
{
public int Id { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; }
public List<TaskListTemplateItemDTO> Items { get; set; } = new();
}
public class TaskListTemplateItemDTO
{
public int Id { get; set; }
public string? ItemText { get; set; }
public int SortOrder { get; set; }
}
public class TaskListTemplateInput
{
public string? Name { get; set; }
public string? Description { get; set; }
public bool IsActive { get; set; } = true;
public List<TaskListTemplateInputItem> Items { get; set; } = new();
}
public class TaskListTemplateInputItem
{
public string? ItemText { get; set; }
public int SortOrder { get; set; }
}
}

View file

@ -0,0 +1,125 @@
using Data.SeaHavenIndustries;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class CalendarService : ICalendarService
{
private readonly ICalendarDataService _dataService;
public CalendarService(ICalendarDataService dataService)
{
_dataService = dataService;
}
public async Task CreateEventAsync(EditEventInput input, CancellationToken cancellationToken)
{
var model = new Events
{
Title = input.Title,
Description = input.Description,
Location = input.Location,
RecordID = input.RecordID,
RecordName = input.RecordName,
RecordTargetType = input.RecordTargetType,
ReminderMinutes = input.ReminderMinutes,
EventColor = input.EventColor,
StartDate = input.StartDate,
StartTime = input.StartTime,
EndDate = input.EndDate,
EndTime = input.EndTime,
AllDay = input.AllDay,
CreatedDate = DateTime.Now,
IsDeleted = false
};
await _dataService.AddEventAsync(model, cancellationToken);
}
public async Task<bool> UpdateEventAsync(EditEventInput input, CancellationToken cancellationToken)
{
var model = await _dataService.GetEventForEditAsync(input.Id!.Value, cancellationToken);
if (model == null || model.IsDeleted == true)
return false;
model.Title = input.Title;
model.Description = input.Description;
model.Location = input.Location;
model.RecordID = input.RecordID;
model.RecordName = input.RecordName;
model.RecordTargetType = input.RecordTargetType;
model.ReminderMinutes = input.ReminderMinutes;
model.EventColor = input.EventColor;
model.StartDate = input.StartDate;
model.StartTime = input.StartTime;
model.EndDate = input.EndDate;
model.EndTime = input.EndTime;
model.AllDay = input.AllDay;
model.LastModificationTime = DateTime.Now;
await _dataService.UpdateEventAsync(model, cancellationToken);
return true;
}
public async Task<EventDTO?> GetEventByIdAsync(int id, CancellationToken cancellationToken)
{
var model = await _dataService.GetEventByIdAsync(id, cancellationToken);
return model == null ? null : MapToDTO(model);
}
public async Task<bool> DeleteEventAsync(int id, CancellationToken cancellationToken)
{
var model = await _dataService.GetEventForEditAsync(id, cancellationToken);
if (model == null)
return false;
model.IsDeleted = true;
model.DeletionTime = DateTime.Now;
await _dataService.UpdateEventAsync(model, cancellationToken);
return true;
}
public async Task<PagedEventsResult> GetEventListAsync(string? search, int page, int pageSize, CancellationToken cancellationToken)
{
var (items, totalCount) = await _dataService.GetEventListPagedAsync(page, pageSize, search, cancellationToken);
return new PagedEventsResult
{
Rows = items.Select(MapToDTO).ToList(),
TotalCount = totalCount
};
}
public async Task<IEnumerable<EventDTO>> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken)
{
var events = await _dataService.GetEventsForCalendarAsync(start, end, cancellationToken);
return events.Select(MapToDTO);
}
private static EventDTO MapToDTO(Events model)
{
return new EventDTO
{
Id = model.Id,
Title = model.Title,
Description = model.Description,
Location = model.Location,
RecordID = model.RecordID,
RecordName = model.RecordName,
RecordTargetType = model.RecordTargetType,
ReminderMinutes = model.ReminderMinutes,
EventColor = model.EventColor,
StartDate = model.StartDate,
StartTime = model.StartTime,
EndDate = model.EndDate,
EndTime = model.EndTime,
AllDay = model.AllDay,
CreatedDate = model.CreatedDate,
createdby = model.createdby
};
}
}
}

View file

@ -0,0 +1,85 @@
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Identity;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class CommonService : ICommonService
{
private readonly ICommonDataService _dataService;
private readonly UserManager<ApplicationUser> _userManager;
public CommonService(ICommonDataService dataService, UserManager<ApplicationUser> userManager)
{
_dataService = dataService;
_userManager = userManager;
}
public async Task<PagedUsersResult> GetUsersPagedAsync(string? search, int page, int pageSize, CancellationToken cancellationToken)
{
var (paginatedUsers, totalCount) = await _dataService.GetUsersPagedAsync(page, pageSize, search ?? "", cancellationToken);
var filteredUsers = new List<UserListItemDTO>();
foreach (var user in paginatedUsers)
{
var roles = await _userManager.GetRolesAsync(user);
if (!roles.Contains("Admin"))
{
filteredUsers.Add(new UserListItemDTO
{
RoleName = roles.Count() > 0 ? roles.First() : "",
Email = user.Email,
UserName = user.FirstName,
Date = user.CreatedDate?.Date.ToString(),
Status = user.UniqueName,
Id = user.Id
});
}
}
var totalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
return new PagedUsersResult
{
Users = filteredUsers,
PageNumber = page,
PageSize = pageSize,
TotalCount = totalCount,
TotalPages = totalPages
};
}
public async Task<IEnumerable<RoleListItemDTO>> GetRolesAsync(CancellationToken cancellationToken)
{
var names = await _dataService.GetRoleNamesAsync(cancellationToken);
return names.Select(name => new RoleListItemDTO { Id = name, Name = name });
}
public async Task<IEnumerable<SiteNameDTO>> GetSiteNamesAsync(CancellationToken cancellationToken)
{
var sites = await _dataService.GetSiteNamesAsync(cancellationToken);
return sites.Select(n => new SiteNameDTO { Id = n.Id, Name = n.Name, Title = n.Title });
}
public async Task<IEnumerable<ContactListItemDTO>> GetContactsAsync(CancellationToken cancellationToken)
{
var contacts = await _dataService.GetContactsAsync(cancellationToken);
return contacts.Select(n => new ContactListItemDTO
{
Id = n.Id,
Name = (n.FirstName + " " + n.LastName).Trim(),
Email = n.Email,
Phone = n.PhoneNumber
});
}
public async Task<IEnumerable<CategoryListItemDTO>> GetCategoriesAsync(CancellationToken cancellationToken)
{
var categories = await _dataService.GetCategoriesAsync(cancellationToken);
return categories.Select(s => new CategoryListItemDTO { Id = s.Id, Name = s.Name });
}
}
}

View file

@ -0,0 +1,29 @@
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class DashboardService : IDashboardService
{
private readonly IDashboardDataService _dataService;
public DashboardService(IDashboardDataService dataService)
{
_dataService = dataService;
}
public async Task<DashboardStatsDTO> GetStatsAsync(CancellationToken cancellationToken)
{
var counts = await _dataService.GetWorkOrderCountsAsync(cancellationToken);
return new DashboardStatsDTO
{
Total = counts.Total,
Open = counts.Open,
NotDispatched = counts.NotDispatched,
Completed = counts.Completed
};
}
}
}

View file

@ -0,0 +1,88 @@
using Data.SeaHavenIndustries;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class DropdownOptionsService : IDropdownOptionsService
{
private readonly IDropdownOptionsDataService _dataService;
public DropdownOptionsService(IDropdownOptionsDataService dataService)
{
_dataService = dataService;
}
public async Task<IEnumerable<DropdownOptionDTO>> GetAllAsync(string? category, CancellationToken cancellationToken)
{
var data = await _dataService.GetAllActiveAsync(category, cancellationToken);
return data.Select(MapToDTO);
}
public async Task<IEnumerable<DropdownOptionDTO>> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken)
{
var data = await _dataService.GetByCategoryAsync(category, parent, cancellationToken);
return data.Select(MapToDTO);
}
public async Task<DropdownOptionDTO> CreateAsync(DropdownOptionDTO option, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(option.Category) || string.IsNullOrWhiteSpace(option.Value))
throw new InvalidOperationException("Category and Value are required");
if (await _dataService.ExistsAsync(option.Category, option.Value, option.ParentValue, cancellationToken))
throw new InvalidOperationException("This option already exists");
var entity = new DropdownOption
{
Id = 0,
Category = option.Category,
Value = option.Value,
ParentValue = option.ParentValue,
SortOrder = option.SortOrder,
IsActive = true
};
var created = await _dataService.AddAsync(entity, cancellationToken);
return MapToDTO(created);
}
public async Task<DropdownOptionDTO> UpdateAsync(int id, DropdownOptionDTO option, CancellationToken cancellationToken)
{
var item = await _dataService.GetByIdAsync(id, cancellationToken);
if (item == null)
throw new KeyNotFoundException("Option not found");
item.Value = option.Value;
item.ParentValue = option.ParentValue;
item.SortOrder = option.SortOrder;
item.IsActive = option.IsActive;
await _dataService.UpdateAsync(item, cancellationToken);
return MapToDTO(item);
}
public async Task DeleteAsync(int id, CancellationToken cancellationToken)
{
var item = await _dataService.GetByIdAsync(id, cancellationToken);
if (item == null)
throw new KeyNotFoundException("Option not found");
await _dataService.RemoveAsync(item, cancellationToken);
}
private static DropdownOptionDTO MapToDTO(DropdownOption option)
{
return new DropdownOptionDTO
{
Id = option.Id,
Category = option.Category,
Value = option.Value,
ParentValue = option.ParentValue,
SortOrder = option.SortOrder,
IsActive = option.IsActive
};
}
}
}

View file

@ -0,0 +1,92 @@
using Data.SeaHavenIndustries;
using SeaHaven.DataServices.Interfaces;
using SeaHaven.Services.DTOs;
using SeaHaven.Services.Interfaces;
namespace SeaHaven.Services.Implementation
{
public class TaskListTemplateService : ITaskListTemplateService
{
private readonly ITaskListTemplateDataService _dataService;
public TaskListTemplateService(ITaskListTemplateDataService dataService)
{
_dataService = dataService;
}
public async Task<IEnumerable<TaskListTemplateDTO>> GetAllAsync(CancellationToken cancellationToken)
{
var templates = await _dataService.GetActiveTemplatesWithItemsAsync(cancellationToken);
return templates.Select(MapToDTO);
}
public async Task<TaskListTemplateDTO?> GetByIdAsync(int id, CancellationToken cancellationToken)
{
var template = await _dataService.GetTemplateWithItemsReadOnlyAsync(id, cancellationToken);
return template == null ? null : MapToDTO(template);
}
public async Task<int> CreateAsync(TaskListTemplateInput input, CancellationToken cancellationToken)
{
var template = new TaskListTemplate
{
Name = input.Name,
Description = input.Description,
IsActive = input.IsActive,
Items = input.Items.Select((item, idx) => new TaskListTemplateItem
{
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList()
};
var created = await _dataService.AddTemplateAsync(template, cancellationToken);
return created.Id;
}
public async Task<bool> UpdateAsync(int id, TaskListTemplateInput input, CancellationToken cancellationToken)
{
var template = await _dataService.GetTemplateWithItemsForUpdateAsync(id, cancellationToken);
if (template == null)
return false;
template.Name = input.Name;
template.Description = input.Description;
template.IsActive = input.IsActive;
var newItems = input.Items.Select((item, idx) => new TaskListTemplateItem
{
TaskListTemplateId = id,
ItemText = item.ItemText,
SortOrder = item.SortOrder > 0 ? item.SortOrder : idx
}).ToList();
await _dataService.ReplaceTemplateItemsAsync(template, newItems, cancellationToken);
return true;
}
public async Task<bool> DeleteAsync(int id, CancellationToken cancellationToken)
{
return await _dataService.DeactivateTemplateAsync(id, cancellationToken);
}
private static TaskListTemplateDTO MapToDTO(TaskListTemplate template)
{
return new TaskListTemplateDTO
{
Id = template.Id,
Name = template.Name,
Description = template.Description,
IsActive = template.IsActive,
Items = (template.Items ?? new List<TaskListTemplateItem>())
.OrderBy(i => i.SortOrder)
.Select(i => new TaskListTemplateItemDTO
{
Id = i.Id,
ItemText = i.ItemText,
SortOrder = i.SortOrder
}).ToList()
};
}
}
}

View file

@ -0,0 +1,14 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface ICalendarService
{
Task CreateEventAsync(EditEventInput input, CancellationToken cancellationToken);
Task<bool> UpdateEventAsync(EditEventInput input, CancellationToken cancellationToken);
Task<EventDTO?> GetEventByIdAsync(int id, CancellationToken cancellationToken);
Task<bool> DeleteEventAsync(int id, CancellationToken cancellationToken);
Task<PagedEventsResult> GetEventListAsync(string? search, int page, int pageSize, CancellationToken cancellationToken);
Task<IEnumerable<EventDTO>> GetEventsForCalendarAsync(DateTime? start, DateTime? end, CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,13 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface ICommonService
{
Task<PagedUsersResult> GetUsersPagedAsync(string? search, int page, int pageSize, CancellationToken cancellationToken);
Task<IEnumerable<RoleListItemDTO>> GetRolesAsync(CancellationToken cancellationToken);
Task<IEnumerable<SiteNameDTO>> GetSiteNamesAsync(CancellationToken cancellationToken);
Task<IEnumerable<ContactListItemDTO>> GetContactsAsync(CancellationToken cancellationToken);
Task<IEnumerable<CategoryListItemDTO>> GetCategoriesAsync(CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,9 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IDashboardService
{
Task<DashboardStatsDTO> GetStatsAsync(CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,13 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface IDropdownOptionsService
{
Task<IEnumerable<DropdownOptionDTO>> GetAllAsync(string? category, CancellationToken cancellationToken);
Task<IEnumerable<DropdownOptionDTO>> GetByCategoryAsync(string category, string? parent, CancellationToken cancellationToken);
Task<DropdownOptionDTO> CreateAsync(DropdownOptionDTO option, CancellationToken cancellationToken);
Task<DropdownOptionDTO> UpdateAsync(int id, DropdownOptionDTO option, CancellationToken cancellationToken);
Task DeleteAsync(int id, CancellationToken cancellationToken);
}
}

View file

@ -0,0 +1,13 @@
using SeaHaven.Services.DTOs;
namespace SeaHaven.Services.Interfaces
{
public interface ITaskListTemplateService
{
Task<IEnumerable<TaskListTemplateDTO>> GetAllAsync(CancellationToken cancellationToken);
Task<TaskListTemplateDTO?> GetByIdAsync(int id, CancellationToken cancellationToken);
Task<int> CreateAsync(TaskListTemplateInput input, CancellationToken cancellationToken);
Task<bool> UpdateAsync(int id, TaskListTemplateInput input, CancellationToken cancellationToken);
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken);
}
}

View file

@ -16,4 +16,8 @@
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" /> <PackageReference Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
</Project> </Project>