2026-04-12 03:45:01 +02:00

124 lines
4.2 KiB
C#

using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using OnlyPrompt.Backend.ApiModels.Category;
using OnlyPrompt.Backend.Database;
using OnlyPrompt.Backend.Database.Models;
using OnlyPrompt.Backend.Utils;
using System.ComponentModel.DataAnnotations;
namespace OnlyPrompt.Backend.Controllers
{
[ApiController]
[Route("api/v1/categories")]
[Authorize(Roles = ModelConstants.AdminRole, AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class CategoryController : BaseController
{
private static ValidationProblem SlugExistsProblem = TypedResults.ValidationProblem(new Dictionary<string, string[]>
{
{ nameof(CategoryModel.Slug), new[] { "Slug already exists." } }
});
public CategoryController(OnlyPromptContext db, IMapper mapper) : base(db, mapper)
{
}
[HttpGet("minimal")]
[Authorize(Roles = ModelConstants.UserRole)]
public async Task<ApiMinimalCategory[]> GetMinimalCategoriesAsync()
{
var categories = await _db.Categories
.ProjectTo<ApiMinimalCategory>(_mapper.ConfigurationProvider)
.ToArrayAsync();
return categories;
}
[HttpGet]
[Authorize(Roles = ModelConstants.UserRole)]
public async Task<ApiCategory[]> GetCategoriesAsync([Range(0, double.MaxValue)][FromQuery] int offset = 0, [Range(1, 100)][FromQuery] int limit = 20)
{
var categories = await _db.Categories
.OrderBy(c => c.Id)
.Skip(offset)
.Take(limit)
.ProjectTo<ApiCategory>(_mapper.ConfigurationProvider)
.ToArrayAsync();
return categories;
}
[HttpPost]
public async Task<Results<Ok<ApiCategory>, ValidationProblem>> CreateCategoryAsync([FromBody] ApiCreateCategoryRequest request)
{
var exists = await _db.Categories.AnyAsync(c => c.Slug == request.Slug);
if (exists)
return SlugExistsProblem;
var model = _mapper.Map<CategoryModel>(request);
if (string.IsNullOrWhiteSpace(model.Slug))
model.Slug = await SlugHelper.GenerateUniqueSlugAsync(request.Name, slug => _db.Categories.AnyAsync(c => c.Slug == slug), ModelConstants.MaxSlugLength);
_db.Categories.Add(model);
await _db.SaveChangesAsync();
return TypedResults.Ok(_mapper.Map<ApiCategory>(model));
}
[HttpPut("{id}")]
public async Task<Results<Ok<ApiCategory>, NotFound<string>, ValidationProblem>> UpdateCategoryAsync(Identifier id, [FromBody] ApiUpdateCategoryRequest request)
{
var category = await _db.Categories.FindByIdentifierAsync(id);
if (category is null)
return TypedResults.NotFound("Category not found");
if (string.IsNullOrWhiteSpace(request.Name) == false)
category.Name = request.Name;
if(string.IsNullOrWhiteSpace(request.Description) == false)
category.Description = request.Description;
if (string.IsNullOrWhiteSpace(request.Slug) == false && request.Slug != category.Slug)
{
var exists = await _db.Categories.AnyAsync(c => c.Slug == request.Slug && c.Id != category.Id);
if (exists)
return SlugExistsProblem;
category.Slug = request.Slug;
}
await _db.SaveChangesAsync();
return TypedResults.Ok(_mapper.Map<ApiCategory>(category));
}
[HttpDelete("{id}")]
public async Task<Results<NoContent, BadRequest<string>, NotFound<string>>> DeleteCategoryAsync(Identifier id, [FromQuery] Identifier? replaceWith = null)
{
var hasPrompts = await _db.Prompts.AnyAsync(p => p.CategoryId == id.Id);
if (hasPrompts)
{
if (replaceWith.HasValue == false)
return TypedResults.BadRequest("Category has associated prompts. Provide a replacement category to reassign them to.");
var replacement = await _db.Categories.FindByIdentifierAsync(replaceWith.Value);
if(replacement is null)
return TypedResults.NotFound("Replacement category not found.");
await _db.Prompts.Where(p => p.CategoryId == id.Id)
.ExecuteUpdateAsync(p => p.SetProperty(p => p.CategoryId, replacement.Id));
}
var count = await _db.Categories.OfIdentifer(id)
.ExecuteDeleteAsync();
if (count == 0)
return TypedResults.NotFound("Category not found");
return TypedResults.NoContent();
}
}
}