feat: 完成資料庫命名規範統一 - 全面實施snake_case標準
🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
923ce16f5f
commit
11b0f606d3
|
|
@ -0,0 +1,213 @@
|
|||
# 資料庫命名規範統一計劃
|
||||
|
||||
## 📋 計劃概述
|
||||
|
||||
本計劃旨在解決 DramaLing 專案中資料庫欄位命名不一致的問題,將所有資料庫欄位統一為 `snake_case` 命名規範。
|
||||
|
||||
## 🔍 現況分析
|
||||
|
||||
### 問題描述
|
||||
目前資料庫中同時存在兩種命名方式:
|
||||
- **PascalCase**: `Example`, `Word`, `Translation`, `Definition`, `Name`, `Color` 等
|
||||
- **snake_case**: `user_id`, `created_at`, `is_favorite`, `part_of_speech` 等
|
||||
|
||||
### 問題根源
|
||||
1. **歷史遺留**: 早期遷移沒有統一配置欄位命名
|
||||
2. **不完整配置**: 部分屬性缺少 `.HasColumnName()` 映射
|
||||
3. **維護疏漏**: 新增欄位時沒有遵循統一規範
|
||||
|
||||
### 已修復項目 ✅
|
||||
- ✅ `Flashcard.Word` → `word`
|
||||
- ✅ `Flashcard.Translation` → `translation`
|
||||
- ✅ `Flashcard.Definition` → `definition`
|
||||
- ✅ `Flashcard.Pronunciation` → `pronunciation`
|
||||
- ✅ `Flashcard.Example` → `example` (原始問題)
|
||||
|
||||
## 🎯 統一規範標準
|
||||
|
||||
### 命名規則
|
||||
| 層級 | 命名方式 | 範例 | 說明 |
|
||||
|------|----------|------|------|
|
||||
| **C# 實體屬性** | PascalCase | `UserId`, `CreatedAt`, `ExampleTranslation` | 符合 C# 慣例 |
|
||||
| **資料庫欄位** | snake_case | `user_id`, `created_at`, `example_translation` | 符合資料庫慣例 |
|
||||
| **表格名稱** | snake_case | `flashcards`, `user_profiles`, `daily_stats` | 保持一致性 |
|
||||
|
||||
### 映射規則
|
||||
```csharp
|
||||
// 所有屬性都需要明確映射
|
||||
entity.Property(e => e.PropertyName).HasColumnName("property_name");
|
||||
```
|
||||
|
||||
## 📝 待修復項目清單
|
||||
|
||||
### 1. Tag 實體
|
||||
```csharp
|
||||
// 目前缺少的映射:
|
||||
public Guid Id { get; set; } // → "id"
|
||||
public string Name { get; set; } // → "name"
|
||||
public string Color { get; set; } // → "color"
|
||||
```
|
||||
|
||||
### 2. ErrorReport 實體
|
||||
```csharp
|
||||
// 目前缺少的映射:
|
||||
public Guid Id { get; set; } // → "id"
|
||||
public string? Description { get; set; } // → "description"
|
||||
public string Status { get; set; } // → "status"
|
||||
```
|
||||
|
||||
### 3. DailyStats 實體
|
||||
```csharp
|
||||
// 目前缺少的映射:
|
||||
public Guid Id { get; set; } // → "id"
|
||||
public DateTime Date { get; set; } // → "date"
|
||||
```
|
||||
|
||||
### 4. 其他實體
|
||||
需要檢查以下實體是否有遺漏的映射:
|
||||
- `SentenceAnalysisCache`
|
||||
- `WordQueryUsageStats`
|
||||
- `ExampleImage`
|
||||
- `ImageGenerationRequest`
|
||||
- `OptionsVocabulary`
|
||||
|
||||
### 5. 通用 Id 欄位
|
||||
所有實體的 `Id` 屬性都應該映射為 `id`
|
||||
|
||||
## 🚀 執行步驟
|
||||
|
||||
### 階段一:DbContext 配置更新
|
||||
1. **補充 Tag 實體配置**
|
||||
```csharp
|
||||
private void ConfigureTagEntities(ModelBuilder modelBuilder)
|
||||
{
|
||||
var tagEntity = modelBuilder.Entity<Tag>();
|
||||
tagEntity.Property(t => t.Id).HasColumnName("id");
|
||||
tagEntity.Property(t => t.Name).HasColumnName("name");
|
||||
tagEntity.Property(t => t.Color).HasColumnName("color");
|
||||
// 其他現有配置...
|
||||
}
|
||||
```
|
||||
|
||||
2. **補充其他實體配置**
|
||||
- 更新 `ConfigureErrorReportEntity`
|
||||
- 更新 `ConfigureDailyStatsEntity`
|
||||
- 新增其他實體的配置方法
|
||||
|
||||
### 階段二:資料庫遷移
|
||||
1. **建立遷移**
|
||||
```bash
|
||||
dotnet ef migrations add CompleteSnakeCaseNaming
|
||||
```
|
||||
|
||||
2. **套用遷移**
|
||||
```bash
|
||||
dotnet ef database update
|
||||
```
|
||||
|
||||
### 階段三:驗證與測試
|
||||
1. **檢查資料庫結構**
|
||||
```sql
|
||||
.schema table_name
|
||||
```
|
||||
|
||||
2. **測試應用程式功能**
|
||||
- API 端點測試
|
||||
- 資料查詢測試
|
||||
- 完整功能驗證
|
||||
|
||||
## 📋 檢核清單
|
||||
|
||||
### 配置檢核
|
||||
- [ ] 所有實體的 `Id` 屬性都有 `.HasColumnName("id")`
|
||||
- [ ] 所有多單字屬性都使用 snake_case(如 `CreatedAt` → `created_at`)
|
||||
- [ ] 所有布林屬性都使用 `is_` 前綴(如 `IsActive` → `is_active`)
|
||||
- [ ] 外鍵屬性都使用 `_id` 後綴(如 `UserId` → `user_id`)
|
||||
|
||||
### 遷移檢核
|
||||
- [ ] 遷移檔案正確生成
|
||||
- [ ] SQL 指令正確(RENAME COLUMN)
|
||||
- [ ] 沒有資料遺失風險
|
||||
- [ ] 回滾計劃準備完成
|
||||
|
||||
### 測試檢核
|
||||
- [ ] 所有 API 端點正常運作
|
||||
- [ ] 資料查詢結果正確
|
||||
- [ ] 無效能退化
|
||||
- [ ] 前端功能正常
|
||||
|
||||
## 🔧 長期維護建議
|
||||
|
||||
### 1. 編碼規範
|
||||
建立明確的編碼規範文檔:
|
||||
```markdown
|
||||
## 資料庫命名規範
|
||||
- 所有新增的實體屬性都必須配置 `.HasColumnName()`
|
||||
- 資料庫欄位名稱統一使用 snake_case
|
||||
- 布林欄位使用 `is_` 前綴
|
||||
- 外鍵欄位使用 `_id` 後綴
|
||||
```
|
||||
|
||||
### 2. Code Review 檢查點
|
||||
在 PR 審查時檢查:
|
||||
- 新增實體是否有完整的欄位映射配置
|
||||
- 遷移檔案是否符合命名規範
|
||||
- 是否需要更新相關文檔
|
||||
|
||||
### 3. 自動化檢查
|
||||
考慮實施:
|
||||
- **Pre-commit Hook**: 檢查新增的 DbContext 配置
|
||||
- **CI/CD 檢查**: 驗證遷移檔案的正確性
|
||||
- **單元測試**: 確保所有實體都有正確的欄位映射
|
||||
|
||||
### 4. 全局慣例配置(進階選項)
|
||||
可以考慮使用 EF Core 的全局慣例:
|
||||
```csharp
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
|
||||
{
|
||||
configurationBuilder.Properties<string>()
|
||||
.HaveColumnName(propertyInfo => propertyInfo.Name.ToSnakeCase());
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 影響評估
|
||||
|
||||
### 優點
|
||||
- ✅ 統一的命名規範
|
||||
- ✅ 更好的可維護性
|
||||
- ✅ 避免開發混淆
|
||||
- ✅ 符合業界標準
|
||||
|
||||
### 風險
|
||||
- ⚠️ 需要資料庫遷移
|
||||
- ⚠️ 可能影響現有查詢
|
||||
- ⚠️ 需要充分測試
|
||||
|
||||
### 緩解措施
|
||||
- 📋 充分的測試計劃
|
||||
- 🔄 準備回滾方案
|
||||
- 📝 詳細的變更文檔
|
||||
- 👥 團隊溝通協調
|
||||
|
||||
## 🗓️ 執行時間表
|
||||
|
||||
| 階段 | 預估時間 | 責任人 | 狀態 |
|
||||
|------|----------|--------|------|
|
||||
| 現況分析 | 0.5 天 | 開發團隊 | ✅ 完成 |
|
||||
| 配置更新 | 1 天 | 後端開發 | 🚧 進行中 |
|
||||
| 遷移建立 | 0.5 天 | 後端開發 | ⏳ 待執行 |
|
||||
| 測試驗證 | 1 天 | 全團隊 | ⏳ 待執行 |
|
||||
| 部署上線 | 0.5 天 | DevOps | ⏳ 待執行 |
|
||||
|
||||
## 📞 聯絡資訊
|
||||
|
||||
如有問題或需要協助,請聯絡:
|
||||
- **技術負責人**: [待填入]
|
||||
- **專案經理**: [待填入]
|
||||
- **QA 負責人**: [待填入]
|
||||
|
||||
---
|
||||
|
||||
**文件版本**: v1.0
|
||||
**最後更新**: 2025-09-30
|
||||
**建立人**: Claude Code Assistant
|
||||
|
|
@ -6,19 +6,16 @@ using System.Diagnostics;
|
|||
|
||||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/ai")]
|
||||
public class AIController : ControllerBase
|
||||
public class AIController : BaseController
|
||||
{
|
||||
private readonly IAnalysisService _analysisService;
|
||||
private readonly ILogger<AIController> _logger;
|
||||
|
||||
public AIController(
|
||||
IAnalysisService analysisService,
|
||||
ILogger<AIController> logger)
|
||||
ILogger<AIController> logger) : base(logger)
|
||||
{
|
||||
_analysisService = analysisService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -28,7 +25,7 @@ public class AIController : ControllerBase
|
|||
/// <returns>分析結果</returns>
|
||||
[HttpPost("analyze-sentence")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<SentenceAnalysisResponse>> AnalyzeSentence(
|
||||
public async Task<IActionResult> AnalyzeSentence(
|
||||
[FromBody] SentenceAnalysisRequest request)
|
||||
{
|
||||
var requestId = Guid.NewGuid().ToString();
|
||||
|
|
@ -45,9 +42,7 @@ public class AIController : ControllerBase
|
|||
// Input validation
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(CreateErrorResponse("INVALID_INPUT", "輸入格式錯誤",
|
||||
ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToList(),
|
||||
requestId));
|
||||
return HandleModelStateErrors();
|
||||
}
|
||||
|
||||
// 使用帶快取的分析服務
|
||||
|
|
@ -61,27 +56,29 @@ public class AIController : ControllerBase
|
|||
_logger.LogInformation("Sentence analysis completed for request {RequestId} in {ElapsedMs}ms",
|
||||
requestId, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
return Ok(new SentenceAnalysisResponse
|
||||
var response = new SentenceAnalysisResponse
|
||||
{
|
||||
Success = true,
|
||||
ProcessingTime = stopwatch.Elapsed.TotalSeconds,
|
||||
Data = analysisData
|
||||
});
|
||||
};
|
||||
|
||||
return SuccessResponse(response);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Invalid input for request {RequestId}", requestId);
|
||||
return BadRequest(CreateErrorResponse("INVALID_INPUT", ex.Message, null, requestId));
|
||||
return ErrorResponse("INVALID_INPUT", ex.Message, null, 400);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "AI service error for request {RequestId}", requestId);
|
||||
return StatusCode(500, CreateErrorResponse("AI_SERVICE_ERROR", "AI服務暫時不可用", null, requestId));
|
||||
return ErrorResponse("AI_SERVICE_ERROR", "AI服務暫時不可用", null, 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error processing request {RequestId}", requestId);
|
||||
return StatusCode(500, CreateErrorResponse("INTERNAL_ERROR", "伺服器內部錯誤", null, requestId));
|
||||
return ErrorResponse("INTERNAL_ERROR", "伺服器內部錯誤", null, 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -90,15 +87,17 @@ public class AIController : ControllerBase
|
|||
/// </summary>
|
||||
[HttpGet("health")]
|
||||
[AllowAnonymous]
|
||||
public ActionResult GetHealth()
|
||||
public IActionResult GetHealth()
|
||||
{
|
||||
return Ok(new
|
||||
var healthData = new
|
||||
{
|
||||
Status = "Healthy",
|
||||
Service = "AI Analysis Service",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Version = "1.0"
|
||||
});
|
||||
};
|
||||
|
||||
return SuccessResponse(healthData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -106,59 +105,28 @@ public class AIController : ControllerBase
|
|||
/// </summary>
|
||||
[HttpGet("stats")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult> GetAnalysisStats()
|
||||
public async Task<IActionResult> GetAnalysisStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _analysisService.GetAnalysisStatsAsync();
|
||||
return Ok(new
|
||||
var statsData = new
|
||||
{
|
||||
Success = true,
|
||||
Data = new
|
||||
{
|
||||
TotalAnalyses = stats.TotalAnalyses,
|
||||
CachedAnalyses = stats.CachedAnalyses,
|
||||
CacheHitRate = stats.CacheHitRate,
|
||||
AverageResponseTimeMs = stats.AverageResponseTimeMs,
|
||||
LastAnalysisAt = stats.LastAnalysisAt,
|
||||
ProviderUsage = stats.ProviderUsageStats
|
||||
}
|
||||
});
|
||||
TotalAnalyses = stats.TotalAnalyses,
|
||||
CachedAnalyses = stats.CachedAnalyses,
|
||||
CacheHitRate = stats.CacheHitRate,
|
||||
AverageResponseTimeMs = stats.AverageResponseTimeMs,
|
||||
LastAnalysisAt = stats.LastAnalysisAt,
|
||||
ProviderUsage = stats.ProviderUsageStats
|
||||
};
|
||||
|
||||
return SuccessResponse(statsData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting analysis stats");
|
||||
return StatusCode(500, new { Success = false, Error = "無法取得統計資訊" });
|
||||
return ErrorResponse("INTERNAL_ERROR", "無法取得統計資訊");
|
||||
}
|
||||
}
|
||||
|
||||
private ApiErrorResponse CreateErrorResponse(string code, string message, object? details, string requestId)
|
||||
{
|
||||
var suggestions = GetSuggestionsForError(code);
|
||||
|
||||
return new ApiErrorResponse
|
||||
{
|
||||
Success = false,
|
||||
Error = new ApiError
|
||||
{
|
||||
Code = code,
|
||||
Message = message,
|
||||
Details = details,
|
||||
Suggestions = suggestions
|
||||
},
|
||||
RequestId = requestId,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
private List<string> GetSuggestionsForError(string errorCode)
|
||||
{
|
||||
return errorCode switch
|
||||
{
|
||||
"INVALID_INPUT" => new List<string> { "請檢查輸入格式", "確保文本長度在限制內" },
|
||||
"RATE_LIMIT_EXCEEDED" => new List<string> { "升級到Premium帳戶以獲得無限使用", "明天重新嘗試" },
|
||||
"AI_SERVICE_ERROR" => new List<string> { "請稍後重試", "如果問題持續,請聯繫客服" },
|
||||
_ => new List<string> { "請稍後重試" }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using DramaLing.Api.Models.DTOs;
|
||||
using DramaLing.Api.Services;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[Route("api/analysis")]
|
||||
[AllowAnonymous]
|
||||
public class AnalysisController : BaseController
|
||||
{
|
||||
private readonly IAnalysisService _analysisService;
|
||||
|
||||
public AnalysisController(
|
||||
IAnalysisService analysisService,
|
||||
ILogger<AnalysisController> logger) : base(logger)
|
||||
{
|
||||
_analysisService = analysisService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 智能分析英文句子
|
||||
/// </summary>
|
||||
/// <param name="request">分析請求</param>
|
||||
/// <returns>分析結果</returns>
|
||||
[HttpPost("analyze")]
|
||||
public async Task<IActionResult> AnalyzeSentence([FromBody] SentenceAnalysisRequest request)
|
||||
{
|
||||
var requestId = Guid.NewGuid().ToString();
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
// Input validation
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return HandleModelStateErrors();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Processing sentence analysis request {RequestId}", requestId);
|
||||
|
||||
// 使用帶快取的分析服務
|
||||
var options = request.Options ?? new AnalysisOptions();
|
||||
var analysisData = await _analysisService.AnalyzeSentenceAsync(
|
||||
request.InputText, options);
|
||||
|
||||
stopwatch.Stop();
|
||||
analysisData.Metadata.ProcessingDate = DateTime.UtcNow;
|
||||
|
||||
_logger.LogInformation("Sentence analysis completed for request {RequestId} in {ElapsedMs}ms",
|
||||
requestId, stopwatch.ElapsedMilliseconds);
|
||||
|
||||
var response = new SentenceAnalysisResponse
|
||||
{
|
||||
Success = true,
|
||||
ProcessingTime = stopwatch.Elapsed.TotalSeconds,
|
||||
Data = analysisData
|
||||
};
|
||||
|
||||
return SuccessResponse(response);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Invalid input for request {RequestId}", requestId);
|
||||
return ErrorResponse("INVALID_INPUT", ex.Message, null, 400);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
_logger.LogError(ex, "AI service error for request {RequestId}", requestId);
|
||||
return ErrorResponse("AI_SERVICE_ERROR", "AI服務暫時不可用", null, 500);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Unexpected error processing request {RequestId}", requestId);
|
||||
return ErrorResponse("INTERNAL_ERROR", "伺服器內部錯誤", null, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 健康檢查端點
|
||||
/// </summary>
|
||||
[HttpGet("health")]
|
||||
public IActionResult GetHealth()
|
||||
{
|
||||
var healthData = new
|
||||
{
|
||||
Status = "Healthy",
|
||||
Service = "Analysis Service",
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Version = "1.0"
|
||||
};
|
||||
|
||||
return SuccessResponse(healthData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取得分析統計資訊
|
||||
/// </summary>
|
||||
[HttpGet("stats")]
|
||||
public async Task<IActionResult> GetAnalysisStats()
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = await _analysisService.GetAnalysisStatsAsync();
|
||||
var statsData = new
|
||||
{
|
||||
TotalAnalyses = stats.TotalAnalyses,
|
||||
CachedAnalyses = stats.CachedAnalyses,
|
||||
CacheHitRate = stats.CacheHitRate,
|
||||
AverageResponseTimeMs = stats.AverageResponseTimeMs,
|
||||
LastAnalysisAt = stats.LastAnalysisAt,
|
||||
ProviderUsage = stats.ProviderUsageStats
|
||||
};
|
||||
|
||||
return SuccessResponse(statsData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error getting analysis stats");
|
||||
return ErrorResponse("INTERNAL_ERROR", "無法取得統計資訊");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,23 +5,20 @@ using DramaLing.Api.Services;
|
|||
|
||||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class AudioController : ControllerBase
|
||||
public class AudioController : BaseController
|
||||
{
|
||||
private readonly IAudioCacheService _audioCacheService;
|
||||
private readonly IAzureSpeechService _speechService;
|
||||
private readonly ILogger<AudioController> _logger;
|
||||
|
||||
public AudioController(
|
||||
IAudioCacheService audioCacheService,
|
||||
IAzureSpeechService speechService,
|
||||
ILogger<AudioController> logger)
|
||||
ILogger<AudioController> logger) : base(logger)
|
||||
{
|
||||
_audioCacheService = audioCacheService;
|
||||
_speechService = speechService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -30,7 +27,7 @@ public class AudioController : ControllerBase
|
|||
/// <param name="request">TTS request parameters</param>
|
||||
/// <returns>Audio URL and metadata</returns>
|
||||
[HttpPost("tts")]
|
||||
public async Task<ActionResult<TTSResponse>> GenerateAudio([FromBody] TTSRequest request)
|
||||
public async Task<IActionResult> GenerateAudio([FromBody] TTSRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -70,10 +67,10 @@ public class AudioController : ControllerBase
|
|||
|
||||
if (!string.IsNullOrEmpty(response.Error))
|
||||
{
|
||||
return StatusCode(500, response);
|
||||
return ErrorResponse("TTS_ERROR", response.Error, null, 500);
|
||||
}
|
||||
|
||||
return Ok(response);
|
||||
return SuccessResponse(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,26 +12,21 @@ using Microsoft.IdentityModel.Tokens;
|
|||
|
||||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AuthController : ControllerBase
|
||||
public class AuthController : BaseController
|
||||
{
|
||||
private readonly DramaLingDbContext _context;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ILogger<AuthController> _logger;
|
||||
|
||||
public AuthController(
|
||||
DramaLingDbContext context,
|
||||
IAuthService authService,
|
||||
ILogger<AuthController> logger)
|
||||
ILogger<AuthController> logger) : base(logger, authService)
|
||||
{
|
||||
_context = context;
|
||||
_authService = authService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult> Register([FromBody] RegisterRequest request)
|
||||
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
|
|||
|
|
@ -7,19 +7,16 @@ using System.Security.Claims;
|
|||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
[AllowAnonymous] // 暫時移除認證要求,與 FlashcardsController 保持一致
|
||||
public class ImageGenerationController : ControllerBase
|
||||
public class ImageGenerationController : BaseController
|
||||
{
|
||||
private readonly IImageGenerationOrchestrator _orchestrator;
|
||||
private readonly ILogger<ImageGenerationController> _logger;
|
||||
|
||||
public ImageGenerationController(
|
||||
IImageGenerationOrchestrator orchestrator,
|
||||
ILogger<ImageGenerationController> logger)
|
||||
ILogger<ImageGenerationController> logger) : base(logger)
|
||||
{
|
||||
_orchestrator = orchestrator ?? throw new ArgumentNullException(nameof(orchestrator));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -43,17 +40,17 @@ public class ImageGenerationController : ControllerBase
|
|||
|
||||
var result = await _orchestrator.StartGenerationAsync(flashcardId, request);
|
||||
|
||||
return Ok(new { success = true, data = result });
|
||||
return SuccessResponse(result);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Invalid request for flashcard {FlashcardId}", flashcardId);
|
||||
return BadRequest(new { success = false, error = ex.Message });
|
||||
return ErrorResponse("INVALID_REQUEST", ex.Message, null, 400);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to start image generation for flashcard {FlashcardId}", flashcardId);
|
||||
return StatusCode(500, new { success = false, error = "Failed to start generation" });
|
||||
return ErrorResponse("GENERATION_FAILED", "Failed to start generation");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -73,17 +70,17 @@ public class ImageGenerationController : ControllerBase
|
|||
|
||||
var status = await _orchestrator.GetGenerationStatusAsync(requestId);
|
||||
|
||||
return Ok(new { success = true, data = status });
|
||||
return SuccessResponse(status);
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Generation request {RequestId} not found", requestId);
|
||||
return NotFound(new { success = false, error = ex.Message });
|
||||
return ErrorResponse("NOT_FOUND", ex.Message, null, 404);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get status for request {RequestId}", requestId);
|
||||
return StatusCode(500, new { success = false, error = "Failed to get status" });
|
||||
return ErrorResponse("STATUS_ERROR", "Failed to get status");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,17 +102,17 @@ public class ImageGenerationController : ControllerBase
|
|||
|
||||
if (cancelled)
|
||||
{
|
||||
return Ok(new { success = true, message = "Generation cancelled successfully" });
|
||||
return SuccessResponse(new { message = "Generation cancelled successfully" });
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new { success = false, error = "Cannot cancel this request" });
|
||||
return ErrorResponse("CANCEL_FAILED", "Cannot cancel this request", null, 400);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to cancel generation request {RequestId}", requestId);
|
||||
return StatusCode(500, new { success = false, error = "Failed to cancel generation" });
|
||||
return ErrorResponse("CANCEL_ERROR", "Failed to cancel generation");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,12 +145,12 @@ public class ImageGenerationController : ControllerBase
|
|||
}
|
||||
};
|
||||
|
||||
return Ok(new { success = true, data = history });
|
||||
return SuccessResponse(history);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get generation history for user");
|
||||
return StatusCode(500, new { success = false, error = "Failed to get history" });
|
||||
return ErrorResponse("HISTORY_ERROR", "Failed to get history");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,19 +6,16 @@ namespace DramaLing.Api.Controllers;
|
|||
/// <summary>
|
||||
/// 選項詞彙庫服務測試控制器 (僅用於開發測試)
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/test/[controller]")]
|
||||
public class OptionsVocabularyTestController : ControllerBase
|
||||
public class OptionsVocabularyTestController : BaseController
|
||||
{
|
||||
private readonly IOptionsVocabularyService _optionsVocabularyService;
|
||||
private readonly ILogger<OptionsVocabularyTestController> _logger;
|
||||
|
||||
public OptionsVocabularyTestController(
|
||||
IOptionsVocabularyService optionsVocabularyService,
|
||||
ILogger<OptionsVocabularyTestController> logger)
|
||||
ILogger<OptionsVocabularyTestController> logger) : base(logger)
|
||||
{
|
||||
_optionsVocabularyService = optionsVocabularyService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ using System.Security.Claims;
|
|||
|
||||
namespace DramaLing.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class StatsController : ControllerBase
|
||||
public class StatsController : BaseController
|
||||
{
|
||||
private readonly DramaLingDbContext _context;
|
||||
|
||||
public StatsController(DramaLingDbContext context)
|
||||
public StatsController(DramaLingDbContext context, ILogger<StatsController> logger) : base(logger)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,14 +49,19 @@ public class DramaLingDbContext : DbContext
|
|||
modelBuilder.Entity<FlashcardExampleImage>().ToTable("flashcard_example_images");
|
||||
modelBuilder.Entity<ImageGenerationRequest>().ToTable("image_generation_requests");
|
||||
modelBuilder.Entity<OptionsVocabulary>().ToTable("options_vocabularies");
|
||||
modelBuilder.Entity<SentenceAnalysisCache>().ToTable("sentence_analysis_cache");
|
||||
modelBuilder.Entity<WordQueryUsageStats>().ToTable("word_query_usage_stats");
|
||||
|
||||
// 配置屬性名稱 (snake_case)
|
||||
ConfigureUserEntity(modelBuilder);
|
||||
ConfigureUserSettingsEntity(modelBuilder);
|
||||
ConfigureFlashcardEntity(modelBuilder);
|
||||
// ConfigureStudyEntities 已移除 - StudyRecord 實體已清理
|
||||
ConfigureTagEntities(modelBuilder);
|
||||
ConfigureErrorReportEntity(modelBuilder);
|
||||
ConfigureDailyStatsEntity(modelBuilder);
|
||||
ConfigureSentenceAnalysisCacheEntity(modelBuilder);
|
||||
ConfigureWordQueryUsageStatsEntity(modelBuilder);
|
||||
ConfigureAudioEntities(modelBuilder);
|
||||
ConfigureImageGenerationEntities(modelBuilder);
|
||||
ConfigureOptionsVocabularyEntity(modelBuilder);
|
||||
|
|
@ -79,6 +84,7 @@ public class DramaLingDbContext : DbContext
|
|||
private void ConfigureUserEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var userEntity = modelBuilder.Entity<User>();
|
||||
userEntity.Property(u => u.Id).HasColumnName("id");
|
||||
userEntity.Property(u => u.Username).HasColumnName("username");
|
||||
userEntity.Property(u => u.Email).HasColumnName("email");
|
||||
userEntity.Property(u => u.PasswordHash).HasColumnName("password_hash");
|
||||
|
|
@ -108,8 +114,14 @@ public class DramaLingDbContext : DbContext
|
|||
private void ConfigureFlashcardEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var flashcardEntity = modelBuilder.Entity<Flashcard>();
|
||||
flashcardEntity.Property(f => f.Id).HasColumnName("id");
|
||||
flashcardEntity.Property(f => f.UserId).HasColumnName("user_id");
|
||||
flashcardEntity.Property(f => f.Word).HasColumnName("word");
|
||||
flashcardEntity.Property(f => f.Translation).HasColumnName("translation");
|
||||
flashcardEntity.Property(f => f.Definition).HasColumnName("definition");
|
||||
flashcardEntity.Property(f => f.PartOfSpeech).HasColumnName("part_of_speech");
|
||||
flashcardEntity.Property(f => f.Pronunciation).HasColumnName("pronunciation");
|
||||
flashcardEntity.Property(f => f.Example).HasColumnName("example");
|
||||
flashcardEntity.Property(f => f.ExampleTranslation).HasColumnName("example_translation");
|
||||
// 已刪除的復習相關屬性配置
|
||||
// EasinessFactor, IntervalDays, NextReviewDate, MasteryLevel,
|
||||
|
|
@ -126,7 +138,10 @@ public class DramaLingDbContext : DbContext
|
|||
private void ConfigureTagEntities(ModelBuilder modelBuilder)
|
||||
{
|
||||
var tagEntity = modelBuilder.Entity<Tag>();
|
||||
tagEntity.Property(t => t.Id).HasColumnName("id");
|
||||
tagEntity.Property(t => t.UserId).HasColumnName("user_id");
|
||||
tagEntity.Property(t => t.Name).HasColumnName("name");
|
||||
tagEntity.Property(t => t.Color).HasColumnName("color");
|
||||
tagEntity.Property(t => t.UsageCount).HasColumnName("usage_count");
|
||||
tagEntity.Property(t => t.CreatedAt).HasColumnName("created_at");
|
||||
|
||||
|
|
@ -138,10 +153,13 @@ public class DramaLingDbContext : DbContext
|
|||
private void ConfigureErrorReportEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var errorEntity = modelBuilder.Entity<ErrorReport>();
|
||||
errorEntity.Property(e => e.Id).HasColumnName("id");
|
||||
errorEntity.Property(e => e.UserId).HasColumnName("user_id");
|
||||
errorEntity.Property(e => e.FlashcardId).HasColumnName("flashcard_id");
|
||||
errorEntity.Property(e => e.ReportType).HasColumnName("report_type");
|
||||
errorEntity.Property(e => e.Description).HasColumnName("description");
|
||||
errorEntity.Property(e => e.StudyMode).HasColumnName("study_mode");
|
||||
errorEntity.Property(e => e.Status).HasColumnName("status");
|
||||
errorEntity.Property(e => e.AdminNotes).HasColumnName("admin_notes");
|
||||
errorEntity.Property(e => e.ResolvedAt).HasColumnName("resolved_at");
|
||||
errorEntity.Property(e => e.ResolvedBy).HasColumnName("resolved_by");
|
||||
|
|
@ -151,7 +169,9 @@ public class DramaLingDbContext : DbContext
|
|||
private void ConfigureDailyStatsEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var statsEntity = modelBuilder.Entity<DailyStats>();
|
||||
statsEntity.Property(d => d.Id).HasColumnName("id");
|
||||
statsEntity.Property(d => d.UserId).HasColumnName("user_id");
|
||||
statsEntity.Property(d => d.Date).HasColumnName("date");
|
||||
statsEntity.Property(d => d.WordsStudied).HasColumnName("words_studied");
|
||||
statsEntity.Property(d => d.WordsCorrect).HasColumnName("words_correct");
|
||||
statsEntity.Property(d => d.StudyTimeSeconds).HasColumnName("study_time_seconds");
|
||||
|
|
@ -161,6 +181,54 @@ public class DramaLingDbContext : DbContext
|
|||
statsEntity.Property(d => d.CreatedAt).HasColumnName("created_at");
|
||||
}
|
||||
|
||||
private void ConfigureUserSettingsEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var settingsEntity = modelBuilder.Entity<UserSettings>();
|
||||
settingsEntity.Property(us => us.Id).HasColumnName("id");
|
||||
settingsEntity.Property(us => us.UserId).HasColumnName("user_id");
|
||||
settingsEntity.Property(us => us.DailyGoal).HasColumnName("daily_goal");
|
||||
settingsEntity.Property(us => us.ReminderTime).HasColumnName("reminder_time");
|
||||
settingsEntity.Property(us => us.ReminderEnabled).HasColumnName("reminder_enabled");
|
||||
settingsEntity.Property(us => us.DifficultyPreference).HasColumnName("difficulty_preference");
|
||||
settingsEntity.Property(us => us.AutoPlayAudio).HasColumnName("auto_play_audio");
|
||||
settingsEntity.Property(us => us.ShowPronunciation).HasColumnName("show_pronunciation");
|
||||
settingsEntity.Property(us => us.CreatedAt).HasColumnName("created_at");
|
||||
settingsEntity.Property(us => us.UpdatedAt).HasColumnName("updated_at");
|
||||
}
|
||||
|
||||
private void ConfigureSentenceAnalysisCacheEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var cacheEntity = modelBuilder.Entity<SentenceAnalysisCache>();
|
||||
cacheEntity.Property(sac => sac.Id).HasColumnName("id");
|
||||
cacheEntity.Property(sac => sac.InputTextHash).HasColumnName("input_text_hash");
|
||||
cacheEntity.Property(sac => sac.InputText).HasColumnName("input_text");
|
||||
cacheEntity.Property(sac => sac.CorrectedText).HasColumnName("corrected_text");
|
||||
cacheEntity.Property(sac => sac.HasGrammarErrors).HasColumnName("has_grammar_errors");
|
||||
cacheEntity.Property(sac => sac.GrammarCorrections).HasColumnName("grammar_corrections");
|
||||
cacheEntity.Property(sac => sac.AnalysisResult).HasColumnName("analysis_result");
|
||||
cacheEntity.Property(sac => sac.HighValueWords).HasColumnName("high_value_words");
|
||||
cacheEntity.Property(sac => sac.IdiomsDetected).HasColumnName("idioms_detected");
|
||||
cacheEntity.Property(sac => sac.CreatedAt).HasColumnName("created_at");
|
||||
cacheEntity.Property(sac => sac.ExpiresAt).HasColumnName("expires_at");
|
||||
cacheEntity.Property(sac => sac.AccessCount).HasColumnName("access_count");
|
||||
cacheEntity.Property(sac => sac.LastAccessedAt).HasColumnName("last_accessed_at");
|
||||
}
|
||||
|
||||
private void ConfigureWordQueryUsageStatsEntity(ModelBuilder modelBuilder)
|
||||
{
|
||||
var statsEntity = modelBuilder.Entity<WordQueryUsageStats>();
|
||||
statsEntity.Property(wq => wq.Id).HasColumnName("id");
|
||||
statsEntity.Property(wq => wq.UserId).HasColumnName("user_id");
|
||||
statsEntity.Property(wq => wq.Date).HasColumnName("date");
|
||||
statsEntity.Property(wq => wq.SentenceAnalysisCount).HasColumnName("sentence_analysis_count");
|
||||
statsEntity.Property(wq => wq.HighValueWordClicks).HasColumnName("high_value_word_clicks");
|
||||
statsEntity.Property(wq => wq.LowValueWordClicks).HasColumnName("low_value_word_clicks");
|
||||
statsEntity.Property(wq => wq.TotalApiCalls).HasColumnName("total_api_calls");
|
||||
statsEntity.Property(wq => wq.UniqueWordsQueried).HasColumnName("unique_words_queried");
|
||||
statsEntity.Property(wq => wq.CreatedAt).HasColumnName("created_at");
|
||||
statsEntity.Property(wq => wq.UpdatedAt).HasColumnName("updated_at");
|
||||
}
|
||||
|
||||
private void ConfigureRelationships(ModelBuilder modelBuilder)
|
||||
{
|
||||
// User relationships
|
||||
|
|
@ -255,8 +323,10 @@ public class DramaLingDbContext : DbContext
|
|||
{
|
||||
// AudioCache configuration
|
||||
var audioCacheEntity = modelBuilder.Entity<AudioCache>();
|
||||
audioCacheEntity.Property(ac => ac.Id).HasColumnName("id");
|
||||
audioCacheEntity.Property(ac => ac.TextHash).HasColumnName("text_hash");
|
||||
audioCacheEntity.Property(ac => ac.TextContent).HasColumnName("text_content");
|
||||
audioCacheEntity.Property(ac => ac.Accent).HasColumnName("accent");
|
||||
audioCacheEntity.Property(ac => ac.VoiceId).HasColumnName("voice_id");
|
||||
audioCacheEntity.Property(ac => ac.AudioUrl).HasColumnName("audio_url");
|
||||
audioCacheEntity.Property(ac => ac.FileSize).HasColumnName("file_size");
|
||||
|
|
@ -274,6 +344,7 @@ public class DramaLingDbContext : DbContext
|
|||
|
||||
// PronunciationAssessment configuration
|
||||
var pronunciationEntity = modelBuilder.Entity<PronunciationAssessment>();
|
||||
pronunciationEntity.Property(pa => pa.Id).HasColumnName("id");
|
||||
pronunciationEntity.Property(pa => pa.UserId).HasColumnName("user_id");
|
||||
pronunciationEntity.Property(pa => pa.FlashcardId).HasColumnName("flashcard_id");
|
||||
pronunciationEntity.Property(pa => pa.TargetText).HasColumnName("target_text");
|
||||
|
|
@ -296,6 +367,7 @@ public class DramaLingDbContext : DbContext
|
|||
|
||||
// UserAudioPreferences configuration
|
||||
var audioPrefsEntity = modelBuilder.Entity<UserAudioPreferences>();
|
||||
audioPrefsEntity.Property(uap => uap.UserId).HasColumnName("user_id");
|
||||
audioPrefsEntity.Property(uap => uap.PreferredAccent).HasColumnName("preferred_accent");
|
||||
audioPrefsEntity.Property(uap => uap.PreferredVoiceMale).HasColumnName("preferred_voice_male");
|
||||
audioPrefsEntity.Property(uap => uap.PreferredVoiceFemale).HasColumnName("preferred_voice_female");
|
||||
|
|
@ -336,6 +408,7 @@ public class DramaLingDbContext : DbContext
|
|||
{
|
||||
// ExampleImage configuration
|
||||
var exampleImageEntity = modelBuilder.Entity<ExampleImage>();
|
||||
exampleImageEntity.Property(ei => ei.Id).HasColumnName("id");
|
||||
exampleImageEntity.Property(ei => ei.RelativePath).HasColumnName("relative_path");
|
||||
exampleImageEntity.Property(ei => ei.AltText).HasColumnName("alt_text");
|
||||
exampleImageEntity.Property(ei => ei.GeminiPrompt).HasColumnName("gemini_prompt");
|
||||
|
|
@ -371,6 +444,7 @@ public class DramaLingDbContext : DbContext
|
|||
|
||||
// ImageGenerationRequest configuration
|
||||
var generationRequestEntity = modelBuilder.Entity<ImageGenerationRequest>();
|
||||
generationRequestEntity.Property(igr => igr.Id).HasColumnName("id");
|
||||
generationRequestEntity.Property(igr => igr.UserId).HasColumnName("user_id");
|
||||
generationRequestEntity.Property(igr => igr.FlashcardId).HasColumnName("flashcard_id");
|
||||
generationRequestEntity.Property(igr => igr.OverallStatus).HasColumnName("overall_status");
|
||||
|
|
@ -433,6 +507,8 @@ public class DramaLingDbContext : DbContext
|
|||
var optionsVocabEntity = modelBuilder.Entity<OptionsVocabulary>();
|
||||
|
||||
// Configure column names (snake_case)
|
||||
optionsVocabEntity.Property(ov => ov.Id).HasColumnName("id");
|
||||
optionsVocabEntity.Property(ov => ov.Word).HasColumnName("word");
|
||||
optionsVocabEntity.Property(ov => ov.CEFRLevel).HasColumnName("cefr_level");
|
||||
optionsVocabEntity.Property(ov => ov.PartOfSpeech).HasColumnName("part_of_speech");
|
||||
optionsVocabEntity.Property(ov => ov.WordLength).HasColumnName("word_length");
|
||||
|
|
|
|||
|
|
@ -69,27 +69,35 @@ public static class ServiceCollectionExtensions
|
|||
services.AddSingleton<ICacheStrategyManager, CacheStrategyManager>();
|
||||
services.AddScoped<IDatabaseCacheManager, DatabaseCacheManager>();
|
||||
|
||||
// 快取提供者
|
||||
services.AddScoped<ICacheProvider>(provider =>
|
||||
new MemoryCacheProvider(
|
||||
provider.GetRequiredService<Microsoft.Extensions.Caching.Memory.IMemoryCache>(),
|
||||
provider.GetRequiredService<ILogger<MemoryCacheProvider>>()));
|
||||
|
||||
services.AddScoped<ICacheProvider>(provider =>
|
||||
// 快取提供者 - 使用具名註冊
|
||||
services.AddScoped<ICacheService>(provider =>
|
||||
{
|
||||
var memoryCache = provider.GetRequiredService<Microsoft.Extensions.Caching.Memory.IMemoryCache>();
|
||||
var logger = provider.GetRequiredService<ILogger<HybridCacheService>>();
|
||||
var databaseCacheManager = provider.GetRequiredService<IDatabaseCacheManager>();
|
||||
var strategyManager = provider.GetRequiredService<ICacheStrategyManager>();
|
||||
|
||||
var memoryProvider = new MemoryCacheProvider(
|
||||
memoryCache,
|
||||
provider.GetRequiredService<ILogger<MemoryCacheProvider>>());
|
||||
|
||||
ICacheProvider? distributedProvider = null;
|
||||
var distributedCache = provider.GetService<Microsoft.Extensions.Caching.Distributed.IDistributedCache>();
|
||||
if (distributedCache != null)
|
||||
{
|
||||
return new DistributedCacheProvider(
|
||||
distributedProvider = new DistributedCacheProvider(
|
||||
distributedCache,
|
||||
provider.GetRequiredService<ICacheSerializer>(),
|
||||
provider.GetRequiredService<ILogger<DistributedCacheProvider>>());
|
||||
}
|
||||
return null!;
|
||||
});
|
||||
|
||||
// 主要快取服務
|
||||
services.AddScoped<ICacheService, HybridCacheService>();
|
||||
return new HybridCacheService(
|
||||
memoryProvider,
|
||||
distributedProvider,
|
||||
databaseCacheManager,
|
||||
strategyManager,
|
||||
logger);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
|
|
|||
1201
backend/DramaLing.Api/Migrations/20250930081940_FixFlashcardColumnNaming.Designer.cs
generated
Normal file
1201
backend/DramaLing.Api/Migrations/20250930081940_FixFlashcardColumnNaming.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,428 @@
|
|||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DramaLing.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixFlashcardColumnNaming : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_pronunciation_assessments_study_sessions_study_session_id",
|
||||
table: "pronunciation_assessments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "study_records");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "test_results");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "study_cards");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "study_sessions");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_PronunciationAssessment_Session",
|
||||
table: "pronunciation_assessments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "study_session_id",
|
||||
table: "pronunciation_assessments");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "FilledQuestionText",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "LastQuestionType",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Repetitions",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ReviewHistory",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Synonyms",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "easiness_factor",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "interval_days",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "last_reviewed_at",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "mastery_level",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "next_review_date",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "times_correct",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "times_reviewed",
|
||||
table: "flashcards");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Word",
|
||||
table: "flashcards",
|
||||
newName: "word");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Translation",
|
||||
table: "flashcards",
|
||||
newName: "translation");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Pronunciation",
|
||||
table: "flashcards",
|
||||
newName: "pronunciation");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Example",
|
||||
table: "flashcards",
|
||||
newName: "example");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Definition",
|
||||
table: "flashcards",
|
||||
newName: "definition");
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "definition",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "word",
|
||||
table: "flashcards",
|
||||
newName: "Word");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "translation",
|
||||
table: "flashcards",
|
||||
newName: "Translation");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "pronunciation",
|
||||
table: "flashcards",
|
||||
newName: "Pronunciation");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "example",
|
||||
table: "flashcards",
|
||||
newName: "Example");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "definition",
|
||||
table: "flashcards",
|
||||
newName: "Definition");
|
||||
|
||||
migrationBuilder.AddColumn<Guid>(
|
||||
name: "study_session_id",
|
||||
table: "pronunciation_assessments",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "Definition",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: "",
|
||||
oldClrType: typeof(string),
|
||||
oldType: "TEXT",
|
||||
oldNullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "FilledQuestionText",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
maxLength: 1000,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "LastQuestionType",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
maxLength: 50,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Repetitions",
|
||||
table: "flashcards",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "ReviewHistory",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Synonyms",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
maxLength: 2000,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<float>(
|
||||
name: "easiness_factor",
|
||||
table: "flashcards",
|
||||
type: "REAL",
|
||||
nullable: false,
|
||||
defaultValue: 0f);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "interval_days",
|
||||
table: "flashcards",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "last_reviewed_at",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "mastery_level",
|
||||
table: "flashcards",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "next_review_date",
|
||||
table: "flashcards",
|
||||
type: "TEXT",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "times_correct",
|
||||
table: "flashcards",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "times_reviewed",
|
||||
table: "flashcards",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "study_sessions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
user_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
average_response_time_ms = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CompletedCards = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CompletedTests = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
correct_count = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CurrentCardIndex = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CurrentTestType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
duration_seconds = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ended_at = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
session_type = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
started_at = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
total_cards = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TotalTests = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_study_sessions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_study_sessions_user_profiles_user_id",
|
||||
column: x => x.user_id,
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "study_cards",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
FlashcardId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
StudySessionId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
CompletedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
IsCompleted = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
Order = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
PlannedTests = table.Column<string>(type: "TEXT", nullable: false),
|
||||
PlannedTestsJson = table.Column<string>(type: "TEXT", nullable: false),
|
||||
StartedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Word = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_study_cards", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_study_cards_flashcards_FlashcardId",
|
||||
column: x => x.FlashcardId,
|
||||
principalTable: "flashcards",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_study_cards_study_sessions_StudySessionId",
|
||||
column: x => x.StudySessionId,
|
||||
principalTable: "study_sessions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "study_records",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
flashcard_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
session_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
user_id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
is_correct = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
NewEasinessFactor = table.Column<float>(type: "REAL", nullable: false),
|
||||
NewIntervalDays = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
NewRepetitions = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
NextReviewDate = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
PreviousEasinessFactor = table.Column<float>(type: "REAL", nullable: false),
|
||||
PreviousIntervalDays = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
PreviousRepetitions = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
quality_rating = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
response_time_ms = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
studied_at = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
study_mode = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
user_answer = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_study_records", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_study_records_flashcards_flashcard_id",
|
||||
column: x => x.flashcard_id,
|
||||
principalTable: "flashcards",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_study_records_study_sessions_session_id",
|
||||
column: x => x.session_id,
|
||||
principalTable: "study_sessions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_study_records_user_profiles_user_id",
|
||||
column: x => x.user_id,
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "test_results",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
StudyCardId = table.Column<Guid>(type: "TEXT", nullable: false),
|
||||
CompletedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
ConfidenceLevel = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
IsCorrect = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ResponseTimeMs = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TestType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
UserAnswer = table.Column<string>(type: "TEXT", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_test_results", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_test_results_study_cards_StudyCardId",
|
||||
column: x => x.StudyCardId,
|
||||
principalTable: "study_cards",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PronunciationAssessment_Session",
|
||||
table: "pronunciation_assessments",
|
||||
column: "study_session_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_study_cards_FlashcardId",
|
||||
table: "study_cards",
|
||||
column: "FlashcardId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_study_cards_StudySessionId",
|
||||
table: "study_cards",
|
||||
column: "StudySessionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_study_records_flashcard_id",
|
||||
table: "study_records",
|
||||
column: "flashcard_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_study_records_session_id",
|
||||
table: "study_records",
|
||||
column: "session_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_StudyRecord_UserCard_TestType_Unique",
|
||||
table: "study_records",
|
||||
columns: new[] { "user_id", "flashcard_id", "study_mode" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_study_sessions_user_id",
|
||||
table: "study_sessions",
|
||||
column: "user_id");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_test_results_StudyCardId",
|
||||
table: "test_results",
|
||||
column: "StudyCardId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_pronunciation_assessments_study_sessions_study_session_id",
|
||||
table: "pronunciation_assessments",
|
||||
column: "study_session_id",
|
||||
principalTable: "study_sessions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
1244
backend/DramaLing.Api/Migrations/20250930083856_CompleteSnakeCaseNaming.Designer.cs
generated
Normal file
1244
backend/DramaLing.Api/Migrations/20250930083856_CompleteSnakeCaseNaming.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,516 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DramaLing.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class CompleteSnakeCaseNaming : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_user_settings_user_profiles_UserId",
|
||||
table: "user_settings");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_WordQueryUsageStats_user_profiles_UserId",
|
||||
table: "WordQueryUsageStats");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Date",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "date");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "UserId",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "user_id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "UpdatedAt",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "updated_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "UniqueWordsQueried",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "unique_words_queried");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "TotalApiCalls",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "total_api_calls");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "SentenceAnalysisCount",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "sentence_analysis_count");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "LowValueWordClicks",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "low_value_word_clicks");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "HighValueWordClicks",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "high_value_word_clicks");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "CreatedAt",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "created_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "user_settings",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "UserId",
|
||||
table: "user_settings",
|
||||
newName: "user_id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "UpdatedAt",
|
||||
table: "user_settings",
|
||||
newName: "updated_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ShowPronunciation",
|
||||
table: "user_settings",
|
||||
newName: "show_pronunciation");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ReminderTime",
|
||||
table: "user_settings",
|
||||
newName: "reminder_time");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ReminderEnabled",
|
||||
table: "user_settings",
|
||||
newName: "reminder_enabled");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "DifficultyPreference",
|
||||
table: "user_settings",
|
||||
newName: "difficulty_preference");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "DailyGoal",
|
||||
table: "user_settings",
|
||||
newName: "daily_goal");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "CreatedAt",
|
||||
table: "user_settings",
|
||||
newName: "created_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "AutoPlayAudio",
|
||||
table: "user_settings",
|
||||
newName: "auto_play_audio");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_user_settings_UserId",
|
||||
table: "user_settings",
|
||||
newName: "IX_user_settings_user_id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "user_profiles",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Name",
|
||||
table: "tags",
|
||||
newName: "name");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Color",
|
||||
table: "tags",
|
||||
newName: "color");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "tags",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "LastAccessedAt",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "last_accessed_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "InputTextHash",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "input_text_hash");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "InputText",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "input_text");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "IdiomsDetected",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "idioms_detected");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "HighValueWords",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "high_value_words");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "HasGrammarErrors",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "has_grammar_errors");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "GrammarCorrections",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "grammar_corrections");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ExpiresAt",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "expires_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "CreatedAt",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "created_at");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "CorrectedText",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "corrected_text");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "AnalysisResult",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "analysis_result");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "AccessCount",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "access_count");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "flashcards",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Status",
|
||||
table: "error_reports",
|
||||
newName: "status");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Description",
|
||||
table: "error_reports",
|
||||
newName: "description");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "error_reports",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Date",
|
||||
table: "daily_stats",
|
||||
newName: "date");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "daily_stats",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_daily_stats_user_id_Date",
|
||||
table: "daily_stats",
|
||||
newName: "IX_daily_stats_user_id_date");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_user_settings_user_profiles_user_id",
|
||||
table: "user_settings",
|
||||
column: "user_id",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_WordQueryUsageStats_user_profiles_user_id",
|
||||
table: "WordQueryUsageStats",
|
||||
column: "user_id",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_user_settings_user_profiles_user_id",
|
||||
table: "user_settings");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_WordQueryUsageStats_user_profiles_user_id",
|
||||
table: "WordQueryUsageStats");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "date",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "Date");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "user_id",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "UserId");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "updated_at",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "UpdatedAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "unique_words_queried",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "UniqueWordsQueried");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "total_api_calls",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "TotalApiCalls");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "sentence_analysis_count",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "SentenceAnalysisCount");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "low_value_word_clicks",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "LowValueWordClicks");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "high_value_word_clicks",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "HighValueWordClicks");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "created_at",
|
||||
table: "WordQueryUsageStats",
|
||||
newName: "CreatedAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "user_settings",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "user_id",
|
||||
table: "user_settings",
|
||||
newName: "UserId");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "updated_at",
|
||||
table: "user_settings",
|
||||
newName: "UpdatedAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "show_pronunciation",
|
||||
table: "user_settings",
|
||||
newName: "ShowPronunciation");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "reminder_time",
|
||||
table: "user_settings",
|
||||
newName: "ReminderTime");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "reminder_enabled",
|
||||
table: "user_settings",
|
||||
newName: "ReminderEnabled");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "difficulty_preference",
|
||||
table: "user_settings",
|
||||
newName: "DifficultyPreference");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "daily_goal",
|
||||
table: "user_settings",
|
||||
newName: "DailyGoal");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "created_at",
|
||||
table: "user_settings",
|
||||
newName: "CreatedAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "auto_play_audio",
|
||||
table: "user_settings",
|
||||
newName: "AutoPlayAudio");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_user_settings_user_id",
|
||||
table: "user_settings",
|
||||
newName: "IX_user_settings_UserId");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "user_profiles",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "name",
|
||||
table: "tags",
|
||||
newName: "Name");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "color",
|
||||
table: "tags",
|
||||
newName: "Color");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "tags",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "last_accessed_at",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "LastAccessedAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "input_text_hash",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "InputTextHash");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "input_text",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "InputText");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "idioms_detected",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "IdiomsDetected");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "high_value_words",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "HighValueWords");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "has_grammar_errors",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "HasGrammarErrors");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "grammar_corrections",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "GrammarCorrections");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "expires_at",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "ExpiresAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "created_at",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "CreatedAt");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "corrected_text",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "CorrectedText");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "analysis_result",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "AnalysisResult");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "access_count",
|
||||
table: "SentenceAnalysisCache",
|
||||
newName: "AccessCount");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "flashcards",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "status",
|
||||
table: "error_reports",
|
||||
newName: "Status");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "description",
|
||||
table: "error_reports",
|
||||
newName: "Description");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "error_reports",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "date",
|
||||
table: "daily_stats",
|
||||
newName: "Date");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "daily_stats",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameIndex(
|
||||
name: "IX_daily_stats_user_id_date",
|
||||
table: "daily_stats",
|
||||
newName: "IX_daily_stats_user_id_Date");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_user_settings_user_profiles_UserId",
|
||||
table: "user_settings",
|
||||
column: "UserId",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_WordQueryUsageStats_user_profiles_UserId",
|
||||
table: "WordQueryUsageStats",
|
||||
column: "UserId",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
1252
backend/DramaLing.Api/Migrations/20250930084945_FinalPascalCaseFieldsFix.Designer.cs
generated
Normal file
1252
backend/DramaLing.Api/Migrations/20250930084945_FinalPascalCaseFieldsFix.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,122 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DramaLing.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FinalPascalCaseFieldsFix : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_user_audio_preferences_user_profiles_UserId",
|
||||
table: "user_audio_preferences");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "UserId",
|
||||
table: "user_audio_preferences",
|
||||
newName: "user_id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "pronunciation_assessments",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Word",
|
||||
table: "options_vocabularies",
|
||||
newName: "word");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "options_vocabularies",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "image_generation_requests",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "example_images",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Accent",
|
||||
table: "audio_cache",
|
||||
newName: "accent");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "Id",
|
||||
table: "audio_cache",
|
||||
newName: "id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_user_audio_preferences_user_profiles_user_id",
|
||||
table: "user_audio_preferences",
|
||||
column: "user_id",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_user_audio_preferences_user_profiles_user_id",
|
||||
table: "user_audio_preferences");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "user_id",
|
||||
table: "user_audio_preferences",
|
||||
newName: "UserId");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "pronunciation_assessments",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "word",
|
||||
table: "options_vocabularies",
|
||||
newName: "Word");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "options_vocabularies",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "image_generation_requests",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "example_images",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "accent",
|
||||
table: "audio_cache",
|
||||
newName: "Accent");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "id",
|
||||
table: "audio_cache",
|
||||
newName: "Id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_user_audio_preferences_user_profiles_UserId",
|
||||
table: "user_audio_preferences",
|
||||
column: "UserId",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
1252
backend/DramaLing.Api/Migrations/20250930085131_FixTableNamesToSnakeCase.Designer.cs
generated
Normal file
1252
backend/DramaLing.Api/Migrations/20250930085131_FixTableNamesToSnakeCase.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,94 @@
|
|||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DramaLing.Api.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class FixTableNamesToSnakeCase : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_WordQueryUsageStats_user_profiles_user_id",
|
||||
table: "WordQueryUsageStats");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_WordQueryUsageStats",
|
||||
table: "WordQueryUsageStats");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_SentenceAnalysisCache",
|
||||
table: "SentenceAnalysisCache");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "WordQueryUsageStats",
|
||||
newName: "word_query_usage_stats");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "SentenceAnalysisCache",
|
||||
newName: "sentence_analysis_cache");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_word_query_usage_stats",
|
||||
table: "word_query_usage_stats",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_sentence_analysis_cache",
|
||||
table: "sentence_analysis_cache",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_word_query_usage_stats_user_profiles_user_id",
|
||||
table: "word_query_usage_stats",
|
||||
column: "user_id",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_word_query_usage_stats_user_profiles_user_id",
|
||||
table: "word_query_usage_stats");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_word_query_usage_stats",
|
||||
table: "word_query_usage_stats");
|
||||
|
||||
migrationBuilder.DropPrimaryKey(
|
||||
name: "PK_sentence_analysis_cache",
|
||||
table: "sentence_analysis_cache");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "word_query_usage_stats",
|
||||
newName: "WordQueryUsageStats");
|
||||
|
||||
migrationBuilder.RenameTable(
|
||||
name: "sentence_analysis_cache",
|
||||
newName: "SentenceAnalysisCache");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_WordQueryUsageStats",
|
||||
table: "WordQueryUsageStats",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddPrimaryKey(
|
||||
name: "PK_SentenceAnalysisCache",
|
||||
table: "SentenceAnalysisCache",
|
||||
column: "id");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_WordQueryUsageStats_user_profiles_user_id",
|
||||
table: "WordQueryUsageStats",
|
||||
column: "user_id",
|
||||
principalTable: "user_profiles",
|
||||
principalColumn: "id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,12 +21,14 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Accent")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("accent");
|
||||
|
||||
b.Property<int>("AccessCount")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -86,7 +88,8 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AiApiCalls")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -101,7 +104,8 @@ namespace DramaLing.Api.Migrations
|
|||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("date");
|
||||
|
||||
b.Property<int>("SessionCount")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -135,7 +139,8 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AdminNotes")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -146,7 +151,8 @@ namespace DramaLing.Api.Migrations
|
|||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<Guid>("FlashcardId")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -169,7 +175,8 @@ namespace DramaLing.Api.Migrations
|
|||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("status");
|
||||
|
||||
b.Property<string>("StudyMode")
|
||||
.HasMaxLength(50)
|
||||
|
|
@ -195,7 +202,8 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AccessCount")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -299,40 +307,30 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<string>("Definition")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("definition");
|
||||
|
||||
b.Property<string>("DifficultyLevel")
|
||||
.HasMaxLength(10)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("difficulty_level");
|
||||
|
||||
b.Property<float>("EasinessFactor")
|
||||
.HasColumnType("REAL")
|
||||
.HasColumnName("easiness_factor");
|
||||
|
||||
b.Property<string>("Example")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("example");
|
||||
|
||||
b.Property<string>("ExampleTranslation")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("example_translation");
|
||||
|
||||
b.Property<string>("FilledQuestionText")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("IntervalDays")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("interval_days");
|
||||
|
||||
b.Property<bool>("IsArchived")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("is_archived");
|
||||
|
|
@ -341,22 +339,6 @@ namespace DramaLing.Api.Migrations
|
|||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("is_favorite");
|
||||
|
||||
b.Property<string>("LastQuestionType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("LastReviewedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_reviewed_at");
|
||||
|
||||
b.Property<int>("MasteryLevel")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("mastery_level");
|
||||
|
||||
b.Property<DateTime>("NextReviewDate")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("next_review_date");
|
||||
|
||||
b.Property<string>("PartOfSpeech")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -364,29 +346,13 @@ namespace DramaLing.Api.Migrations
|
|||
|
||||
b.Property<string>("Pronunciation")
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Repetitions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ReviewHistory")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Synonyms")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TimesCorrect")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("times_correct");
|
||||
|
||||
b.Property<int>("TimesReviewed")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("times_reviewed");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("pronunciation");
|
||||
|
||||
b.Property<string>("Translation")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("translation");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -399,7 +365,8 @@ namespace DramaLing.Api.Migrations
|
|||
b.Property<string>("Word")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("word");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
|
@ -462,7 +429,8 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime?>("CompletedAt")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -582,7 +550,8 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("CEFRLevel")
|
||||
.IsRequired()
|
||||
|
|
@ -617,7 +586,8 @@ namespace DramaLing.Api.Migrations
|
|||
b.Property<string>("Word")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("word");
|
||||
|
||||
b.Property<int>("WordLength")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -645,7 +615,8 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<decimal>("AccuracyScore")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -689,10 +660,6 @@ namespace DramaLing.Api.Migrations
|
|||
.HasColumnType("TEXT")
|
||||
.HasColumnName("prosody_score");
|
||||
|
||||
b.Property<Guid?>("StudySessionId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("study_session_id");
|
||||
|
||||
b.Property<string>("Suggestions")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("suggestions");
|
||||
|
|
@ -710,9 +677,6 @@ namespace DramaLing.Api.Migrations
|
|||
|
||||
b.HasIndex("FlashcardId");
|
||||
|
||||
b.HasIndex("StudySessionId")
|
||||
.HasDatabaseName("IX_PronunciationAssessment_Session");
|
||||
|
||||
b.HasIndex("UserId", "FlashcardId")
|
||||
.HasDatabaseName("IX_PronunciationAssessment_UserFlashcard");
|
||||
|
||||
|
|
@ -723,49 +687,62 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<int>("AccessCount")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("access_count");
|
||||
|
||||
b.Property<string>("AnalysisResult")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("analysis_result");
|
||||
|
||||
b.Property<string>("CorrectedText")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("corrected_text");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("expires_at");
|
||||
|
||||
b.Property<string>("GrammarCorrections")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("grammar_corrections");
|
||||
|
||||
b.Property<bool>("HasGrammarErrors")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("has_grammar_errors");
|
||||
|
||||
b.Property<string>("HighValueWords")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("high_value_words");
|
||||
|
||||
b.Property<string>("IdiomsDetected")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("idioms_detected");
|
||||
|
||||
b.Property<string>("InputText")
|
||||
.IsRequired()
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("input_text");
|
||||
|
||||
b.Property<string>("InputTextHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("input_text_hash");
|
||||
|
||||
b.Property<DateTime?>("LastAccessedAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("last_accessed_at");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
|
@ -778,209 +755,21 @@ namespace DramaLing.Api.Migrations
|
|||
b.HasIndex("InputTextHash", "ExpiresAt")
|
||||
.HasDatabaseName("IX_SentenceAnalysisCache_Hash_Expires");
|
||||
|
||||
b.ToTable("SentenceAnalysisCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudyCard", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("CompletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("FlashcardId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsCompleted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Order")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("PlannedTests")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PlannedTestsJson")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("StudySessionId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Word")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FlashcardId");
|
||||
|
||||
b.HasIndex("StudySessionId");
|
||||
|
||||
b.ToTable("study_cards", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudyRecord", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<Guid>("FlashcardId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("flashcard_id");
|
||||
|
||||
b.Property<bool>("IsCorrect")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("is_correct");
|
||||
|
||||
b.Property<float>("NewEasinessFactor")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("NewIntervalDays")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("NewRepetitions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("NextReviewDate")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<float>("PreviousEasinessFactor")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("PreviousIntervalDays")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PreviousRepetitions")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("QualityRating")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("quality_rating");
|
||||
|
||||
b.Property<int?>("ResponseTimeMs")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("response_time_ms");
|
||||
|
||||
b.Property<Guid>("SessionId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("session_id");
|
||||
|
||||
b.Property<DateTime>("StudiedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("studied_at");
|
||||
|
||||
b.Property<string>("StudyMode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("study_mode");
|
||||
|
||||
b.Property<string>("UserAnswer")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_answer");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("FlashcardId");
|
||||
|
||||
b.HasIndex("SessionId");
|
||||
|
||||
b.HasIndex("UserId", "FlashcardId", "StudyMode")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_StudyRecord_UserCard_TestType_Unique");
|
||||
|
||||
b.ToTable("study_records", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudySession", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("AverageResponseTimeMs")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("average_response_time_ms");
|
||||
|
||||
b.Property<int>("CompletedCards")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CompletedTests")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("CorrectCount")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("correct_count");
|
||||
|
||||
b.Property<int>("CurrentCardIndex")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CurrentTestType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("DurationSeconds")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("duration_seconds");
|
||||
|
||||
b.Property<DateTime?>("EndedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("ended_at");
|
||||
|
||||
b.Property<string>("SessionType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("session_type");
|
||||
|
||||
b.Property<DateTime>("StartedAt")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("started_at");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("TotalCards")
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("total_cards");
|
||||
|
||||
b.Property<int>("TotalTests")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("study_sessions", (string)null);
|
||||
b.ToTable("sentence_analysis_cache", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.Tag", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("color");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -989,7 +778,8 @@ namespace DramaLing.Api.Migrations
|
|||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("name");
|
||||
|
||||
b.Property<int>("UsageCount")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -1006,47 +796,12 @@ namespace DramaLing.Api.Migrations
|
|||
b.ToTable("tags", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.TestResult", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CompletedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("ConfidenceLevel")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsCorrect")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ResponseTimeMs")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<Guid>("StudyCardId")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TestType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("UserAnswer")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StudyCardId");
|
||||
|
||||
b.ToTable("test_results", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<string>("AvatarUrl")
|
||||
.HasColumnType("TEXT")
|
||||
|
|
@ -1127,7 +882,8 @@ namespace DramaLing.Api.Migrations
|
|||
modelBuilder.Entity("DramaLing.Api.Models.Entities.UserAudioPreferences", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.Property<bool>("AutoPlayEnabled")
|
||||
.HasColumnType("INTEGER")
|
||||
|
|
@ -1180,36 +936,46 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<bool>("AutoPlayAudio")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("auto_play_audio");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<int>("DailyGoal")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("daily_goal");
|
||||
|
||||
b.Property<string>("DifficultyPreference")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("difficulty_preference");
|
||||
|
||||
b.Property<bool>("ReminderEnabled")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("reminder_enabled");
|
||||
|
||||
b.Property<TimeOnly>("ReminderTime")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("reminder_time");
|
||||
|
||||
b.Property<bool>("ShowPronunciation")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("show_pronunciation");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
|
@ -1223,34 +989,44 @@ namespace DramaLing.Api.Migrations
|
|||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("id");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
b.Property<DateOnly>("Date")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("date");
|
||||
|
||||
b.Property<int>("HighValueWordClicks")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("high_value_word_clicks");
|
||||
|
||||
b.Property<int>("LowValueWordClicks")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("low_value_word_clicks");
|
||||
|
||||
b.Property<int>("SentenceAnalysisCount")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("sentence_analysis_count");
|
||||
|
||||
b.Property<int>("TotalApiCalls")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("total_api_calls");
|
||||
|
||||
b.Property<int>("UniqueWordsQueried")
|
||||
.HasColumnType("INTEGER");
|
||||
.HasColumnType("INTEGER")
|
||||
.HasColumnName("unique_words_queried");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("updated_at");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("TEXT");
|
||||
.HasColumnType("TEXT")
|
||||
.HasColumnName("user_id");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
|
|
@ -1261,7 +1037,7 @@ namespace DramaLing.Api.Migrations
|
|||
.IsUnique()
|
||||
.HasDatabaseName("IX_WordQueryUsageStats_UserDate");
|
||||
|
||||
b.ToTable("WordQueryUsageStats");
|
||||
b.ToTable("word_query_usage_stats", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.DailyStats", b =>
|
||||
|
|
@ -1383,11 +1159,6 @@ namespace DramaLing.Api.Migrations
|
|||
.HasForeignKey("FlashcardId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("DramaLing.Api.Models.Entities.StudySession", "StudySession")
|
||||
.WithMany()
|
||||
.HasForeignKey("StudySessionId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("DramaLing.Api.Models.Entities.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
|
|
@ -1396,65 +1167,6 @@ namespace DramaLing.Api.Migrations
|
|||
|
||||
b.Navigation("Flashcard");
|
||||
|
||||
b.Navigation("StudySession");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudyCard", b =>
|
||||
{
|
||||
b.HasOne("DramaLing.Api.Models.Entities.Flashcard", "Flashcard")
|
||||
.WithMany()
|
||||
.HasForeignKey("FlashcardId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DramaLing.Api.Models.Entities.StudySession", "StudySession")
|
||||
.WithMany("StudyCards")
|
||||
.HasForeignKey("StudySessionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Flashcard");
|
||||
|
||||
b.Navigation("StudySession");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudyRecord", b =>
|
||||
{
|
||||
b.HasOne("DramaLing.Api.Models.Entities.Flashcard", "Flashcard")
|
||||
.WithMany("StudyRecords")
|
||||
.HasForeignKey("FlashcardId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DramaLing.Api.Models.Entities.StudySession", "Session")
|
||||
.WithMany("StudyRecords")
|
||||
.HasForeignKey("SessionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("DramaLing.Api.Models.Entities.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Flashcard");
|
||||
|
||||
b.Navigation("Session");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudySession", b =>
|
||||
{
|
||||
b.HasOne("DramaLing.Api.Models.Entities.User", "User")
|
||||
.WithMany("StudySessions")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
|
|
@ -1469,17 +1181,6 @@ namespace DramaLing.Api.Migrations
|
|||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.TestResult", b =>
|
||||
{
|
||||
b.HasOne("DramaLing.Api.Models.Entities.StudyCard", "StudyCard")
|
||||
.WithMany("TestResults")
|
||||
.HasForeignKey("StudyCardId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("StudyCard");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.UserAudioPreferences", b =>
|
||||
{
|
||||
b.HasOne("DramaLing.Api.Models.Entities.User", "User")
|
||||
|
|
@ -1525,20 +1226,6 @@ namespace DramaLing.Api.Migrations
|
|||
b.Navigation("FlashcardExampleImages");
|
||||
|
||||
b.Navigation("FlashcardTags");
|
||||
|
||||
b.Navigation("StudyRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudyCard", b =>
|
||||
{
|
||||
b.Navigation("TestResults");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.StudySession", b =>
|
||||
{
|
||||
b.Navigation("StudyCards");
|
||||
|
||||
b.Navigation("StudyRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("DramaLing.Api.Models.Entities.Tag", b =>
|
||||
|
|
@ -1555,8 +1242,6 @@ namespace DramaLing.Api.Migrations
|
|||
b.Navigation("Flashcards");
|
||||
|
||||
b.Navigation("Settings");
|
||||
|
||||
b.Navigation("StudySessions");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,19 +9,37 @@ public class GeminiOptions
|
|||
[Required(ErrorMessage = "Gemini API Key is required")]
|
||||
public string ApiKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// API 請求超時時間(秒)
|
||||
/// </summary>
|
||||
[Range(1, 120, ErrorMessage = "Timeout must be between 1 and 120 seconds")]
|
||||
public int TimeoutSeconds { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// API 請求最大重試次數
|
||||
/// </summary>
|
||||
[Range(1, 10, ErrorMessage = "Max retries must be between 1 and 10")]
|
||||
public int MaxRetries { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// AI 回應最大 Token 數量
|
||||
/// </summary>
|
||||
[Range(100, 10000, ErrorMessage = "Max tokens must be between 100 and 10000")]
|
||||
public int MaxOutputTokens { get; set; } = 2000;
|
||||
|
||||
/// <summary>
|
||||
/// AI 回應的隨機性程度(0.0-2.0)
|
||||
/// </summary>
|
||||
[Range(0.0, 2.0, ErrorMessage = "Temperature must be between 0 and 2")]
|
||||
public double Temperature { get; set; } = 0.7;
|
||||
|
||||
public string Model { get; set; } = "gemini-1.5-flash";
|
||||
/// <summary>
|
||||
/// 使用的 Gemini 模型名稱
|
||||
/// </summary>
|
||||
public string Model { get; set; } = "gemini-2.0-flash";
|
||||
|
||||
/// <summary>
|
||||
/// Gemini API 基本 URL
|
||||
/// </summary>
|
||||
public string BaseUrl { get; set; } = "https://generativelanguage.googleapis.com";
|
||||
}
|
||||
|
|
@ -26,17 +26,27 @@ builder.Services.Configure<ReplicateOptions>(
|
|||
builder.Configuration.GetSection(ReplicateOptions.SectionName));
|
||||
builder.Services.AddSingleton<IValidateOptions<ReplicateOptions>, ReplicateOptionsValidator>();
|
||||
|
||||
// 在開發環境設定測試用的API Key
|
||||
if (builder.Environment.IsDevelopment() &&
|
||||
string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GEMINI_API_KEY")))
|
||||
// 在開發環境設定 API Key - 修復配置讀取邏輯
|
||||
if (builder.Environment.IsDevelopment())
|
||||
{
|
||||
builder.Services.PostConfigure<GeminiOptions>(options =>
|
||||
{
|
||||
// 只有當 ApiKey 未設置時才嘗試從其他來源讀取
|
||||
if (string.IsNullOrEmpty(options.ApiKey))
|
||||
{
|
||||
options.ApiKey = Environment.GetEnvironmentVariable("GEMINI_API_KEY")
|
||||
?? builder.Configuration["Gemini:ApiKey"]
|
||||
?? "test-key";
|
||||
// 優先從環境變數讀取
|
||||
var envApiKey = Environment.GetEnvironmentVariable("GEMINI_API_KEY");
|
||||
if (!string.IsNullOrEmpty(envApiKey))
|
||||
{
|
||||
options.ApiKey = envApiKey;
|
||||
}
|
||||
// 如果環境變數沒有,Configuration 應該已經包含 user-secrets
|
||||
// 這裡只是作為後備,不應該覆蓋已經從 user-secrets 載入的設定
|
||||
else if (string.IsNullOrEmpty(builder.Configuration["Gemini:ApiKey"]))
|
||||
{
|
||||
// 只有在真的沒有任何配置時才使用測試金鑰
|
||||
options.ApiKey = "test-key";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,6 @@
|
|||
"Frontend": {
|
||||
"Urls": ["http://localhost:3000", "http://localhost:3001"]
|
||||
},
|
||||
"Gemini": {
|
||||
"ApiKey": "",
|
||||
"TimeoutSeconds": 30,
|
||||
"MaxRetries": 3,
|
||||
"MaxOutputTokens": 2000,
|
||||
"Temperature": 0.7,
|
||||
"Model": "gemini-1.5-flash",
|
||||
"BaseUrl": "https://generativelanguage.googleapis.com"
|
||||
},
|
||||
"Replicate": {
|
||||
"ApiKey": "",
|
||||
"BaseUrl": "https://api.replicate.com/v1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue