shoc-backend/Api.SeaHavenIndustries/Controllers/CalendarController.cs
Adam Moussa 1f3972ae49
Add calendar/events backend API (#8)
* Align EntityFrameworkCore.SqlServer and Tools to 8.0.8

* Add calendar/events backend API

Cherry-picked from main-backup (19994ef); scratch notes file removed.

* Require authentication on CalendarController

Security review found [Authorize] commented out, leaving all 6 calendar
endpoints anonymous. Enforce auth to match the API convention (17/23
controllers).

* Add CalendarController unit tests (xUnit + EF InMemory)
2026-06-22 18:46:46 -04:00

306 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Api.SeaHavenIndustries.DTOs;
using Data.SeaHavenIndustries;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace Api.SeaHavenIndustries.Controllers
{
[Authorize]
[ApiController]
[Route("api/Calendar")]
public class CalendarController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly ApplicationDbContext _db;
public CalendarController(UserManager<ApplicationUser> userManager, ApplicationDbContext db)
{
_userManager = userManager;
_db = db;
}
// ===================================================================================
// CREATE EVENT
// ===================================================================================
[HttpPost]
[Route("AddEvent")]
public async Task<IActionResult> AddEvent(EditEvent_DTO eventDto)
{
try
{
var model = new Events
{
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" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// UPDATE EVENT
// ===================================================================================
[HttpPost]
[Route("EditEvent")]
public async Task<IActionResult> EditEvent(EditEvent_DTO eventDto)
{
try
{
if (!eventDto.Id.HasValue)
{
return BadRequest(new Response { Status = "Error", Message = "Event ID is required for editing" });
}
var model = await _db.Events.FindAsync(eventDto.Id.Value);
if (model == null || model.IsDeleted == true)
{
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" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENT BY ID
// ===================================================================================
[HttpGet]
[Route("GetEventById/{id}")]
public async Task<IActionResult> GetEventById(int id)
{
try
{
var model = await _db.Events
.Where(e => e.Id == id && e.IsDeleted != true)
.FirstOrDefaultAsync();
if (model == null)
{
return NotFound(new Response { Status = "Error", Message = "Event not found" });
}
var dto = new Event_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)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// DELETE EVENT
// ===================================================================================
[HttpDelete]
[Route("DeleteEvent/{id}")]
public async Task<IActionResult> DeleteEvent(int id)
{
try
{
var model = await _db.Events.FindAsync(id);
if (model == null)
{
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" });
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENTS LIST (WITH PAGINATION AND FILTERS)
// ===================================================================================
[HttpGet]
[Route("GetEventList")]
public IActionResult GetEventList(string? search = "", int page = 1, int pageSize = 10)
{
try
{
var query = _db.Events.Where(e => e.IsDeleted != true);
// Apply search filter if provided
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
{
Message = "Success",
Status = "200",
Data = new { rows = events, totalCount }
});
}
catch (Exception ex)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
// ===================================================================================
// GET EVENTS FOR CALENDAR (Date Range Filter)
// ===================================================================================
[HttpGet]
[Route("GetEventsForCalendar")]
public IActionResult GetEventsForCalendar([FromQuery] DateTime? start, [FromQuery] DateTime? end)
{
try
{
var query = _db.Events.Where(e => e.IsDeleted != true);
// Filter by date range if provided
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)
{
return BadRequest(new Response { Status = "Error", Message = ex.Message });
}
}
}
}