256 lines
8.4 KiB
C#
256 lines
8.4 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/flashcards-simple")]
|
||
[Authorize] // 恢復認證要求
|
||
public class SimplifiedFlashcardsController : ControllerBase
|
||
{
|
||
private readonly DramaLingDbContext _context;
|
||
private readonly ILogger<SimplifiedFlashcardsController> _logger;
|
||
|
||
public SimplifiedFlashcardsController(DramaLingDbContext context, ILogger<SimplifiedFlashcardsController> logger)
|
||
{
|
||
_context = context;
|
||
_logger = logger;
|
||
}
|
||
|
||
private Guid GetUserId()
|
||
{
|
||
var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
|
||
User.FindFirst("sub")?.Value;
|
||
|
||
if (Guid.TryParse(userIdString, out var userId))
|
||
return userId;
|
||
|
||
// 如果解析失敗,記錄錯誤並拋出異常
|
||
_logger.LogError("Failed to extract valid user ID from token. Claims: {Claims}",
|
||
string.Join(", ", User.Claims.Select(c => $"{c.Type}={c.Value}")));
|
||
|
||
throw new UnauthorizedAccessException("Invalid user ID in token");
|
||
}
|
||
|
||
[HttpGet]
|
||
public async Task<ActionResult> GetFlashcards(
|
||
[FromQuery] string? search = null,
|
||
[FromQuery] bool favoritesOnly = false)
|
||
{
|
||
try
|
||
{
|
||
var userId = GetUserId();
|
||
|
||
var query = _context.Flashcards
|
||
.Where(f => f.UserId == userId && !f.IsArchived)
|
||
.AsQueryable();
|
||
|
||
// 搜尋篩選
|
||
if (!string.IsNullOrEmpty(search))
|
||
{
|
||
query = query.Where(f =>
|
||
f.Word.Contains(search) ||
|
||
f.Translation.Contains(search) ||
|
||
(f.Definition != null && f.Definition.Contains(search)));
|
||
}
|
||
|
||
// 收藏篩選
|
||
if (favoritesOnly)
|
||
{
|
||
query = query.Where(f => f.IsFavorite);
|
||
}
|
||
|
||
var flashcards = await query
|
||
.OrderByDescending(f => f.CreatedAt)
|
||
.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.DifficultyLevel,
|
||
f.CreatedAt,
|
||
f.UpdatedAt
|
||
// 移除 CardSet 屬性
|
||
})
|
||
.ToListAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
Flashcards = flashcards,
|
||
Count = flashcards.Count
|
||
}
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error getting flashcards for user");
|
||
return StatusCode(500, new { Success = false, Error = "Failed to load flashcards" });
|
||
}
|
||
}
|
||
|
||
[HttpPost]
|
||
public async Task<ActionResult> CreateFlashcard([FromBody] CreateSimpleFlashcardRequest request)
|
||
{
|
||
try
|
||
{
|
||
var userId = GetUserId();
|
||
|
||
// 檢測重複詞卡
|
||
var existing = await _context.Flashcards
|
||
.FirstOrDefaultAsync(f => f.UserId == userId &&
|
||
f.Word.ToLower() == request.Word.ToLower() &&
|
||
!f.IsArchived);
|
||
|
||
if (existing != null)
|
||
{
|
||
return Ok(new
|
||
{
|
||
Success = false,
|
||
Error = "詞卡已存在",
|
||
IsDuplicate = true,
|
||
ExistingCard = new
|
||
{
|
||
existing.Id,
|
||
existing.Word,
|
||
existing.Translation,
|
||
existing.CreatedAt
|
||
}
|
||
});
|
||
}
|
||
|
||
var flashcard = new Flashcard
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
UserId = userId,
|
||
// 移除 CardSetId,設為 Guid.Empty 或 null
|
||
CardSetId = Guid.Empty,
|
||
Word = request.Word,
|
||
Translation = request.Translation,
|
||
Definition = request.Definition ?? "",
|
||
PartOfSpeech = request.PartOfSpeech,
|
||
Pronunciation = request.Pronunciation,
|
||
Example = request.Example,
|
||
ExampleTranslation = request.ExampleTranslation,
|
||
MasteryLevel = 0,
|
||
TimesReviewed = 0,
|
||
IsFavorite = false,
|
||
NextReviewDate = DateTime.Today,
|
||
DifficultyLevel = "A2", // 預設等級
|
||
EasinessFactor = 2.5f,
|
||
IntervalDays = 1,
|
||
CreatedAt = DateTime.UtcNow,
|
||
UpdatedAt = DateTime.UtcNow
|
||
};
|
||
|
||
_context.Flashcards.Add(flashcard);
|
||
await _context.SaveChangesAsync();
|
||
|
||
return Ok(new
|
||
{
|
||
Success = true,
|
||
Data = new
|
||
{
|
||
flashcard.Id,
|
||
flashcard.Word,
|
||
flashcard.Translation,
|
||
flashcard.Definition,
|
||
flashcard.CreatedAt
|
||
},
|
||
Message = "詞卡創建成功"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error creating flashcard");
|
||
return StatusCode(500, new { Success = false, Error = "Failed to create flashcard" });
|
||
}
|
||
}
|
||
|
||
[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 = "詞卡已刪除" });
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error deleting flashcard {FlashcardId}", id);
|
||
return StatusCode(500, new { Success = false, Error = "Failed to delete flashcard" });
|
||
}
|
||
}
|
||
|
||
[HttpPost("{id}/favorite")]
|
||
public async Task<ActionResult> ToggleFavorite(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" });
|
||
}
|
||
|
||
flashcard.IsFavorite = !flashcard.IsFavorite;
|
||
flashcard.UpdatedAt = DateTime.UtcNow;
|
||
|
||
await _context.SaveChangesAsync();
|
||
|
||
return Ok(new {
|
||
Success = true,
|
||
IsFavorite = flashcard.IsFavorite,
|
||
Message = flashcard.IsFavorite ? "已加入收藏" : "已取消收藏"
|
||
});
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Error toggling favorite for flashcard {FlashcardId}", id);
|
||
return StatusCode(500, new { Success = false, Error = "Failed to toggle favorite" });
|
||
}
|
||
}
|
||
}
|
||
|
||
// 簡化的請求 DTO,移除 CardSetId
|
||
public class CreateSimpleFlashcardRequest
|
||
{
|
||
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; } = string.Empty;
|
||
public string Pronunciation { get; set; } = string.Empty;
|
||
public string Example { get; set; } = string.Empty;
|
||
public string? ExampleTranslation { get; set; }
|
||
} |