dramaling-vocab-learning/backend/DramaLing.Api/Controllers/FlashcardsController.cs

350 lines
11 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 FlashcardsController : ControllerBase
{
private readonly DramaLingDbContext _context;
public FlashcardsController(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");
}
private async Task<Guid> GetOrCreateDefaultCardSetAsync(Guid userId)
{
// 嘗試找到預設卡組
var defaultCardSet = await _context.CardSets
.FirstOrDefaultAsync(cs => cs.UserId == userId && cs.IsDefault);
if (defaultCardSet != null)
return defaultCardSet.Id;
// 如果沒有預設卡組,創建一個
var newDefaultCardSet = new CardSet
{
Id = Guid.NewGuid(),
UserId = userId,
Name = "未分類",
Description = "系統預設卡組,用於存放尚未分類的詞卡",
Color = "bg-slate-700",
IsDefault = true
};
_context.CardSets.Add(newDefaultCardSet);
await _context.SaveChangesAsync();
return newDefaultCardSet.Id;
}
[HttpGet]
public async Task<ActionResult> GetFlashcards(
[FromQuery] Guid? setId,
[FromQuery] string? search,
[FromQuery] bool favoritesOnly = false,
[FromQuery] int limit = 50,
[FromQuery] int offset = 0)
{
try
{
var userId = GetUserId();
var query = _context.Flashcards
.Include(f => f.CardSet)
.Where(f => f.UserId == userId);
if (setId.HasValue)
query = query.Where(f => f.CardSetId == setId);
if (!string.IsNullOrEmpty(search))
query = query.Where(f => f.Word.Contains(search) || f.Translation.Contains(search));
if (favoritesOnly)
query = query.Where(f => f.IsFavorite);
var total = await query.CountAsync();
var flashcards = await query
.OrderByDescending(f => f.CreatedAt)
.Skip(offset)
.Take(Math.Min(limit, 100))
.Select(f => new
{
f.Id,
f.Word,
f.Translation,
f.Definition,
f.PartOfSpeech,
f.Pronunciation,
f.Example,
f.ExampleTranslation,
f.MasteryLevel,
f.TimesReviewed,
f.IsFavorite,
f.NextReviewDate,
f.CreatedAt,
CardSet = new
{
f.CardSet.Name,
f.CardSet.Color
}
})
.ToListAsync();
return Ok(new
{
Success = true,
Data = new
{
Flashcards = flashcards,
Total = total,
HasMore = offset + limit < total
}
});
}
catch (UnauthorizedAccessException)
{
return Unauthorized(new { Success = false, Error = "Unauthorized" });
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = "Failed to fetch flashcards",
Timestamp = DateTime.UtcNow
});
}
}
[HttpPost]
public async Task<ActionResult> CreateFlashcard([FromBody] CreateFlashcardRequest request)
{
try
{
var userId = GetUserId();
// 確定要使用的卡組ID
Guid cardSetId;
if (request.CardSetId.HasValue)
{
// 如果指定了卡組,驗證是否屬於用戶
var cardSet = await _context.CardSets
.FirstOrDefaultAsync(cs => cs.Id == request.CardSetId.Value && cs.UserId == userId);
if (cardSet == null)
return NotFound(new { Success = false, Error = "Card set not found" });
cardSetId = request.CardSetId.Value;
}
else
{
// 如果沒有指定卡組,使用或創建預設卡組
cardSetId = await GetOrCreateDefaultCardSetAsync(userId);
}
var flashcard = new Flashcard
{
Id = Guid.NewGuid(),
UserId = userId,
CardSetId = cardSetId,
Word = request.Word.Trim(),
Translation = request.Translation.Trim(),
Definition = request.Definition.Trim(),
PartOfSpeech = request.PartOfSpeech?.Trim(),
Pronunciation = request.Pronunciation?.Trim(),
Example = request.Example?.Trim(),
ExampleTranslation = request.ExampleTranslation?.Trim()
};
_context.Flashcards.Add(flashcard);
await _context.SaveChangesAsync();
return Ok(new
{
Success = true,
Data = flashcard,
Message = "Flashcard created successfully"
});
}
catch (UnauthorizedAccessException)
{
return Unauthorized(new { Success = false, Error = "Unauthorized" });
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = "Failed to create flashcard",
Timestamp = DateTime.UtcNow
});
}
}
[HttpGet("{id}")]
public async Task<ActionResult> GetFlashcard(Guid id)
{
try
{
var userId = GetUserId();
var flashcard = await _context.Flashcards
.Include(f => f.CardSet)
.FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId);
if (flashcard == null)
return NotFound(new { Success = false, Error = "Flashcard not found" });
return Ok(new { Success = true, Data = flashcard });
}
catch (UnauthorizedAccessException)
{
return Unauthorized(new { Success = false, Error = "Unauthorized" });
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = "Failed to fetch flashcard",
Timestamp = DateTime.UtcNow
});
}
}
[HttpPut("{id}")]
public async Task<ActionResult> UpdateFlashcard(Guid id, [FromBody] UpdateFlashcardRequest request)
{
try
{
var userId = GetUserId();
var flashcard = await _context.Flashcards
.FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId);
if (flashcard == null)
return NotFound(new { Success = false, Error = "Flashcard not found" });
// 更新欄位
if (!string.IsNullOrEmpty(request.Word))
flashcard.Word = request.Word.Trim();
if (!string.IsNullOrEmpty(request.Translation))
flashcard.Translation = request.Translation.Trim();
if (!string.IsNullOrEmpty(request.Definition))
flashcard.Definition = request.Definition.Trim();
if (request.PartOfSpeech != null)
flashcard.PartOfSpeech = request.PartOfSpeech?.Trim();
if (request.Pronunciation != null)
flashcard.Pronunciation = request.Pronunciation?.Trim();
if (request.Example != null)
flashcard.Example = request.Example?.Trim();
if (request.ExampleTranslation != null)
flashcard.ExampleTranslation = request.ExampleTranslation?.Trim();
if (request.IsFavorite.HasValue)
flashcard.IsFavorite = request.IsFavorite.Value;
flashcard.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return Ok(new
{
Success = true,
Data = flashcard,
Message = "Flashcard updated successfully"
});
}
catch (UnauthorizedAccessException)
{
return Unauthorized(new { Success = false, Error = "Unauthorized" });
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = "Failed to update flashcard",
Timestamp = DateTime.UtcNow
});
}
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteFlashcard(Guid id)
{
try
{
var userId = GetUserId();
var flashcard = await _context.Flashcards
.FirstOrDefaultAsync(f => f.Id == id && f.UserId == userId);
if (flashcard == null)
return NotFound(new { Success = false, Error = "Flashcard not found" });
_context.Flashcards.Remove(flashcard);
await _context.SaveChangesAsync();
return Ok(new
{
Success = true,
Message = "Flashcard deleted successfully"
});
}
catch (UnauthorizedAccessException)
{
return Unauthorized(new { Success = false, Error = "Unauthorized" });
}
catch (Exception ex)
{
return StatusCode(500, new
{
Success = false,
Error = "Failed to delete flashcard",
Timestamp = DateTime.UtcNow
});
}
}
}
// DTOs
public class CreateFlashcardRequest
{
public Guid? CardSetId { get; set; }
public string Word { get; set; } = string.Empty;
public string Translation { get; set; } = string.Empty;
public string Definition { get; set; } = string.Empty;
public string? PartOfSpeech { get; set; }
public string? Pronunciation { get; set; }
public string? Example { get; set; }
public string? ExampleTranslation { get; set; }
}
public class UpdateFlashcardRequest
{
public string? Word { get; set; }
public string? Translation { get; set; }
public string? Definition { get; set; }
public string? PartOfSpeech { get; set; }
public string? Pronunciation { get; set; }
public string? Example { get; set; }
public string? ExampleTranslation { get; set; }
public bool? IsFavorite { get; set; }
}