feat: 完成Controllers架構統一優化與後端重啟修復
主要改進: - 🏗️ 新增BaseController統一響應處理架構 - 標準化SuccessResponse和ErrorResponse格式 - 統一GetCurrentUserIdAsync認證處理 - 統一HandleModelStateErrors驗證錯誤處理 - 🔧 重構FlashcardsController使用BaseController - 所有返回類型改為IActionResult統一格式 - 完整的異常處理與錯誤回應 - 移除重複的用戶ID獲取邏輯 - 🛠️ 修復依賴注入配置問題 - 使用ServiceCollectionExtensions組織服務註冊 - 修復ICacheProvider和IImageGenerationWorkflow缺失問題 - 清理重複的服務註冊,提升代碼可維護性 - 🐛 解決編譯錯誤 - 修復GeminiOptionsValidator nullable警告 - 排除測試文件避免編譯衝突 - 確保所有依賴正確註冊 後端現已成功重啟並運行在 http://localhost:5008 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2a6c130bb8
commit
923ce16f5f
|
|
@ -0,0 +1,157 @@
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using DramaLing.Api.Models.DTOs;
|
||||||
|
using DramaLing.Api.Services;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace DramaLing.Api.Controllers;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
public abstract class BaseController : ControllerBase
|
||||||
|
{
|
||||||
|
protected readonly ILogger _logger;
|
||||||
|
protected readonly IAuthService? _authService;
|
||||||
|
|
||||||
|
protected BaseController(ILogger logger, IAuthService? authService = null)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_authService = authService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 統一的成功響應格式
|
||||||
|
/// </summary>
|
||||||
|
protected IActionResult SuccessResponse<T>(T data, string? message = null)
|
||||||
|
{
|
||||||
|
return Ok(new ApiResponse<T>
|
||||||
|
{
|
||||||
|
Success = true,
|
||||||
|
Data = data,
|
||||||
|
Message = message,
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 統一的錯誤響應格式
|
||||||
|
/// </summary>
|
||||||
|
protected IActionResult ErrorResponse(string code, string message, object? details = null, int statusCode = 500)
|
||||||
|
{
|
||||||
|
var response = new ApiErrorResponse
|
||||||
|
{
|
||||||
|
Success = false,
|
||||||
|
Error = new ApiError
|
||||||
|
{
|
||||||
|
Code = code,
|
||||||
|
Message = message,
|
||||||
|
Details = details,
|
||||||
|
Suggestions = GetSuggestionsForError(code)
|
||||||
|
},
|
||||||
|
RequestId = Guid.NewGuid().ToString(),
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
return StatusCode(statusCode, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 獲取當前用戶ID(統一處理認證)
|
||||||
|
/// </summary>
|
||||||
|
protected async Task<Guid> GetCurrentUserIdAsync()
|
||||||
|
{
|
||||||
|
if (_authService != null)
|
||||||
|
{
|
||||||
|
// 使用AuthService進行JWT解析(適用於已實現認證的Controller)
|
||||||
|
var userId = await _authService.GetUserIdFromTokenAsync(Request.Headers.Authorization);
|
||||||
|
if (userId.HasValue)
|
||||||
|
return userId.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: 從Claims直接解析
|
||||||
|
var userIdString = User.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
|
||||||
|
User.FindFirst("sub")?.Value;
|
||||||
|
|
||||||
|
if (Guid.TryParse(userIdString, out var parsedUserId))
|
||||||
|
return parsedUserId;
|
||||||
|
|
||||||
|
// 開發階段:使用固定測試用戶ID
|
||||||
|
if (IsTestEnvironment())
|
||||||
|
{
|
||||||
|
return Guid.Parse("00000000-0000-0000-0000-000000000001");
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnauthorizedAccessException("Invalid or missing user authentication");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 檢查是否為測試環境
|
||||||
|
/// </summary>
|
||||||
|
protected bool IsTestEnvironment()
|
||||||
|
{
|
||||||
|
var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
||||||
|
return environment == "Development" || environment == "Testing";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 統一的模型驗證錯誤處理
|
||||||
|
/// </summary>
|
||||||
|
protected IActionResult HandleModelStateErrors()
|
||||||
|
{
|
||||||
|
var errors = ModelState
|
||||||
|
.Where(x => x.Value?.Errors.Count > 0)
|
||||||
|
.ToDictionary(
|
||||||
|
kvp => kvp.Key,
|
||||||
|
kvp => kvp.Value?.Errors.Select(e => e.ErrorMessage).ToArray() ?? Array.Empty<string>()
|
||||||
|
);
|
||||||
|
|
||||||
|
return ErrorResponse("VALIDATION_ERROR", "輸入資料驗證失敗", errors, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根據錯誤代碼獲取建議
|
||||||
|
/// </summary>
|
||||||
|
private List<string> GetSuggestionsForError(string errorCode)
|
||||||
|
{
|
||||||
|
return errorCode switch
|
||||||
|
{
|
||||||
|
"VALIDATION_ERROR" => new List<string> { "請檢查輸入格式", "確保所有必填欄位已填寫" },
|
||||||
|
"INVALID_INPUT" => new List<string> { "請檢查輸入格式", "確保文本長度在限制內" },
|
||||||
|
"RATE_LIMIT_EXCEEDED" => new List<string> { "升級到Premium帳戶以獲得無限使用", "明天重新嘗試" },
|
||||||
|
"AI_SERVICE_ERROR" => new List<string> { "請稍後重試", "如果問題持續,請聯繫客服" },
|
||||||
|
"UNAUTHORIZED" => new List<string> { "請檢查登入狀態", "確認Token是否有效" },
|
||||||
|
"NOT_FOUND" => new List<string> { "請檢查資源ID是否正確", "確認資源是否存在" },
|
||||||
|
_ => new List<string> { "請稍後重試", "如果問題持續,請聯繫客服" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 統一API響應格式
|
||||||
|
/// </summary>
|
||||||
|
public class ApiResponse<T>
|
||||||
|
{
|
||||||
|
public bool Success { get; set; } = true;
|
||||||
|
public T? Data { get; set; }
|
||||||
|
public string? Message { get; set; }
|
||||||
|
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分頁響應格式
|
||||||
|
/// </summary>
|
||||||
|
public class PagedApiResponse<T> : ApiResponse<List<T>>
|
||||||
|
{
|
||||||
|
public PaginationMetadata Pagination { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 分頁元數據
|
||||||
|
/// </summary>
|
||||||
|
public class PaginationMetadata
|
||||||
|
{
|
||||||
|
public int CurrentPage { get; set; }
|
||||||
|
public int PageSize { get; set; }
|
||||||
|
public int TotalCount { get; set; }
|
||||||
|
public int TotalPages { get; set; }
|
||||||
|
public bool HasNext { get; set; }
|
||||||
|
public bool HasPrevious { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -5,75 +5,73 @@ using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
namespace DramaLing.Api.Controllers;
|
namespace DramaLing.Api.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
|
||||||
[Route("api/flashcards")]
|
[Route("api/flashcards")]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public class FlashcardsController : ControllerBase
|
public class FlashcardsController : BaseController
|
||||||
{
|
{
|
||||||
private readonly IFlashcardRepository _flashcardRepository;
|
private readonly IFlashcardRepository _flashcardRepository;
|
||||||
private readonly ILogger<FlashcardsController> _logger;
|
|
||||||
|
|
||||||
public FlashcardsController(
|
public FlashcardsController(
|
||||||
IFlashcardRepository flashcardRepository,
|
IFlashcardRepository flashcardRepository,
|
||||||
ILogger<FlashcardsController> logger)
|
ILogger<FlashcardsController> logger) : base(logger)
|
||||||
{
|
{
|
||||||
_flashcardRepository = flashcardRepository;
|
_flashcardRepository = flashcardRepository;
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Guid GetUserId()
|
|
||||||
{
|
|
||||||
// 暫時使用固定測試用戶 ID
|
|
||||||
return Guid.Parse("00000000-0000-0000-0000-000000000001");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult> GetFlashcards(
|
public async Task<IActionResult> GetFlashcards(
|
||||||
[FromQuery] string? search = null,
|
[FromQuery] string? search = null,
|
||||||
[FromQuery] bool favoritesOnly = false)
|
[FromQuery] bool favoritesOnly = false)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = await GetCurrentUserIdAsync();
|
||||||
var flashcards = await _flashcardRepository.GetByUserIdAsync(userId, search, favoritesOnly);
|
var flashcards = await _flashcardRepository.GetByUserIdAsync(userId, search, favoritesOnly);
|
||||||
|
|
||||||
return Ok(new
|
var flashcardData = new
|
||||||
{
|
{
|
||||||
Success = true,
|
Flashcards = flashcards.Select(f => new
|
||||||
Data = new
|
|
||||||
{
|
{
|
||||||
Flashcards = flashcards.Select(f => new
|
f.Id,
|
||||||
{
|
f.Word,
|
||||||
f.Id,
|
f.Translation,
|
||||||
f.Word,
|
f.Definition,
|
||||||
f.Translation,
|
f.PartOfSpeech,
|
||||||
f.Definition,
|
f.Pronunciation,
|
||||||
f.PartOfSpeech,
|
f.Example,
|
||||||
f.Pronunciation,
|
f.ExampleTranslation,
|
||||||
f.Example,
|
f.IsFavorite,
|
||||||
f.ExampleTranslation,
|
f.DifficultyLevel,
|
||||||
f.IsFavorite,
|
f.CreatedAt,
|
||||||
f.DifficultyLevel,
|
f.UpdatedAt
|
||||||
f.CreatedAt,
|
}),
|
||||||
f.UpdatedAt
|
Count = flashcards.Count()
|
||||||
}),
|
};
|
||||||
Count = flashcards.Count()
|
|
||||||
}
|
return SuccessResponse(flashcardData);
|
||||||
});
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting flashcards");
|
_logger.LogError(ex, "Error getting flashcards");
|
||||||
return StatusCode(500, new { Success = false, Error = "Failed to load flashcards" });
|
return ErrorResponse("INTERNAL_ERROR", "載入詞卡失敗");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<ActionResult> CreateFlashcard([FromBody] CreateFlashcardRequest request)
|
public async Task<IActionResult> CreateFlashcard([FromBody] CreateFlashcardRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return HandleModelStateErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = await GetCurrentUserIdAsync();
|
||||||
|
|
||||||
var flashcard = new Flashcard
|
var flashcard = new Flashcard
|
||||||
{
|
{
|
||||||
|
|
@ -93,55 +91,63 @@ public class FlashcardsController : ControllerBase
|
||||||
|
|
||||||
await _flashcardRepository.AddAsync(flashcard);
|
await _flashcardRepository.AddAsync(flashcard);
|
||||||
|
|
||||||
return Ok(new
|
return SuccessResponse(flashcard, "詞卡創建成功");
|
||||||
{
|
}
|
||||||
Success = true,
|
catch (UnauthorizedAccessException)
|
||||||
Data = flashcard,
|
{
|
||||||
Message = "詞卡創建成功"
|
return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error creating flashcard");
|
_logger.LogError(ex, "Error creating flashcard");
|
||||||
return StatusCode(500, new { Success = false, Error = "Failed to create flashcard" });
|
return ErrorResponse("INTERNAL_ERROR", "創建詞卡失敗");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{id}")]
|
[HttpGet("{id}")]
|
||||||
public async Task<ActionResult> GetFlashcard(Guid id)
|
public async Task<IActionResult> GetFlashcard(Guid id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = await GetCurrentUserIdAsync();
|
||||||
|
|
||||||
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
||||||
|
|
||||||
if (flashcard == null)
|
if (flashcard == null)
|
||||||
{
|
{
|
||||||
return NotFound(new { Success = false, Error = "Flashcard not found" });
|
return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new { Success = true, Data = flashcard });
|
return SuccessResponse(flashcard);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error getting flashcard {FlashcardId}", id);
|
_logger.LogError(ex, "Error getting flashcard {FlashcardId}", id);
|
||||||
return StatusCode(500, new { Success = false, Error = "Failed to get flashcard" });
|
return ErrorResponse("INTERNAL_ERROR", "取得詞卡失敗");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id}")]
|
[HttpPut("{id}")]
|
||||||
public async Task<ActionResult> UpdateFlashcard(Guid id, [FromBody] CreateFlashcardRequest request)
|
public async Task<IActionResult> UpdateFlashcard(Guid id, [FromBody] CreateFlashcardRequest request)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
if (!ModelState.IsValid)
|
||||||
|
{
|
||||||
|
return HandleModelStateErrors();
|
||||||
|
}
|
||||||
|
|
||||||
|
var userId = await GetCurrentUserIdAsync();
|
||||||
|
|
||||||
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
||||||
|
|
||||||
if (flashcard == null)
|
if (flashcard == null)
|
||||||
{
|
{
|
||||||
return NotFound(new { Success = false, Error = "Flashcard not found" });
|
return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新詞卡資訊
|
// 更新詞卡資訊
|
||||||
|
|
@ -156,57 +162,60 @@ public class FlashcardsController : ControllerBase
|
||||||
|
|
||||||
await _flashcardRepository.UpdateAsync(flashcard);
|
await _flashcardRepository.UpdateAsync(flashcard);
|
||||||
|
|
||||||
return Ok(new
|
return SuccessResponse(flashcard, "詞卡更新成功");
|
||||||
{
|
}
|
||||||
Success = true,
|
catch (UnauthorizedAccessException)
|
||||||
Data = flashcard,
|
{
|
||||||
Message = "詞卡更新成功"
|
return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error updating flashcard {FlashcardId}", id);
|
_logger.LogError(ex, "Error updating flashcard {FlashcardId}", id);
|
||||||
return StatusCode(500, new { Success = false, Error = "Failed to update flashcard" });
|
return ErrorResponse("INTERNAL_ERROR", "更新詞卡失敗");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
public async Task<ActionResult> DeleteFlashcard(Guid id)
|
public async Task<IActionResult> DeleteFlashcard(Guid id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = await GetCurrentUserIdAsync();
|
||||||
|
|
||||||
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
||||||
|
|
||||||
if (flashcard == null)
|
if (flashcard == null)
|
||||||
{
|
{
|
||||||
return NotFound(new { Success = false, Error = "Flashcard not found" });
|
return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
await _flashcardRepository.DeleteAsync(flashcard);
|
await _flashcardRepository.DeleteAsync(flashcard);
|
||||||
|
|
||||||
return Ok(new { Success = true, Message = "詞卡已刪除" });
|
return SuccessResponse(new { Id = id }, "詞卡已刪除");
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error deleting flashcard {FlashcardId}", id);
|
_logger.LogError(ex, "Error deleting flashcard {FlashcardId}", id);
|
||||||
return StatusCode(500, new { Success = false, Error = "Failed to delete flashcard" });
|
return ErrorResponse("INTERNAL_ERROR", "刪除詞卡失敗");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{id}/favorite")]
|
[HttpPost("{id}/favorite")]
|
||||||
public async Task<ActionResult> ToggleFavorite(Guid id)
|
public async Task<IActionResult> ToggleFavorite(Guid id)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var userId = GetUserId();
|
var userId = await GetCurrentUserIdAsync();
|
||||||
|
|
||||||
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
var flashcard = await _flashcardRepository.GetByUserIdAndFlashcardIdAsync(userId, id);
|
||||||
|
|
||||||
if (flashcard == null)
|
if (flashcard == null)
|
||||||
{
|
{
|
||||||
return NotFound(new { Success = false, Error = "Flashcard not found" });
|
return ErrorResponse("NOT_FOUND", "詞卡不存在", null, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
flashcard.IsFavorite = !flashcard.IsFavorite;
|
flashcard.IsFavorite = !flashcard.IsFavorite;
|
||||||
|
|
@ -214,16 +223,22 @@ public class FlashcardsController : ControllerBase
|
||||||
|
|
||||||
await _flashcardRepository.UpdateAsync(flashcard);
|
await _flashcardRepository.UpdateAsync(flashcard);
|
||||||
|
|
||||||
return Ok(new {
|
var result = new {
|
||||||
Success = true,
|
Id = flashcard.Id,
|
||||||
IsFavorite = flashcard.IsFavorite,
|
IsFavorite = flashcard.IsFavorite
|
||||||
Message = flashcard.IsFavorite ? "已加入收藏" : "已取消收藏"
|
};
|
||||||
});
|
|
||||||
|
var message = flashcard.IsFavorite ? "已加入收藏" : "已取消收藏";
|
||||||
|
return SuccessResponse(result, message);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return ErrorResponse("UNAUTHORIZED", "認證失敗", null, 401);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error toggling favorite for flashcard {FlashcardId}", id);
|
_logger.LogError(ex, "Error toggling favorite for flashcard {FlashcardId}", id);
|
||||||
return StatusCode(500, new { Success = false, Error = "Failed to toggle favorite" });
|
return ErrorResponse("INTERNAL_ERROR", "切換收藏狀態失敗");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,8 @@
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.10" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="8.0.10" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove="DramaLing.Api.Tests/**/*.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -5,7 +5,7 @@ namespace DramaLing.Api.Models.Configuration;
|
||||||
|
|
||||||
public class GeminiOptionsValidator : IValidateOptions<GeminiOptions>
|
public class GeminiOptionsValidator : IValidateOptions<GeminiOptions>
|
||||||
{
|
{
|
||||||
public ValidateOptionsResult Validate(string name, GeminiOptions options)
|
public ValidateOptionsResult Validate(string? name, GeminiOptions options)
|
||||||
{
|
{
|
||||||
var failures = new List<string>();
|
var failures = new List<string>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using DramaLing.Api.Data;
|
using DramaLing.Api.Data;
|
||||||
using DramaLing.Api.Services;
|
using DramaLing.Api.Services;
|
||||||
// Services.AI namespace removed
|
|
||||||
using DramaLing.Api.Services.Caching;
|
using DramaLing.Api.Services.Caching;
|
||||||
using DramaLing.Api.Services.Monitoring;
|
using DramaLing.Api.Services.Monitoring;
|
||||||
using DramaLing.Api.Services.Storage;
|
using DramaLing.Api.Services.Storage;
|
||||||
using DramaLing.Api.Middleware;
|
using DramaLing.Api.Middleware;
|
||||||
using DramaLing.Api.Models.Configuration;
|
using DramaLing.Api.Models.Configuration;
|
||||||
using DramaLing.Api.Repositories;
|
using DramaLing.Api.Repositories;
|
||||||
|
using DramaLing.Api.Extensions;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
@ -49,51 +49,16 @@ builder.Services.AddControllers()
|
||||||
options.JsonSerializerOptions.WriteIndented = true;
|
options.JsonSerializerOptions.WriteIndented = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Entity Framework - 使用 SQLite 進行測試
|
// 配置資料庫服務
|
||||||
var useInMemoryDb = Environment.GetEnvironmentVariable("USE_INMEMORY_DB") == "true";
|
builder.Services.AddDatabaseServices(builder.Configuration);
|
||||||
if (useInMemoryDb)
|
|
||||||
{
|
|
||||||
builder.Services.AddDbContext<DramaLingDbContext>(options =>
|
|
||||||
options.UseSqlite("Data Source=:memory:"));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var connectionString = Environment.GetEnvironmentVariable("DRAMALING_DB_CONNECTION")
|
|
||||||
?? builder.Configuration.GetConnectionString("DefaultConnection")
|
|
||||||
?? "Data Source=dramaling_test.db"; // SQLite 檔案
|
|
||||||
|
|
||||||
builder.Services.AddDbContext<DramaLingDbContext>(options =>
|
// 配置 Repository 和 Caching 服務
|
||||||
options.UseSqlite(connectionString));
|
builder.Services.AddRepositoryServices();
|
||||||
}
|
builder.Services.AddCachingServices();
|
||||||
|
|
||||||
// 暫時註解新的服務,等修正編譯錯誤後再啟用
|
// 配置 AI 和業務服務
|
||||||
// Repository Services
|
builder.Services.AddAIServices(builder.Configuration);
|
||||||
// builder.Services.AddScoped(typeof(IRepository<>), typeof(BaseRepository<>));
|
builder.Services.AddBusinessServices();
|
||||||
// builder.Services.AddScoped<IFlashcardRepository, SimpleFlashcardRepository>();
|
|
||||||
// builder.Services.AddScoped<IUserRepository, UserRepository>();
|
|
||||||
|
|
||||||
// Caching Services - now using Extension method
|
|
||||||
// builder.Services.AddMemoryCache();
|
|
||||||
// builder.Services.AddScoped<ICacheService, HybridCacheService>();
|
|
||||||
|
|
||||||
// AI Services
|
|
||||||
// builder.Services.AddHttpClient<GeminiAIProvider>();
|
|
||||||
// builder.Services.AddScoped<IAIProvider, GeminiAIProvider>();
|
|
||||||
// builder.Services.AddScoped<IAIProviderManager, AIProviderManager>();
|
|
||||||
|
|
||||||
// Custom Services
|
|
||||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
||||||
builder.Services.AddHttpClient<IGeminiService, GeminiService>();
|
|
||||||
// 新增帶快取的分析服務
|
|
||||||
builder.Services.AddScoped<IAnalysisService, AnalysisService>();
|
|
||||||
builder.Services.AddScoped<IUsageTrackingService, UsageTrackingService>();
|
|
||||||
builder.Services.AddScoped<IAzureSpeechService, AzureSpeechService>();
|
|
||||||
// 智能填空題系統服務已移除
|
|
||||||
builder.Services.AddScoped<IAudioCacheService, AudioCacheService>();
|
|
||||||
|
|
||||||
// 智能複習服務已移除,準備重新實施
|
|
||||||
|
|
||||||
// 學習會話服務已清理移除
|
|
||||||
|
|
||||||
// 🆕 選項詞彙庫服務註冊
|
// 🆕 選項詞彙庫服務註冊
|
||||||
builder.Services.Configure<OptionsVocabularyOptions>(
|
builder.Services.Configure<OptionsVocabularyOptions>(
|
||||||
|
|
@ -103,15 +68,7 @@ builder.Services.AddSingleton<OptionsVocabularyMetrics>(); // 監控指標服務
|
||||||
// builder.Services.AddScoped<OptionsVocabularySeeder>(); // 暫時註解,使用固定選項
|
// builder.Services.AddScoped<OptionsVocabularySeeder>(); // 暫時註解,使用固定選項
|
||||||
builder.Services.AddScoped<IOptionsVocabularyService, OptionsVocabularyService>();
|
builder.Services.AddScoped<IOptionsVocabularyService, OptionsVocabularyService>();
|
||||||
|
|
||||||
// Image Generation Services
|
// (圖片相關服務已透過 AddAIServices 和 AddBusinessServices 註冊)
|
||||||
builder.Services.AddHttpClient<IReplicateService, ReplicateService>();
|
|
||||||
builder.Services.AddScoped<IImageGenerationOrchestrator, ImageGenerationOrchestrator>();
|
|
||||||
|
|
||||||
// Image Storage Services
|
|
||||||
builder.Services.AddScoped<IImageStorageService, LocalImageStorageService>();
|
|
||||||
|
|
||||||
// Image Processing Services
|
|
||||||
builder.Services.AddScoped<IImageProcessingService, ImageProcessingService>();
|
|
||||||
|
|
||||||
// Background Services (快取清理服務已移除)
|
// Background Services (快取清理服務已移除)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue