dramaling-vocab-learning/backend/DramaLing.Api/Controllers/SimplifiedFlashcardsControl...

372 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")]
[AllowAnonymous] // 暫時移除認證要求,修復網路錯誤
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()
{
// 暫時使用固定測試用戶 ID避免認證問題
// TODO: 恢復真實認證後改回 JWT Token 解析
return Guid.Parse("00000000-0000-0000-0000-000000000001");
// 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 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 testUser = await _context.Users.FirstOrDefaultAsync(u => u.Id == userId);
if (testUser == null)
{
testUser = new User
{
Id = userId,
Username = "testuser",
Email = "test@example.com",
PasswordHash = "test_hash",
DisplayName = "測試用戶",
SubscriptionType = "free",
Preferences = new Dictionary<string, object>(),
EnglishLevel = "A2",
LevelUpdatedAt = DateTime.UtcNow,
IsLevelVerified = false,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_context.Users.Add(testUser);
await _context.SaveChangesAsync();
}
// 檢測重複詞卡
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 = null, // 暫時不使用 CardSet
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" });
}
}
[HttpGet("{id}")]
public async Task<ActionResult> GetFlashcard(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" });
}
return Ok(new
{
Success = true,
Data = new
{
flashcard.Id,
flashcard.Word,
flashcard.Translation,
flashcard.Definition,
flashcard.PartOfSpeech,
flashcard.Pronunciation,
flashcard.Example,
flashcard.ExampleTranslation,
flashcard.MasteryLevel,
flashcard.TimesReviewed,
flashcard.IsFavorite,
flashcard.NextReviewDate,
flashcard.DifficultyLevel,
flashcard.CreatedAt,
flashcard.UpdatedAt
}
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error getting flashcard {FlashcardId}", id);
return StatusCode(500, new { Success = false, Error = "Failed to get flashcard" });
}
}
[HttpPut("{id}")]
public async Task<ActionResult> UpdateFlashcard(Guid id, [FromBody] CreateSimpleFlashcardRequest 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" });
}
// 更新詞卡資訊
flashcard.Word = request.Word;
flashcard.Translation = request.Translation;
flashcard.Definition = request.Definition ?? "";
flashcard.PartOfSpeech = request.PartOfSpeech;
flashcard.Pronunciation = request.Pronunciation;
flashcard.Example = request.Example;
flashcard.ExampleTranslation = request.ExampleTranslation;
flashcard.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
return Ok(new
{
Success = true,
Data = new
{
flashcard.Id,
flashcard.Word,
flashcard.Translation,
flashcard.Definition,
flashcard.CreatedAt,
flashcard.UpdatedAt
},
Message = "詞卡更新成功"
});
}
catch (Exception ex)
{
_logger.LogError(ex, "Error updating flashcard {FlashcardId}", id);
return StatusCode(500, new { Success = false, Error = "Failed to update 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; }
}