v1.0.0: DevicesController — Kitten CRUD + Playlist management
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NoticeBoard.Data;
|
||||
using NoticeBoard.Models;
|
||||
|
||||
namespace NoticeBoard.Controllers;
|
||||
|
||||
public class DevicesController : Controller
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public DevicesController(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
var devices = await _db.Devices
|
||||
.Include(d => d.DeviceSlides)
|
||||
.OrderBy(d => d.Name)
|
||||
.ToListAsync();
|
||||
return View(devices);
|
||||
}
|
||||
|
||||
public IActionResult Create()
|
||||
{
|
||||
return View(new Device());
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Create(Device device)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(device.Slug))
|
||||
device.Slug = device.Name.ToLower().Replace(" ", "").Replace("-", "");
|
||||
|
||||
device.Slug = device.Slug.ToLower().Trim();
|
||||
|
||||
if (await _db.Devices.AnyAsync(d => d.Slug == device.Slug))
|
||||
{
|
||||
ModelState.AddModelError("Slug", "This slug is already in use.");
|
||||
return View(device);
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid) return View(device);
|
||||
|
||||
device.CreatedAt = DateTime.UtcNow;
|
||||
_db.Devices.Add(device);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
TempData["Success"] = $"Device '{device.Name}' created. URL: /{device.Slug}";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Edit(int id)
|
||||
{
|
||||
var device = await _db.Devices.FindAsync(id);
|
||||
if (device == null) return NotFound();
|
||||
return View(device);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Edit(int id, Device device)
|
||||
{
|
||||
if (id != device.Id) return NotFound();
|
||||
|
||||
device.Slug = device.Slug.ToLower().Trim();
|
||||
|
||||
if (await _db.Devices.AnyAsync(d => d.Slug == device.Slug && d.Id != id))
|
||||
{
|
||||
ModelState.AddModelError("Slug", "This slug is already in use.");
|
||||
return View(device);
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid) return View(device);
|
||||
|
||||
var existing = await _db.Devices.FindAsync(id);
|
||||
if (existing == null) return NotFound();
|
||||
|
||||
existing.Name = device.Name;
|
||||
existing.Slug = device.Slug;
|
||||
existing.ResolutionWidth = device.ResolutionWidth;
|
||||
existing.ResolutionHeight = device.ResolutionHeight;
|
||||
existing.Transition = device.Transition;
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
TempData["Success"] = $"Device '{device.Name}' updated.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var device = await _db.Devices
|
||||
.Include(d => d.DeviceSlides)
|
||||
.FirstOrDefaultAsync(d => d.Id == id);
|
||||
if (device == null) return NotFound();
|
||||
return View(device);
|
||||
}
|
||||
|
||||
[HttpPost, ActionName("Delete")]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> DeleteConfirmed(int id)
|
||||
{
|
||||
var device = await _db.Devices.FindAsync(id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
_db.Devices.Remove(device);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
TempData["Success"] = $"Device '{device.Name}' deleted.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
||||
// === Playlist Management ===
|
||||
|
||||
public async Task<IActionResult> Playlist(int id)
|
||||
{
|
||||
var device = await _db.Devices
|
||||
.Include(d => d.DeviceSlides.OrderBy(ds => ds.DisplayOrder))
|
||||
.ThenInclude(ds => ds.Slide)
|
||||
.FirstOrDefaultAsync(d => d.Id == id);
|
||||
|
||||
if (device == null) return NotFound();
|
||||
|
||||
var assignedSlideIds = device.DeviceSlides.Select(ds => ds.SlideId).ToHashSet();
|
||||
ViewBag.AvailableSlides = await _db.Slides
|
||||
.Where(s => !assignedSlideIds.Contains(s.Id))
|
||||
.OrderBy(s => s.Name)
|
||||
.ToListAsync();
|
||||
|
||||
return View(device);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> AddToPlaylist(int id, int slideId, int durationSeconds = 30)
|
||||
{
|
||||
var device = await _db.Devices.Include(d => d.DeviceSlides).FirstOrDefaultAsync(d => d.Id == id);
|
||||
if (device == null) return NotFound();
|
||||
|
||||
var maxOrder = device.DeviceSlides.Any() ? device.DeviceSlides.Max(ds => ds.DisplayOrder) : 0;
|
||||
|
||||
var ds = new DeviceSlide
|
||||
{
|
||||
DeviceId = id,
|
||||
SlideId = slideId,
|
||||
DisplayOrder = maxOrder + 1,
|
||||
DurationSeconds = durationSeconds,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
_db.DeviceSlides.Add(ds);
|
||||
await _db.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Playlist), new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> RemoveFromPlaylist(int id, int deviceSlideId)
|
||||
{
|
||||
var ds = await _db.DeviceSlides.FindAsync(deviceSlideId);
|
||||
if (ds == null) return NotFound();
|
||||
|
||||
_db.DeviceSlides.Remove(ds);
|
||||
await _db.SaveChangesAsync();
|
||||
return RedirectToAction(nameof(Playlist), new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> UpdatePlaylist(int id, int[] slideIds, int[] durations, bool[] enabled)
|
||||
{
|
||||
var deviceSlides = await _db.DeviceSlides
|
||||
.Where(ds => ds.DeviceId == id)
|
||||
.ToListAsync();
|
||||
|
||||
for (int i = 0; i < slideIds.Length; i++)
|
||||
{
|
||||
var ds = deviceSlides.FirstOrDefault(x => x.Id == slideIds[i]);
|
||||
if (ds != null)
|
||||
{
|
||||
ds.DisplayOrder = i + 1;
|
||||
if (i < durations.Length) ds.DurationSeconds = durations[i];
|
||||
ds.Enabled = i < enabled.Length && enabled[i];
|
||||
}
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
TempData["Success"] = "Playlist updated.";
|
||||
return RedirectToAction(nameof(Playlist), new { id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ReorderPlaylist([FromBody] ReorderRequest request)
|
||||
{
|
||||
if (request?.ItemIds == null) return BadRequest();
|
||||
|
||||
var deviceSlides = await _db.DeviceSlides
|
||||
.Where(ds => request.ItemIds.Contains(ds.Id))
|
||||
.ToListAsync();
|
||||
|
||||
for (int i = 0; i < request.ItemIds.Length; i++)
|
||||
{
|
||||
var ds = deviceSlides.FirstOrDefault(x => x.Id == request.ItemIds[i]);
|
||||
if (ds != null) ds.DisplayOrder = i + 1;
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> UpdateDuration([FromBody] UpdateDurationRequest request)
|
||||
{
|
||||
var ds = await _db.DeviceSlides.FindAsync(request.DeviceSlideId);
|
||||
if (ds == null) return NotFound();
|
||||
ds.DurationSeconds = request.DurationSeconds;
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> ToggleEnabled([FromBody] ToggleEnabledRequest request)
|
||||
{
|
||||
var ds = await _db.DeviceSlides.FindAsync(request.DeviceSlideId);
|
||||
if (ds == null) return NotFound();
|
||||
ds.Enabled = request.Enabled;
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
|
||||
public class ReorderRequest
|
||||
{
|
||||
public int[]? ItemIds { get; set; }
|
||||
}
|
||||
|
||||
public class UpdateDurationRequest
|
||||
{
|
||||
public int DeviceSlideId { get; set; }
|
||||
public int DurationSeconds { get; set; }
|
||||
}
|
||||
|
||||
public class ToggleEnabledRequest
|
||||
{
|
||||
public int DeviceSlideId { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user