227 lines
6.6 KiB
C#
227 lines
6.6 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using DramaLing.Api.Data;
|
|
using DramaLing.Api.Models.Entities;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using System.Security.Claims;
|
|
|
|
namespace DramaLing.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
public class CardSetsController : ControllerBase
|
|
{
|
|
private readonly DramaLingDbContext _context;
|
|
|
|
public CardSetsController(DramaLingDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
private Guid GetUserId()
|
|
{
|
|
var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
|
|
User.FindFirst("sub")?.Value;
|
|
|
|
if (Guid.TryParse(userIdString, out var userId))
|
|
return userId;
|
|
|
|
throw new UnauthorizedAccessException("Invalid user ID");
|
|
}
|
|
|
|
[HttpGet]
|
|
public async Task<ActionResult> GetCardSets()
|
|
{
|
|
try
|
|
{
|
|
var userId = GetUserId();
|
|
|
|
var cardSets = await _context.CardSets
|
|
.Where(cs => cs.UserId == userId)
|
|
.OrderByDescending(cs => cs.CreatedAt)
|
|
.Select(cs => new
|
|
{
|
|
cs.Id,
|
|
cs.Name,
|
|
cs.Description,
|
|
cs.Color,
|
|
cs.CardCount,
|
|
cs.CreatedAt,
|
|
cs.UpdatedAt,
|
|
// 計算進度 (簡化版)
|
|
Progress = cs.CardCount > 0 ?
|
|
_context.Flashcards
|
|
.Where(f => f.CardSetId == cs.Id)
|
|
.Average(f => (double?)f.MasteryLevel) ?? 0 : 0,
|
|
LastStudied = cs.UpdatedAt,
|
|
Tags = new string[] { } // Phase 1 簡化
|
|
})
|
|
.ToListAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Data = new { Sets = cardSets }
|
|
});
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new { Success = false, Error = "Unauthorized" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
Success = false,
|
|
Error = "Failed to fetch card sets",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult> CreateCardSet([FromBody] CreateCardSetRequest request)
|
|
{
|
|
try
|
|
{
|
|
var userId = GetUserId();
|
|
|
|
if (string.IsNullOrWhiteSpace(request.Name))
|
|
return BadRequest(new { Success = false, Error = "Name is required" });
|
|
|
|
if (request.Name.Length > 255)
|
|
return BadRequest(new { Success = false, Error = "Name must be less than 255 characters" });
|
|
|
|
var cardSet = new CardSet
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
UserId = userId,
|
|
Name = request.Name.Trim(),
|
|
Description = request.Description?.Trim(),
|
|
Color = request.Color ?? "bg-blue-500"
|
|
};
|
|
|
|
_context.CardSets.Add(cardSet);
|
|
await _context.SaveChangesAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Data = cardSet,
|
|
Message = "Card set created successfully"
|
|
});
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new { Success = false, Error = "Unauthorized" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
Success = false,
|
|
Error = "Failed to create card set",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
|
|
[HttpPut("{id}")]
|
|
public async Task<ActionResult> UpdateCardSet(Guid id, [FromBody] UpdateCardSetRequest request)
|
|
{
|
|
try
|
|
{
|
|
var userId = GetUserId();
|
|
|
|
var cardSet = await _context.CardSets
|
|
.FirstOrDefaultAsync(cs => cs.Id == id && cs.UserId == userId);
|
|
|
|
if (cardSet == null)
|
|
return NotFound(new { Success = false, Error = "Card set not found" });
|
|
|
|
if (!string.IsNullOrEmpty(request.Name))
|
|
cardSet.Name = request.Name.Trim();
|
|
if (request.Description != null)
|
|
cardSet.Description = request.Description?.Trim();
|
|
if (!string.IsNullOrEmpty(request.Color))
|
|
cardSet.Color = request.Color;
|
|
|
|
cardSet.UpdatedAt = DateTime.UtcNow;
|
|
await _context.SaveChangesAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Data = cardSet,
|
|
Message = "Card set updated successfully"
|
|
});
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new { Success = false, Error = "Unauthorized" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
Success = false,
|
|
Error = "Failed to update card set",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
|
|
[HttpDelete("{id}")]
|
|
public async Task<ActionResult> DeleteCardSet(Guid id)
|
|
{
|
|
try
|
|
{
|
|
var userId = GetUserId();
|
|
|
|
var cardSet = await _context.CardSets
|
|
.Include(cs => cs.Flashcards)
|
|
.FirstOrDefaultAsync(cs => cs.Id == id && cs.UserId == userId);
|
|
|
|
if (cardSet == null)
|
|
return NotFound(new { Success = false, Error = "Card set not found" });
|
|
|
|
_context.CardSets.Remove(cardSet);
|
|
await _context.SaveChangesAsync();
|
|
|
|
return Ok(new
|
|
{
|
|
Success = true,
|
|
Message = "Card set deleted successfully"
|
|
});
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
return Unauthorized(new { Success = false, Error = "Unauthorized" });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return StatusCode(500, new
|
|
{
|
|
Success = false,
|
|
Error = "Failed to delete card set",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Request DTOs
|
|
public class CreateCardSetRequest
|
|
{
|
|
public string Name { get; set; } = string.Empty;
|
|
public string? Description { get; set; }
|
|
public string? Color { get; set; }
|
|
}
|
|
|
|
public class UpdateCardSetRequest
|
|
{
|
|
public string? Name { get; set; }
|
|
public string? Description { get; set; }
|
|
public string? Color { get; set; }
|
|
} |