using System.ComponentModel.DataAnnotations; using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Infrastructure.Persistence; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace Jiaowu.Api.Controllers; [ApiController] [Authorize(Roles = ReadRoles)] [Route("api/course-groups")] public sealed class CourseGroupsController(AppDbContext db) : ControllerBase { private const string ReadRoles = SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin + "," + SystemRoles.CollegeAdmin; private const string ManageRoles = SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin; [HttpGet] public async Task GetAll(CancellationToken cancellationToken) { var groups = await db.CourseGroups.AsNoTracking() .OrderBy(x => x.Code) .Select(x => new { x.Id, x.Code, x.Name, x.Description, CourseCount = x.Courses.Count, Courses = x.Courses.OrderBy(item => item.Course!.Code).Select(item => new { item.Id, item.CourseId, CourseCode = item.Course!.Code, CourseName = item.Course.Name, item.Course.Credits, item.Course.TotalHours, item.Course.Nature }) }) .ToListAsync(cancellationToken); return Ok(groups); } [HttpPost] [Authorize(Roles = ManageRoles)] public async Task Create( CourseGroupRequest request, CancellationToken cancellationToken) { var group = new CourseGroup { Code = request.Code.Trim(), Name = request.Name.Trim(), Description = Normalize(request.Description) }; db.CourseGroups.Add(group); return await SaveCreatedAsync(group.Id, cancellationToken); } [HttpPut("{id:guid}")] [Authorize(Roles = ManageRoles)] public async Task Update( Guid id, CourseGroupRequest request, CancellationToken cancellationToken) { var group = await db.CourseGroups.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); if (group is null) return NotFound(); group.Code = request.Code.Trim(); group.Name = request.Name.Trim(); group.Description = Normalize(request.Description); return await SaveNoContentAsync(cancellationToken); } [HttpDelete("{id:guid}")] [Authorize(Roles = ManageRoles)] public async Task Delete(Guid id, CancellationToken cancellationToken) { var group = await db.CourseGroups.FirstOrDefaultAsync(x => x.Id == id, cancellationToken); if (group is null) return NotFound(); db.CourseGroups.Remove(group); return await SaveNoContentAsync(cancellationToken); } [HttpPost("{id:guid}/courses")] [Authorize(Roles = ManageRoles)] public async Task AddCourse( Guid id, CourseGroupCourseRequest request, CancellationToken cancellationToken) { if (!await db.CourseGroups.AnyAsync(x => x.Id == id, cancellationToken)) return NotFound(); if (!await db.Courses.AnyAsync(x => x.Id == request.CourseId && x.IsEnabled, cancellationToken)) return ValidationProblem("所选课程不存在或已停用。"); db.CourseGroupCourses.Add(new CourseGroupCourse { CourseGroupId = id, CourseId = request.CourseId }); return await SaveCreatedAsync(id, cancellationToken); } [HttpDelete("{id:guid}/courses/{courseId:guid}")] [Authorize(Roles = ManageRoles)] public async Task RemoveCourse( Guid id, Guid courseId, CancellationToken cancellationToken) { var item = await db.CourseGroupCourses.FirstOrDefaultAsync( x => x.CourseGroupId == id && x.CourseId == courseId, cancellationToken); if (item is null) return NotFound(); db.CourseGroupCourses.Remove(item); return await SaveNoContentAsync(cancellationToken); } private async Task SaveCreatedAsync(Guid id, CancellationToken cancellationToken) { try { await db.SaveChangesAsync(cancellationToken); return Created(string.Empty, new { id }); } catch (DbUpdateException) { return ConflictProblem("课程组编码或组内课程重复。"); } } private async Task SaveNoContentAsync(CancellationToken cancellationToken) { try { await db.SaveChangesAsync(cancellationToken); return NoContent(); } catch (DbUpdateException) { return ConflictProblem("课程组编码或组内课程重复。"); } } private ActionResult ConflictProblem(string detail) => Conflict(new ProblemDetails { Title = "无法完成操作", Detail = detail, Status = StatusCodes.Status409Conflict }); private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value.Trim(); } public sealed record CourseGroupRequest( [Required, MaxLength(30)] string Code, [Required, MaxLength(100)] string Name, [MaxLength(500)] string? Description); public sealed record CourseGroupCourseRequest(Guid CourseId);