初始化
This commit is contained in:
49
FutureMailAPI/ApiTest.cs
Normal file
49
FutureMailAPI/ApiTest.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class ApiTest
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
using var client = new HttpClient();
|
||||
|
||||
// 设置请求头
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJ0ZXN0dXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5iZiI6MTc2MDUwOTEwNCwiZXhwIjoxNzYxMTEzOTA0LCJpYXQiOjE3NjA1MDkxMDQsImlzcyI6IkZ1dHVyZU1haWxBUEkiLCJhdWQiOiJGdXR1cmVNYWlsQ2xpZW50In0.122kbPX2GsD1uo2DZNnJ6M7s6AP31bm8arNm770jBG8");
|
||||
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
||||
// 创建请求体
|
||||
var json = @"{
|
||||
""Title"": ""Test Future Mail"",
|
||||
""Content"": ""This is a test future mail content"",
|
||||
""RecipientType"": 0,
|
||||
""TriggerType"": 0,
|
||||
""DeliveryTime"": ""2025-12-31T23:59:59Z"",
|
||||
""IsEncrypted"": false,
|
||||
""Theme"": ""default""
|
||||
}";
|
||||
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
// 发送POST请求
|
||||
var response = await client.PostAsync("http://localhost:5001/api/v1/mails", content);
|
||||
|
||||
// 显示响应状态
|
||||
Console.WriteLine($"状态码: {response.StatusCode}");
|
||||
|
||||
// 读取响应内容
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"响应内容: {responseContent}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"错误: {ex.Message}");
|
||||
Console.WriteLine($"详细信息: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
74
FutureMailAPI/Controllers/AIAssistantController.cs
Normal file
74
FutureMailAPI/Controllers/AIAssistantController.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/ai")]
|
||||
[Authorize]
|
||||
public class AIAssistantController : ControllerBase
|
||||
{
|
||||
private readonly IAIAssistantService _aiAssistantService;
|
||||
|
||||
public AIAssistantController(IAIAssistantService aiAssistantService)
|
||||
{
|
||||
_aiAssistantService = aiAssistantService;
|
||||
}
|
||||
|
||||
[HttpPost("writing-assistant")]
|
||||
public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<WritingAssistantResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
var result = await _aiAssistantService.GetWritingAssistanceAsync(request);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("sentiment-analysis")]
|
||||
public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<SentimentAnalysisResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
var result = await _aiAssistantService.AnalyzeSentimentAsync(request);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("future-prediction")]
|
||||
public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> PredictFuture([FromBody] FuturePredictionRequestDto request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<FuturePredictionResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
var result = await _aiAssistantService.PredictFutureAsync(request);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
130
FutureMailAPI/Controllers/AIController.cs
Normal file
130
FutureMailAPI/Controllers/AIController.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/ai")]
|
||||
[Authorize]
|
||||
public class AIController : ControllerBase
|
||||
{
|
||||
private readonly IAIAssistantService _aiAssistantService;
|
||||
private readonly ILogger<AIController> _logger;
|
||||
|
||||
public AIController(IAIAssistantService aiAssistantService, ILogger<AIController> logger)
|
||||
{
|
||||
_aiAssistantService = aiAssistantService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI写作辅助
|
||||
/// </summary>
|
||||
/// <param name="request">写作辅助请求</param>
|
||||
/// <returns>AI生成的内容和建议</returns>
|
||||
[HttpPost("writing-assistant")]
|
||||
public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> WritingAssistant([FromBody] WritingAssistantRequestDto request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<WritingAssistantResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _aiAssistantService.GetWritingAssistanceAsync(request);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取写作辅助时发生错误");
|
||||
return StatusCode(500, ApiResponse<WritingAssistantResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 情感分析
|
||||
/// </summary>
|
||||
/// <param name="request">情感分析请求</param>
|
||||
/// <returns>情感分析结果</returns>
|
||||
[HttpPost("sentiment-analysis")]
|
||||
public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<SentimentAnalysisResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _aiAssistantService.AnalyzeSentimentAsync(request);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "进行情感分析时发生错误");
|
||||
return StatusCode(500, ApiResponse<SentimentAnalysisResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未来预测
|
||||
/// </summary>
|
||||
/// <param name="request">未来预测请求</param>
|
||||
/// <returns>未来预测结果</returns>
|
||||
[HttpPost("future-prediction")]
|
||||
public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> FuturePrediction([FromBody] FuturePredictionRequestDto request)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<FuturePredictionResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _aiAssistantService.PredictFutureAsync(request);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "进行未来预测时发生错误");
|
||||
return StatusCode(500, ApiResponse<FuturePredictionResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JWT令牌中获取当前用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
123
FutureMailAPI/Controllers/AuthController.cs
Normal file
123
FutureMailAPI/Controllers/AuthController.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Extensions;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/auth")]
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly IAuthService _authService;
|
||||
private readonly ILogger<AuthController> _logger;
|
||||
|
||||
public AuthController(IAuthService authService, ILogger<AuthController> logger)
|
||||
{
|
||||
_authService = authService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Register([FromBody] UserRegisterDto registerDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
var result = await _authService.RegisterAsync(registerDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Login([FromBody] UserLoginDto loginDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
var result = await _authService.LoginAsync(loginDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("refresh")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> RefreshToken([FromBody] RefreshTokenRequestDto request)
|
||||
{
|
||||
if (request == null || string.IsNullOrEmpty(request.Token))
|
||||
{
|
||||
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("令牌不能为空"));
|
||||
}
|
||||
|
||||
// 使用OAuth刷新令牌
|
||||
var tokenResult = await _authService.RefreshTokenAsync(request.Token);
|
||||
|
||||
if (!tokenResult.Success)
|
||||
{
|
||||
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult(tokenResult.Message));
|
||||
}
|
||||
|
||||
// 创建认证响应DTO
|
||||
var authResponse = new AuthResponseDto
|
||||
{
|
||||
Token = tokenResult.Data,
|
||||
Expires = DateTime.UtcNow.AddHours(1) // OAuth访问令牌默认1小时过期
|
||||
};
|
||||
|
||||
return Ok(ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "令牌刷新成功"));
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
public async Task<ActionResult<ApiResponse<bool>>> Logout()
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
// 这里可以实现令牌黑名单或其他注销逻辑
|
||||
// 目前只返回成功响应
|
||||
return Ok(ApiResponse<bool>.SuccessResult(true));
|
||||
}
|
||||
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
// 从OAuth中间件获取用户ID
|
||||
var userId = HttpContext.GetCurrentUserId();
|
||||
if (userId.HasValue)
|
||||
{
|
||||
return userId.Value;
|
||||
}
|
||||
|
||||
// 兼容旧的JWT方式
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var jwtUserId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return jwtUserId;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
FutureMailAPI/Controllers/CapsulesController.cs
Normal file
81
FutureMailAPI/Controllers/CapsulesController.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/capsules")]
|
||||
[Authorize]
|
||||
public class CapsulesController : ControllerBase
|
||||
{
|
||||
private readonly ITimeCapsuleService _timeCapsuleService;
|
||||
private readonly ILogger<CapsulesController> _logger;
|
||||
|
||||
public CapsulesController(ITimeCapsuleService timeCapsuleService, ILogger<CapsulesController> logger)
|
||||
{
|
||||
_timeCapsuleService = timeCapsuleService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetCapsules()
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{capsuleId}/style")]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
189
FutureMailAPI/Controllers/FileUploadController.cs
Normal file
189
FutureMailAPI/Controllers/FileUploadController.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Helpers;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
public class FileUploadController : ControllerBase
|
||||
{
|
||||
private readonly IFileUploadService _fileUploadService;
|
||||
private readonly ILogger<FileUploadController> _logger;
|
||||
|
||||
public FileUploadController(IFileUploadService fileUploadService, ILogger<FileUploadController> logger)
|
||||
{
|
||||
_fileUploadService = fileUploadService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传附件
|
||||
/// </summary>
|
||||
/// <param name="request">文件上传请求</param>
|
||||
/// <returns>上传结果</returns>
|
||||
[HttpPost("attachment")]
|
||||
public async Task<IActionResult> UploadAttachment([FromForm] FileUploadWithFileRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (request.File == null || request.File.Length == 0)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("请选择要上传的文件"));
|
||||
}
|
||||
|
||||
var result = await _fileUploadService.UploadFileAsync(request.File, userId, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "上传附件时发生错误");
|
||||
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传头像
|
||||
/// </summary>
|
||||
/// <param name="request">文件上传请求</param>
|
||||
/// <returns>上传结果</returns>
|
||||
[HttpPost("avatar")]
|
||||
public async Task<IActionResult> UploadAvatar([FromForm] FileUploadWithFileRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (request.File == null || request.File.Length == 0)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("请选择要上传的头像文件"));
|
||||
}
|
||||
|
||||
// 设置头像特定的属性
|
||||
request.Type = AttachmentType.IMAGE;
|
||||
request.Category = "avatar";
|
||||
|
||||
var result = await _fileUploadService.UploadFileAsync(request.File, userId, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "上传头像时发生错误");
|
||||
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除文件
|
||||
/// </summary>
|
||||
/// <param name="fileId">文件ID</param>
|
||||
/// <returns>删除结果</returns>
|
||||
[HttpDelete("{fileId}")]
|
||||
public async Task<IActionResult> DeleteFile(string fileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fileId))
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("文件ID不能为空"));
|
||||
}
|
||||
|
||||
var result = await _fileUploadService.DeleteFileAsync(fileId, userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除文件时发生错误");
|
||||
return StatusCode(500, ApiResponse<bool>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取文件信息
|
||||
/// </summary>
|
||||
/// <param name="fileId">文件ID</param>
|
||||
/// <returns>文件信息</returns>
|
||||
[HttpGet("info/{fileId}")]
|
||||
public async Task<IActionResult> GetFile(string fileId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(fileId))
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("文件ID不能为空"));
|
||||
}
|
||||
|
||||
var result = await _fileUploadService.GetFileAsync(fileId, userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取文件信息时发生错误");
|
||||
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前请求中获取用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
225
FutureMailAPI/Controllers/MailsController.cs
Normal file
225
FutureMailAPI/Controllers/MailsController.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
public class MailsController : ControllerBase
|
||||
{
|
||||
private readonly IMailService _mailService;
|
||||
|
||||
public MailsController(IMailService mailService)
|
||||
{
|
||||
_mailService = mailService;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> CreateMail([FromBody] SentMailCreateDto createDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<SentMailResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.CreateMailAsync(currentUserId.Value, createDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetMail),
|
||||
new { mailId = result.Data!.Id },
|
||||
result);
|
||||
}
|
||||
|
||||
[HttpGet("{mailId}")]
|
||||
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> GetMail(int mailId)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.GetSentMailByIdAsync(currentUserId.Value, mailId);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return NotFound(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ApiResponse<PagedResponse<SentMailResponseDto>>>> GetMails([FromQuery] MailListQueryDto queryDto)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.GetSentMailsAsync(currentUserId.Value, queryDto);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{mailId}")]
|
||||
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<SentMailResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.UpdateMailAsync(currentUserId.Value, mailId, updateDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{mailId}")]
|
||||
public async Task<ActionResult<ApiResponse<bool>>> DeleteMail(int mailId)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.DeleteMailAsync(currentUserId.Value, mailId);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("received")]
|
||||
public async Task<ActionResult<ApiResponse<PagedResponse<ReceivedMailResponseDto>>>> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<PagedResponse<ReceivedMailResponseDto>>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.GetReceivedMailsAsync(currentUserId.Value, queryDto);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("received/{id}")]
|
||||
public async Task<ActionResult<ApiResponse<ReceivedMailResponseDto>>> GetReceivedMail(int id)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<ReceivedMailResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.GetReceivedMailByIdAsync(currentUserId.Value, id);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return NotFound(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("received/{id}/mark-read")]
|
||||
public async Task<ActionResult<ApiResponse<bool>>> MarkReceivedMailAsRead(int id)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId.Value, id);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("{mailId}/revoke")]
|
||||
public async Task<ActionResult<ApiResponse<bool>>> RevokeMail(int mailId)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _mailService.RevokeMailAsync(currentUserId.Value, mailId);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
FutureMailAPI/Controllers/NotificationController.cs
Normal file
105
FutureMailAPI/Controllers/NotificationController.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/notification")]
|
||||
[Authorize]
|
||||
public class NotificationController : ControllerBase
|
||||
{
|
||||
private readonly INotificationService _notificationService;
|
||||
private readonly ILogger<NotificationController> _logger;
|
||||
|
||||
public NotificationController(INotificationService notificationService, ILogger<NotificationController> logger)
|
||||
{
|
||||
_notificationService = notificationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册设备
|
||||
/// </summary>
|
||||
/// <param name="request">设备注册请求</param>
|
||||
/// <returns>注册结果</returns>
|
||||
[HttpPost("device")]
|
||||
public async Task<IActionResult> RegisterDevice([FromBody] NotificationDeviceRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(request.DeviceType) || string.IsNullOrEmpty(request.DeviceToken))
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("设备类型和设备令牌不能为空"));
|
||||
}
|
||||
|
||||
var result = await _notificationService.RegisterDeviceAsync(userId, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "注册设备时发生错误");
|
||||
return StatusCode(500, ApiResponse<NotificationDeviceResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取通知设置
|
||||
/// </summary>
|
||||
/// <returns>通知设置</returns>
|
||||
[HttpGet("settings")]
|
||||
public async Task<IActionResult> GetNotificationSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _notificationService.GetNotificationSettingsAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取通知设置时发生错误");
|
||||
return StatusCode(500, ApiResponse<NotificationSettingsDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JWT令牌中获取当前用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
295
FutureMailAPI/Controllers/OAuthController.cs
Normal file
295
FutureMailAPI/Controllers/OAuthController.cs
Normal file
@@ -0,0 +1,295 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.Extensions;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/oauth")]
|
||||
public class OAuthController : ControllerBase
|
||||
{
|
||||
private readonly IOAuthService _oauthService;
|
||||
private readonly ILogger<OAuthController> _logger;
|
||||
|
||||
public OAuthController(IOAuthService oauthService, ILogger<OAuthController> logger)
|
||||
{
|
||||
_oauthService = oauthService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OAuth登录端点
|
||||
/// </summary>
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] OAuthLoginDto loginDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _oauthService.LoginAsync(loginDto);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "OAuth登录时发生错误");
|
||||
return StatusCode(500, new { message = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建OAuth客户端
|
||||
/// </summary>
|
||||
[HttpPost("clients")]
|
||||
public async Task<IActionResult> CreateClient([FromBody] OAuthClientCreateDto createDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从OAuth中间件获取当前用户ID
|
||||
var userId = HttpContext.GetCurrentUserId();
|
||||
if (!userId.HasValue)
|
||||
{
|
||||
return Unauthorized(new { message = "未授权访问" });
|
||||
}
|
||||
|
||||
var result = await _oauthService.CreateClientAsync(userId.Value, createDto);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "创建OAuth客户端时发生错误");
|
||||
return StatusCode(500, new { message = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取OAuth客户端信息
|
||||
/// </summary>
|
||||
[HttpGet("clients/{clientId}")]
|
||||
public async Task<IActionResult> GetClient(string clientId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _oauthService.GetClientAsync(clientId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return NotFound(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取OAuth客户端信息时发生错误");
|
||||
return StatusCode(500, new { message = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OAuth授权端点
|
||||
/// </summary>
|
||||
[HttpGet("authorize")]
|
||||
public async Task<IActionResult> Authorize([FromQuery] OAuthAuthorizationRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从OAuth中间件获取当前用户ID
|
||||
var userId = HttpContext.GetCurrentUserId();
|
||||
if (!userId.HasValue)
|
||||
{
|
||||
// 如果用户未登录,重定向到登录页面
|
||||
var loginRedirectUri = $"/api/v1/auth/login?redirect_uri={Uri.EscapeDataString(request.RedirectUri)}";
|
||||
if (!string.IsNullOrEmpty(request.State))
|
||||
{
|
||||
loginRedirectUri += $"&state={request.State}";
|
||||
}
|
||||
return Redirect(loginRedirectUri);
|
||||
}
|
||||
|
||||
var result = await _oauthService.AuthorizeAsync(userId.Value, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
// 重定向到客户端,携带授权码
|
||||
var redirectUri = $"{request.RedirectUri}?code={result.Data.Code}";
|
||||
if (!string.IsNullOrEmpty(request.State))
|
||||
{
|
||||
redirectUri += $"&state={request.State}";
|
||||
}
|
||||
|
||||
return Redirect(redirectUri);
|
||||
}
|
||||
|
||||
// 错误重定向
|
||||
var errorRedirectUri = $"{request.RedirectUri}?error={result.Message}";
|
||||
if (!string.IsNullOrEmpty(request.State))
|
||||
{
|
||||
errorRedirectUri += $"&state={request.State}";
|
||||
}
|
||||
|
||||
return Redirect(errorRedirectUri);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "OAuth授权时发生错误");
|
||||
|
||||
// 错误重定向
|
||||
var errorRedirectUri = $"{request.RedirectUri}?error=server_error";
|
||||
if (!string.IsNullOrEmpty(request.State))
|
||||
{
|
||||
errorRedirectUri += $"&state={request.State}";
|
||||
}
|
||||
|
||||
return Redirect(errorRedirectUri);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// OAuth令牌端点
|
||||
/// </summary>
|
||||
[HttpPost("token")]
|
||||
[Microsoft.AspNetCore.Authorization.AllowAnonymous]
|
||||
public async Task<IActionResult> ExchangeToken([FromForm] OAuthTokenRequestDto request)
|
||||
{
|
||||
_logger.LogInformation("OAuth令牌端点被调用");
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("OAuth令牌交换请求: GrantType={GrantType}, ClientId={ClientId}, Username={Username}",
|
||||
request.GrantType, request.ClientId, request.Username);
|
||||
|
||||
if (request.GrantType == "authorization_code")
|
||||
{
|
||||
var result = await _oauthService.ExchangeCodeForTokenAsync(request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
else if (request.GrantType == "refresh_token")
|
||||
{
|
||||
var result = await _oauthService.RefreshTokenAsync(request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
else if (request.GrantType == "password")
|
||||
{
|
||||
_logger.LogInformation("处理密码授权类型登录请求");
|
||||
|
||||
// 创建OAuth登录请求
|
||||
var loginDto = new OAuthLoginDto
|
||||
{
|
||||
UsernameOrEmail = request.Username,
|
||||
Password = request.Password,
|
||||
ClientId = request.ClientId,
|
||||
ClientSecret = request.ClientSecret,
|
||||
Scope = request.Scope
|
||||
};
|
||||
|
||||
var result = await _oauthService.LoginAsync(loginDto);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
_logger.LogInformation("密码授权类型登录成功");
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
_logger.LogWarning("密码授权类型登录失败: {Message}", result.Message);
|
||||
return BadRequest(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("不支持的授权类型: {GrantType}", request.GrantType);
|
||||
return BadRequest(new { message = "不支持的授权类型" });
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "OAuth令牌交换时发生错误");
|
||||
return StatusCode(500, new { message = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 撤销令牌
|
||||
/// </summary>
|
||||
[HttpPost("revoke")]
|
||||
public async Task<IActionResult> RevokeToken([FromForm] string token, [FromForm] string token_type_hint = "access_token")
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _oauthService.RevokeTokenAsync(token);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(new { message = "令牌已撤销" });
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "撤销令牌时发生错误");
|
||||
return StatusCode(500, new { message = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证令牌
|
||||
/// </summary>
|
||||
[HttpPost("introspect")]
|
||||
public async Task<IActionResult> IntrospectToken([FromForm] string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _oauthService.ValidateTokenAsync(token);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
var accessToken = await _oauthService.GetAccessTokenAsync(token);
|
||||
|
||||
if (accessToken != null)
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
active = true,
|
||||
scope = accessToken.Scopes,
|
||||
client_id = accessToken.Client.ClientId,
|
||||
username = accessToken.User.Email,
|
||||
exp = ((DateTimeOffset)accessToken.ExpiresAt).ToUnixTimeSeconds(),
|
||||
iat = ((DateTimeOffset)accessToken.CreatedAt).ToUnixTimeSeconds()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(new { active = false });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "验证令牌时发生错误");
|
||||
return StatusCode(500, new { message = "服务器内部错误" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
174
FutureMailAPI/Controllers/PersonalSpaceController.cs
Normal file
174
FutureMailAPI/Controllers/PersonalSpaceController.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Helpers;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
public class PersonalSpaceController : ControllerBase
|
||||
{
|
||||
private readonly IPersonalSpaceService _personalSpaceService;
|
||||
private readonly ILogger<PersonalSpaceController> _logger;
|
||||
|
||||
public PersonalSpaceController(IPersonalSpaceService personalSpaceService, ILogger<PersonalSpaceController> logger)
|
||||
{
|
||||
_personalSpaceService = personalSpaceService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户时间线
|
||||
/// </summary>
|
||||
/// <param name="type">时间线类型</param>
|
||||
/// <param name="startDate">开始日期</param>
|
||||
/// <param name="endDate">结束日期</param>
|
||||
/// <returns>用户时间线</returns>
|
||||
[HttpGet("timeline")]
|
||||
public async Task<IActionResult> GetTimeline(
|
||||
[FromQuery] TimelineType type = TimelineType.ALL,
|
||||
[FromQuery] DateTime? startDate = null,
|
||||
[FromQuery] DateTime? endDate = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var query = new TimelineQueryDto
|
||||
{
|
||||
Type = type,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate
|
||||
};
|
||||
|
||||
var result = await _personalSpaceService.GetTimelineAsync(userId, query);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取时间线时发生错误");
|
||||
return StatusCode(500, ApiResponse<TimelineResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户统计数据
|
||||
/// </summary>
|
||||
/// <returns>用户统计数据</returns>
|
||||
[HttpGet("statistics")]
|
||||
public async Task<IActionResult> GetStatistics()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _personalSpaceService.GetStatisticsAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取统计数据时发生错误");
|
||||
return StatusCode(500, ApiResponse<StatisticsResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户订阅信息
|
||||
/// </summary>
|
||||
/// <returns>用户订阅信息</returns>
|
||||
[HttpGet("subscription")]
|
||||
public async Task<IActionResult> GetSubscription()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _personalSpaceService.GetSubscriptionAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取订阅信息时发生错误");
|
||||
return StatusCode(500, ApiResponse<SubscriptionResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户资料
|
||||
/// </summary>
|
||||
/// <returns>用户资料</returns>
|
||||
[HttpGet("profile")]
|
||||
public async Task<IActionResult> GetUserProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _personalSpaceService.GetUserProfileAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取用户资料时发生错误");
|
||||
return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从当前请求中获取用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
FutureMailAPI/Controllers/StatisticsController.cs
Normal file
68
FutureMailAPI/Controllers/StatisticsController.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/statistics")]
|
||||
[Authorize]
|
||||
public class StatisticsController : ControllerBase
|
||||
{
|
||||
private readonly IPersonalSpaceService _personalSpaceService;
|
||||
private readonly ILogger<StatisticsController> _logger;
|
||||
|
||||
public StatisticsController(IPersonalSpaceService personalSpaceService, ILogger<StatisticsController> logger)
|
||||
{
|
||||
_personalSpaceService = personalSpaceService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户统计数据
|
||||
/// </summary>
|
||||
/// <returns>用户统计数据</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetStatistics()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _personalSpaceService.GetStatisticsAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取统计数据时发生错误");
|
||||
return StatusCode(500, ApiResponse<StatisticsResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JWT令牌中获取当前用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
224
FutureMailAPI/Controllers/TimeCapsulesController.cs
Normal file
224
FutureMailAPI/Controllers/TimeCapsulesController.cs
Normal file
@@ -0,0 +1,224 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/[controller]")]
|
||||
[Authorize]
|
||||
public class TimeCapsulesController : ControllerBase
|
||||
{
|
||||
private readonly ITimeCapsuleService _timeCapsuleService;
|
||||
private readonly ILogger<TimeCapsulesController> _logger;
|
||||
|
||||
public TimeCapsulesController(ITimeCapsuleService timeCapsuleService, ILogger<TimeCapsulesController> logger)
|
||||
{
|
||||
_timeCapsuleService = timeCapsuleService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId.Value, createDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetTimeCapsule),
|
||||
new { capsuleId = result.Data!.Id },
|
||||
result);
|
||||
}
|
||||
|
||||
[HttpGet("{capsuleId}")]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> GetTimeCapsule(int capsuleId)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId.Value, capsuleId);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return NotFound(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId.Value, queryDto);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{capsuleId}")]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId.Value, capsuleId, updateDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpDelete("{capsuleId}")]
|
||||
public async Task<ActionResult<ApiResponse<bool>>> DeleteTimeCapsule(int capsuleId)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId.Value, capsuleId);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("public")]
|
||||
[AllowAnonymous]
|
||||
public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
|
||||
{
|
||||
var result = await _timeCapsuleService.GetPublicTimeCapsulesAsync(queryDto);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("public/{capsuleId}/claim")]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> ClaimPublicCapsule(int capsuleId)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId.Value, capsuleId);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("view")]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetTimeCapsuleView()
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{capsuleId}/style")]
|
||||
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
81
FutureMailAPI/Controllers/TimelineController.cs
Normal file
81
FutureMailAPI/Controllers/TimelineController.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/timeline")]
|
||||
[Authorize]
|
||||
public class TimelineController : ControllerBase
|
||||
{
|
||||
private readonly IPersonalSpaceService _personalSpaceService;
|
||||
private readonly ILogger<TimelineController> _logger;
|
||||
|
||||
public TimelineController(IPersonalSpaceService personalSpaceService, ILogger<TimelineController> logger)
|
||||
{
|
||||
_personalSpaceService = personalSpaceService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户时间线
|
||||
/// </summary>
|
||||
/// <param name="type">时间线类型</param>
|
||||
/// <param name="startDate">开始日期</param>
|
||||
/// <param name="endDate">结束日期</param>
|
||||
/// <returns>用户时间线</returns>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetTimeline(
|
||||
[FromQuery] TimelineType type = TimelineType.ALL,
|
||||
[FromQuery] DateTime? startDate = null,
|
||||
[FromQuery] DateTime? endDate = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var query = new TimelineQueryDto
|
||||
{
|
||||
Type = type,
|
||||
StartDate = startDate,
|
||||
EndDate = endDate
|
||||
};
|
||||
|
||||
var result = await _personalSpaceService.GetTimelineAsync(userId, query);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取时间线时发生错误");
|
||||
return StatusCode(500, ApiResponse<TimelineResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JWT令牌中获取当前用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
115
FutureMailAPI/Controllers/UploadController.cs
Normal file
115
FutureMailAPI/Controllers/UploadController.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/upload")]
|
||||
[Authorize]
|
||||
public class UploadController : ControllerBase
|
||||
{
|
||||
private readonly IFileUploadService _fileUploadService;
|
||||
private readonly ILogger<UploadController> _logger;
|
||||
|
||||
public UploadController(IFileUploadService fileUploadService, ILogger<UploadController> logger)
|
||||
{
|
||||
_fileUploadService = fileUploadService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传附件
|
||||
/// </summary>
|
||||
/// <param name="request">文件上传请求</param>
|
||||
/// <returns>上传结果</returns>
|
||||
[HttpPost("attachment")]
|
||||
public async Task<IActionResult> UploadAttachment([FromForm] FileUploadWithFileRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (request.File == null || request.File.Length == 0)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("请选择要上传的文件"));
|
||||
}
|
||||
|
||||
var result = await _fileUploadService.UploadFileAsync(request.File, userId, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "上传附件时发生错误");
|
||||
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传头像
|
||||
/// </summary>
|
||||
/// <param name="request">文件上传请求</param>
|
||||
/// <returns>上传结果</returns>
|
||||
[HttpPost("avatar")]
|
||||
public async Task<IActionResult> UploadAvatar([FromForm] FileUploadWithFileRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
if (request.File == null || request.File.Length == 0)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResult("请选择要上传的头像文件"));
|
||||
}
|
||||
|
||||
// 设置头像特定的属性
|
||||
request.Type = AttachmentType.IMAGE;
|
||||
request.Category = "avatar";
|
||||
|
||||
var result = await _fileUploadService.UploadFileAsync(request.File, userId, request);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "上传头像时发生错误");
|
||||
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JWT令牌中获取当前用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
FutureMailAPI/Controllers/UserController.cs
Normal file
99
FutureMailAPI/Controllers/UserController.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/user")]
|
||||
[Authorize]
|
||||
public class UserController : ControllerBase
|
||||
{
|
||||
private readonly IPersonalSpaceService _personalSpaceService;
|
||||
private readonly ILogger<UserController> _logger;
|
||||
|
||||
public UserController(IPersonalSpaceService personalSpaceService, ILogger<UserController> logger)
|
||||
{
|
||||
_personalSpaceService = personalSpaceService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户订阅信息
|
||||
/// </summary>
|
||||
/// <returns>用户订阅信息</returns>
|
||||
[HttpGet("subscription")]
|
||||
public async Task<IActionResult> GetSubscription()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _personalSpaceService.GetSubscriptionAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取订阅信息时发生错误");
|
||||
return StatusCode(500, ApiResponse<SubscriptionResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户资料
|
||||
/// </summary>
|
||||
/// <returns>用户资料</returns>
|
||||
[HttpGet("profile")]
|
||||
public async Task<IActionResult> GetUserProfile()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userId = GetCurrentUserId();
|
||||
if (userId <= 0)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResult("无效的用户令牌"));
|
||||
}
|
||||
|
||||
var result = await _personalSpaceService.GetUserProfileAsync(userId);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
return BadRequest(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取用户资料时发生错误");
|
||||
return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从JWT令牌中获取当前用户ID
|
||||
/// </summary>
|
||||
/// <returns>用户ID</returns>
|
||||
private int GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||||
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
FutureMailAPI/Controllers/UsersController.cs
Normal file
125
FutureMailAPI/Controllers/UsersController.cs
Normal file
@@ -0,0 +1,125 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/v1/users")]
|
||||
[Authorize]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly ILogger<UsersController> _logger;
|
||||
|
||||
public UsersController(IUserService userService, ILogger<UsersController> logger)
|
||||
{
|
||||
_userService = userService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<UserResponseDto>>> GetUser(int id)
|
||||
{
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
// 只有用户本人可以查看自己的信息
|
||||
if (currentUserId != id)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var result = await _userService.GetUserByIdAsync(id);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return NotFound(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public async Task<ActionResult<ApiResponse<UserResponseDto>>> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<UserResponseDto>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
// 只有用户本人可以更新自己的信息
|
||||
if (currentUserId != id)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var result = await _userService.UpdateUserAsync(id, updateDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("{id}/change-password")]
|
||||
public async Task<ActionResult<ApiResponse<bool>>> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ApiResponse<bool>.ErrorResult("输入数据无效"));
|
||||
}
|
||||
|
||||
// 从JWT令牌中获取当前用户ID
|
||||
var currentUserId = GetCurrentUserId();
|
||||
|
||||
if (currentUserId == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
|
||||
}
|
||||
|
||||
// 只有用户本人可以修改自己的密码
|
||||
if (currentUserId != id)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var result = await _userService.ChangePasswordAsync(id, changePasswordDto);
|
||||
|
||||
if (!result.Success)
|
||||
{
|
||||
return BadRequest(result);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
private int? GetCurrentUserId()
|
||||
{
|
||||
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
|
||||
|
||||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
127
FutureMailAPI/DTOs/AIDTOs.cs
Normal file
127
FutureMailAPI/DTOs/AIDTOs.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class WritingAssistantRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "提示内容是必填项")]
|
||||
[StringLength(1000, ErrorMessage = "提示内容长度不能超过1000个字符")]
|
||||
public string Prompt { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "辅助类型是必填项")]
|
||||
[EnumDataType(typeof(WritingAssistantType), ErrorMessage = "无效的辅助类型")]
|
||||
public WritingAssistantType Type { get; set; }
|
||||
|
||||
[EnumDataType(typeof(WritingTone), ErrorMessage = "无效的语气类型")]
|
||||
public WritingTone Tone { get; set; } = WritingTone.CASUAL;
|
||||
|
||||
[EnumDataType(typeof(WritingLength), ErrorMessage = "无效的长度类型")]
|
||||
public WritingLength Length { get; set; } = WritingLength.MEDIUM;
|
||||
|
||||
[StringLength(500, ErrorMessage = "上下文信息长度不能超过500个字符")]
|
||||
public string? Context { get; set; }
|
||||
}
|
||||
|
||||
public class WritingAssistantResponseDto
|
||||
{
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public List<string> Suggestions { get; set; } = new();
|
||||
public int EstimatedTime { get; set; } // 预计写作时间(分钟)
|
||||
}
|
||||
|
||||
public class SentimentAnalysisRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "内容是必填项")]
|
||||
[StringLength(2000, ErrorMessage = "内容长度不能超过2000个字符")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class SentimentAnalysisResponseDto
|
||||
{
|
||||
public SentimentType Sentiment { get; set; }
|
||||
public double Confidence { get; set; } // 0-1 置信度
|
||||
public List<EmotionScore> Emotions { get; set; } = new();
|
||||
public List<string> Keywords { get; set; } = new();
|
||||
public string Summary { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class EmotionScore
|
||||
{
|
||||
public EmotionType Type { get; set; }
|
||||
public double Score { get; set; }
|
||||
}
|
||||
|
||||
public class FuturePredictionRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "预测内容是必填项")]
|
||||
[StringLength(1000, ErrorMessage = "预测内容长度不能超过1000个字符")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "预测类型是必填项")]
|
||||
[EnumDataType(typeof(PredictionType), ErrorMessage = "无效的预测类型")]
|
||||
public PredictionType Type { get; set; }
|
||||
|
||||
[Range(1, 365, ErrorMessage = "预测天数必须在1-365之间")]
|
||||
public int DaysAhead { get; set; } = 30;
|
||||
}
|
||||
|
||||
public class FuturePredictionResponseDto
|
||||
{
|
||||
public string Prediction { get; set; } = string.Empty;
|
||||
public double Confidence { get; set; } // 0-1 置信度
|
||||
public List<string> Factors { get; set; } = new();
|
||||
public List<string> Suggestions { get; set; } = new();
|
||||
}
|
||||
|
||||
// 枚举定义
|
||||
public enum WritingAssistantType
|
||||
{
|
||||
OUTLINE = 0,
|
||||
DRAFT = 1,
|
||||
COMPLETE = 2
|
||||
}
|
||||
|
||||
public enum WritingTone
|
||||
{
|
||||
FORMAL = 0,
|
||||
CASUAL = 1,
|
||||
EMOTIONAL = 2,
|
||||
INSPIRATIONAL = 3
|
||||
}
|
||||
|
||||
public enum WritingLength
|
||||
{
|
||||
SHORT = 0,
|
||||
MEDIUM = 1,
|
||||
LONG = 2
|
||||
}
|
||||
|
||||
public enum SentimentType
|
||||
{
|
||||
POSITIVE = 0,
|
||||
NEUTRAL = 1,
|
||||
NEGATIVE = 2,
|
||||
MIXED = 3
|
||||
}
|
||||
|
||||
public enum EmotionType
|
||||
{
|
||||
HAPPY = 0,
|
||||
SAD = 1,
|
||||
HOPEFUL = 2,
|
||||
NOSTALGIC = 3,
|
||||
EXCITED = 4,
|
||||
ANXIOUS = 5,
|
||||
GRATEFUL = 6,
|
||||
CONFUSED = 7
|
||||
}
|
||||
|
||||
public enum PredictionType
|
||||
{
|
||||
CAREER = 0,
|
||||
RELATIONSHIP = 1,
|
||||
HEALTH = 2,
|
||||
FINANCIAL = 3,
|
||||
PERSONAL_GROWTH = 4
|
||||
}
|
||||
}
|
||||
60
FutureMailAPI/DTOs/CommonDTOs.cs
Normal file
60
FutureMailAPI/DTOs/CommonDTOs.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class ApiResponse<T>
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; } = string.Empty;
|
||||
public T? Data { get; set; }
|
||||
public List<string>? Errors { get; set; }
|
||||
|
||||
public static ApiResponse<T> SuccessResult(T data, string message = "操作成功")
|
||||
{
|
||||
return new ApiResponse<T>
|
||||
{
|
||||
Success = true,
|
||||
Message = message,
|
||||
Data = data
|
||||
};
|
||||
}
|
||||
|
||||
public static ApiResponse<T> ErrorResult(string message, List<string>? errors = null)
|
||||
{
|
||||
return new ApiResponse<T>
|
||||
{
|
||||
Success = false,
|
||||
Message = message,
|
||||
Errors = errors
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class PagedResponse<T>
|
||||
{
|
||||
public IEnumerable<T> Items { get; set; } = new List<T>();
|
||||
public int PageIndex { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int TotalCount { get; set; }
|
||||
public int TotalPages { get; set; }
|
||||
public bool HasPreviousPage { get; set; }
|
||||
public bool HasNextPage { get; set; }
|
||||
|
||||
public PagedResponse(IEnumerable<T> items, int pageIndex, int pageSize, int totalCount)
|
||||
{
|
||||
Items = items;
|
||||
PageIndex = pageIndex;
|
||||
PageSize = pageSize;
|
||||
TotalCount = totalCount;
|
||||
TotalPages = (int)Math.Ceiling(totalCount / (double)pageSize);
|
||||
HasPreviousPage = pageIndex > 1;
|
||||
HasNextPage = pageIndex < TotalPages;
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthResponseDto
|
||||
{
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public string? RefreshToken { get; set; }
|
||||
public DateTime Expires { get; set; }
|
||||
public UserResponseDto User { get; set; } = new();
|
||||
}
|
||||
}
|
||||
47
FutureMailAPI/DTOs/FileUploadDTOs.cs
Normal file
47
FutureMailAPI/DTOs/FileUploadDTOs.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class FileUploadDto
|
||||
{
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string ContentType { get; set; } = string.Empty;
|
||||
public long FileSize { get; set; }
|
||||
public string FilePath { get; set; } = string.Empty;
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
public string ThumbnailUrl { get; set; } = string.Empty;
|
||||
public AttachmentType Type { get; set; }
|
||||
}
|
||||
|
||||
public class FileUploadRequestDto
|
||||
{
|
||||
public AttachmentType Type { get; set; }
|
||||
public string? Category { get; set; } // 分类:avatar, attachment等
|
||||
}
|
||||
|
||||
public class FileUploadWithFileRequestDto : FileUploadRequestDto
|
||||
{
|
||||
public IFormFile File { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class FileUploadResponseDto
|
||||
{
|
||||
public string FileId { get; set; } = string.Empty;
|
||||
public string FileName { get; set; } = string.Empty;
|
||||
public string FileUrl { get; set; } = string.Empty;
|
||||
public string ThumbnailUrl { get; set; } = string.Empty;
|
||||
public long FileSize { get; set; }
|
||||
public string ContentType { get; set; } = string.Empty;
|
||||
public AttachmentType Type { get; set; }
|
||||
public DateTime UploadedAt { get; set; }
|
||||
}
|
||||
|
||||
public enum AttachmentType
|
||||
{
|
||||
IMAGE,
|
||||
VOICE,
|
||||
VIDEO,
|
||||
DOCUMENT,
|
||||
OTHER
|
||||
}
|
||||
}
|
||||
115
FutureMailAPI/DTOs/MailDTOs.cs
Normal file
115
FutureMailAPI/DTOs/MailDTOs.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class SentMailCreateDto
|
||||
{
|
||||
[Required(ErrorMessage = "标题是必填项")]
|
||||
[StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "内容是必填项")]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
// 收件人类型: 0-自己, 1-指定用户, 2-公开时间胶囊
|
||||
[Required(ErrorMessage = "收件人类型是必填项")]
|
||||
[Range(0, 2, ErrorMessage = "收件人类型必须是0、1或2")]
|
||||
public int RecipientType { get; set; }
|
||||
|
||||
// 如果是指定用户,提供用户邮箱
|
||||
public string? RecipientEmail { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "投递时间是必填项")]
|
||||
public DateTime DeliveryTime { get; set; }
|
||||
|
||||
// 触发条件类型: 0-时间, 1-地点, 2-事件
|
||||
[Required(ErrorMessage = "触发条件类型是必填项")]
|
||||
[Range(0, 2, ErrorMessage = "触发条件类型必须是0、1或2")]
|
||||
public int TriggerType { get; set; } = 0;
|
||||
|
||||
// 触发条件详情(JSON格式)
|
||||
public string? TriggerDetails { get; set; }
|
||||
|
||||
// 附件路径(JSON数组格式)
|
||||
public string? Attachments { get; set; }
|
||||
|
||||
// 是否加密
|
||||
public bool IsEncrypted { get; set; } = false;
|
||||
|
||||
// 加密密钥(如果使用端到端加密)
|
||||
public string? EncryptionKey { get; set; }
|
||||
|
||||
// 邮件主题/胶囊皮肤
|
||||
[StringLength(50, ErrorMessage = "主题长度不能超过50个字符")]
|
||||
public string? Theme { get; set; }
|
||||
}
|
||||
|
||||
public class SentMailUpdateDto
|
||||
{
|
||||
[StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
|
||||
public string? Title { get; set; }
|
||||
|
||||
public string? Content { get; set; }
|
||||
|
||||
public DateTime? DeliveryTime { get; set; }
|
||||
|
||||
public string? TriggerDetails { get; set; }
|
||||
|
||||
public string? Attachments { get; set; }
|
||||
|
||||
public string? Theme { get; set; }
|
||||
}
|
||||
|
||||
public class SentMailResponseDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public int SenderId { get; set; }
|
||||
public string SenderUsername { get; set; } = string.Empty;
|
||||
public int RecipientType { get; set; }
|
||||
public int? RecipientId { get; set; }
|
||||
public string? RecipientUsername { get; set; }
|
||||
public DateTime SentAt { get; set; }
|
||||
public DateTime DeliveryTime { get; set; }
|
||||
public int Status { get; set; }
|
||||
public int TriggerType { get; set; }
|
||||
public string? TriggerDetails { get; set; }
|
||||
public string? Attachments { get; set; }
|
||||
public bool IsEncrypted { get; set; }
|
||||
public string? Theme { get; set; }
|
||||
|
||||
// 计算属性
|
||||
public string StatusText { get; set; } = string.Empty;
|
||||
public string RecipientTypeText { get; set; } = string.Empty;
|
||||
public string TriggerTypeText { get; set; } = string.Empty;
|
||||
public int DaysUntilDelivery { get; set; }
|
||||
}
|
||||
|
||||
public class ReceivedMailResponseDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int SentMailId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Content { get; set; } = string.Empty;
|
||||
public string SenderUsername { get; set; } = string.Empty;
|
||||
public DateTime SentAt { get; set; }
|
||||
public DateTime ReceivedAt { get; set; }
|
||||
public bool IsRead { get; set; }
|
||||
public DateTime? ReadAt { get; set; }
|
||||
public bool IsReplied { get; set; }
|
||||
public int? ReplyMailId { get; set; }
|
||||
public string? Theme { get; set; }
|
||||
}
|
||||
|
||||
public class MailListQueryDto
|
||||
{
|
||||
public int PageIndex { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 10;
|
||||
public int? Status { get; set; }
|
||||
public int? RecipientType { get; set; }
|
||||
public string? Keyword { get; set; }
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
}
|
||||
}
|
||||
70
FutureMailAPI/DTOs/NotificationDTOs.cs
Normal file
70
FutureMailAPI/DTOs/NotificationDTOs.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class NotificationDeviceDto
|
||||
{
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
public string DeviceType { get; set; } = string.Empty; // iOS, Android, Web
|
||||
public string DeviceToken { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime RegisteredAt { get; set; }
|
||||
public DateTime? LastActiveAt { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationSettingsDto
|
||||
{
|
||||
public bool EmailDelivery { get; set; } = true;
|
||||
public bool PushNotification { get; set; } = true;
|
||||
public bool InAppNotification { get; set; } = true;
|
||||
public bool DeliveryReminder { get; set; } = true;
|
||||
public bool ReceivedNotification { get; set; } = true;
|
||||
public bool SystemUpdates { get; set; } = false;
|
||||
public string QuietHoursStart { get; set; } = "22:00";
|
||||
public string QuietHoursEnd { get; set; } = "08:00";
|
||||
public bool EnableQuietHours { get; set; } = false;
|
||||
}
|
||||
|
||||
public class NotificationMessageDto
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public int UserId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Body { get; set; } = string.Empty;
|
||||
public string Type { get; set; } = string.Empty; // DELIVERY, RECEIVED, SYSTEM, REMINDER
|
||||
public string RelatedEntityId { get; set; } = string.Empty; // 邮件ID等
|
||||
public bool IsRead { get; set; } = false;
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? ReadAt { get; set; }
|
||||
public Dictionary<string, object> Data { get; set; } = new();
|
||||
}
|
||||
|
||||
public class NotificationDeviceRequestDto
|
||||
{
|
||||
public string DeviceType { get; set; } = string.Empty;
|
||||
public string DeviceToken { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class NotificationDeviceResponseDto
|
||||
{
|
||||
public string DeviceId { get; set; } = string.Empty;
|
||||
public string DeviceType { get; set; } = string.Empty;
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime RegisteredAt { get; set; }
|
||||
}
|
||||
|
||||
public class NotificationListQueryDto
|
||||
{
|
||||
public bool UnreadOnly { get; set; } = false;
|
||||
public string? Type { get; set; }
|
||||
public int Page { get; set; } = 1;
|
||||
public int Size { get; set; } = 20;
|
||||
}
|
||||
|
||||
public class NotificationListResponseDto
|
||||
{
|
||||
public List<NotificationMessageDto> Notifications { get; set; } = new();
|
||||
public int Total { get; set; }
|
||||
public int UnreadCount { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int Size { get; set; }
|
||||
}
|
||||
}
|
||||
105
FutureMailAPI/DTOs/OAuthDTOs.cs
Normal file
105
FutureMailAPI/DTOs/OAuthDTOs.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class OAuthAuthorizationRequestDto
|
||||
{
|
||||
[Required]
|
||||
public string ResponseType { get; set; } = "code"; // code for authorization code flow
|
||||
|
||||
[Required]
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string RedirectUri { get; set; } = string.Empty;
|
||||
|
||||
public string Scope { get; set; } = "read write"; // Default scopes
|
||||
|
||||
public string State { get; set; } = string.Empty; // CSRF protection
|
||||
}
|
||||
|
||||
public class OAuthTokenRequestDto
|
||||
{
|
||||
[Required]
|
||||
public string GrantType { get; set; } = string.Empty; // authorization_code, refresh_token, client_credentials, password
|
||||
|
||||
public string Code { get; set; } = string.Empty; // For authorization_code grant
|
||||
|
||||
public string RefreshToken { get; set; } = string.Empty; // For refresh_token grant
|
||||
|
||||
public string Username { get; set; } = string.Empty; // For password grant
|
||||
|
||||
public string Password { get; set; } = string.Empty; // For password grant
|
||||
|
||||
[Required]
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
|
||||
public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
|
||||
|
||||
public string RedirectUri { get; set; } = string.Empty; // Required for authorization_code grant
|
||||
|
||||
public string Scope { get; set; } = string.Empty; // Optional, defaults to requested scopes
|
||||
}
|
||||
|
||||
public class OAuthTokenResponseDto
|
||||
{
|
||||
public string AccessToken { get; set; } = string.Empty;
|
||||
public string TokenType { get; set; } = "Bearer";
|
||||
public int ExpiresIn { get; set; } // Seconds until expiration
|
||||
public string RefreshToken { get; set; } = string.Empty;
|
||||
public string Scope { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class OAuthAuthorizationResponseDto
|
||||
{
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public string State { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class OAuthClientDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string[] RedirectUris { get; set; } = Array.Empty<string>();
|
||||
public string[] Scopes { get; set; } = Array.Empty<string>();
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
}
|
||||
|
||||
public class OAuthClientCreateDto
|
||||
{
|
||||
[Required]
|
||||
[StringLength(100)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string[] RedirectUris { get; set; } = Array.Empty<string>();
|
||||
|
||||
[Required]
|
||||
public string[] Scopes { get; set; } = Array.Empty<string>();
|
||||
}
|
||||
|
||||
public class OAuthClientSecretDto
|
||||
{
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string ClientSecret { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class OAuthLoginDto
|
||||
{
|
||||
[Required]
|
||||
public string UsernameOrEmail { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
|
||||
public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
|
||||
|
||||
public string Scope { get; set; } = "read write"; // Default scopes
|
||||
}
|
||||
}
|
||||
115
FutureMailAPI/DTOs/PersonalSpaceDTOs.cs
Normal file
115
FutureMailAPI/DTOs/PersonalSpaceDTOs.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class TimelineQueryDto
|
||||
{
|
||||
public DateTime? StartDate { get; set; }
|
||||
public DateTime? EndDate { get; set; }
|
||||
public TimelineType Type { get; set; } = TimelineType.ALL;
|
||||
}
|
||||
|
||||
public class TimelineResponseDto
|
||||
{
|
||||
public List<TimelineDateDto> Timeline { get; set; } = new();
|
||||
}
|
||||
|
||||
public class TimelineDateDto
|
||||
{
|
||||
public string Date { get; set; } = string.Empty; // YYYY-MM-DD格式
|
||||
public List<TimelineEventDto> Events { get; set; } = new();
|
||||
}
|
||||
|
||||
public class TimelineEventDto
|
||||
{
|
||||
public TimelineEventType Type { get; set; }
|
||||
public int MailId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Time { get; set; } = string.Empty; // HH:mm格式
|
||||
public UserInfoDto WithUser { get; set; } = new();
|
||||
public string Emotion { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class StatisticsResponseDto
|
||||
{
|
||||
public int TotalSent { get; set; }
|
||||
public int TotalReceived { get; set; }
|
||||
public int TimeTravelDuration { get; set; } // 总时间旅行时长(天)
|
||||
public string MostFrequentRecipient { get; set; } = string.Empty;
|
||||
public int MostCommonYear { get; set; }
|
||||
public List<KeywordCloudDto> KeywordCloud { get; set; } = new();
|
||||
public List<MonthlyStatsDto> MonthlyStats { get; set; } = new();
|
||||
}
|
||||
|
||||
public class KeywordCloudDto
|
||||
{
|
||||
public string Word { get; set; } = string.Empty;
|
||||
public int Count { get; set; }
|
||||
public int Size { get; set; } // 用于显示的大小
|
||||
}
|
||||
|
||||
public class MonthlyStatsDto
|
||||
{
|
||||
public string Month { get; set; } = string.Empty; // YYYY-MM格式
|
||||
public int Sent { get; set; }
|
||||
public int Received { get; set; }
|
||||
}
|
||||
|
||||
public class UserInfoDto
|
||||
{
|
||||
public int UserId { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string? Avatar { get; set; }
|
||||
}
|
||||
|
||||
public class SubscriptionResponseDto
|
||||
{
|
||||
public SubscriptionPlan Plan { get; set; }
|
||||
public int RemainingMails { get; set; }
|
||||
public long MaxAttachmentSize { get; set; } // 字节
|
||||
public SubscriptionFeaturesDto Features { get; set; } = new();
|
||||
public DateTime? ExpireDate { get; set; }
|
||||
}
|
||||
|
||||
public class SubscriptionFeaturesDto
|
||||
{
|
||||
public bool AdvancedTriggers { get; set; }
|
||||
public bool CustomCapsules { get; set; }
|
||||
public bool AIAssistant { get; set; }
|
||||
public bool UnlimitedStorage { get; set; }
|
||||
public bool PriorityDelivery { get; set; }
|
||||
}
|
||||
|
||||
public class UserProfileResponseDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string? Nickname { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? LastLoginAt { get; set; }
|
||||
public SubscriptionResponseDto Subscription { get; set; } = new();
|
||||
}
|
||||
|
||||
// 枚举定义
|
||||
public enum TimelineType
|
||||
{
|
||||
ALL = 0,
|
||||
SENT = 1,
|
||||
RECEIVED = 2
|
||||
}
|
||||
|
||||
public enum TimelineEventType
|
||||
{
|
||||
SENT = 0,
|
||||
RECEIVED = 1
|
||||
}
|
||||
|
||||
public enum SubscriptionPlan
|
||||
{
|
||||
FREE = 0,
|
||||
PREMIUM = 1,
|
||||
PRO = 2
|
||||
}
|
||||
}
|
||||
130
FutureMailAPI/DTOs/TimeCapsuleDTOs.cs
Normal file
130
FutureMailAPI/DTOs/TimeCapsuleDTOs.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class TimeCapsuleCreateDto
|
||||
{
|
||||
[Required(ErrorMessage = "邮件ID是必填项")]
|
||||
public int SentMailId { get; set; }
|
||||
|
||||
// 胶囊位置信息(X, Y, Z坐标)
|
||||
public double PositionX { get; set; } = 0;
|
||||
public double PositionY { get; set; } = 0;
|
||||
public double PositionZ { get; set; } = 0;
|
||||
|
||||
// 胶囊大小
|
||||
[Range(0.1, 5.0, ErrorMessage = "胶囊大小必须在0.1-5.0之间")]
|
||||
public double Size { get; set; } = 1.0;
|
||||
|
||||
// 胶囊颜色
|
||||
[StringLength(20, ErrorMessage = "颜色代码长度不能超过20个字符")]
|
||||
public string? Color { get; set; }
|
||||
|
||||
// 胶囊透明度
|
||||
[Range(0.1, 1.0, ErrorMessage = "透明度必须在0.1-1.0之间")]
|
||||
public double Opacity { get; set; } = 1.0;
|
||||
|
||||
// 胶囊旋转角度
|
||||
[Range(0, 360, ErrorMessage = "旋转角度必须在0-360度之间")]
|
||||
public double Rotation { get; set; } = 0;
|
||||
|
||||
// 胶囊类型: 0-普通, 1-特殊, 2-限时
|
||||
[Range(0, 2, ErrorMessage = "胶囊类型必须是0、1或2")]
|
||||
public int Type { get; set; } = 0;
|
||||
}
|
||||
|
||||
public class TimeCapsuleUpdateDto
|
||||
{
|
||||
// 胶囊位置信息(X, Y, Z坐标)
|
||||
public double? PositionX { get; set; }
|
||||
public double? PositionY { get; set; }
|
||||
public double? PositionZ { get; set; }
|
||||
|
||||
// 胶囊大小
|
||||
[Range(0.1, 5.0, ErrorMessage = "胶囊大小必须在0.1-5.0之间")]
|
||||
public double? Size { get; set; }
|
||||
|
||||
// 胶囊颜色
|
||||
[StringLength(20, ErrorMessage = "颜色代码长度不能超过20个字符")]
|
||||
public string? Color { get; set; }
|
||||
|
||||
// 胶囊透明度
|
||||
[Range(0.1, 1.0, ErrorMessage = "透明度必须在0.1-1.0之间")]
|
||||
public double? Opacity { get; set; }
|
||||
|
||||
// 胶囊旋转角度
|
||||
[Range(0, 360, ErrorMessage = "旋转角度必须在0-360度之间")]
|
||||
public double? Rotation { get; set; }
|
||||
}
|
||||
|
||||
public class TimeCapsuleResponseDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public int SentMailId { get; set; }
|
||||
public string MailTitle { get; set; } = string.Empty;
|
||||
public double PositionX { get; set; }
|
||||
public double PositionY { get; set; }
|
||||
public double PositionZ { get; set; }
|
||||
public double Size { get; set; }
|
||||
public string? Color { get; set; }
|
||||
public double Opacity { get; set; }
|
||||
public double Rotation { get; set; }
|
||||
public int Status { get; set; }
|
||||
public int Type { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime DeliveryTime { get; set; }
|
||||
|
||||
// 计算属性
|
||||
public string StatusText { get; set; } = string.Empty;
|
||||
public string TypeText { get; set; } = string.Empty;
|
||||
public int DaysUntilDelivery { get; set; }
|
||||
public double DistanceFromNow { get; set; } // 距离"现在"的距离,用于3D可视化
|
||||
}
|
||||
|
||||
public class TimeCapsuleListQueryDto
|
||||
{
|
||||
public int PageIndex { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 50;
|
||||
public int? Status { get; set; }
|
||||
public int? Type { get; set; }
|
||||
public bool? IncludeExpired { get; set; } = false;
|
||||
}
|
||||
|
||||
public class TimeCapsuleViewResponseDto
|
||||
{
|
||||
public List<TimeCapsuleViewDto> Capsules { get; set; } = new List<TimeCapsuleViewDto>();
|
||||
public string Scene { get; set; } = "SPACE"; // 场景类型: SPACE | OCEAN
|
||||
public string Background { get; set; } = string.Empty; // 背景配置
|
||||
}
|
||||
|
||||
public class TimeCapsuleViewDto
|
||||
{
|
||||
public int CapsuleId { get; set; }
|
||||
public int MailId { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public DateTime SendTime { get; set; }
|
||||
public DateTime DeliveryTime { get; set; }
|
||||
public double Progress { get; set; } // 0-1 的进度
|
||||
public TimeCapsulePosition Position { get; set; } = new TimeCapsulePosition();
|
||||
public string Style { get; set; } = string.Empty;
|
||||
public double GlowIntensity { get; set; } // 发光强度
|
||||
}
|
||||
|
||||
public class TimeCapsulePosition
|
||||
{
|
||||
public double X { get; set; } // 0-1 相对位置
|
||||
public double Y { get; set; }
|
||||
public double Z { get; set; }
|
||||
}
|
||||
|
||||
public class TimeCapsuleStyleUpdateDto
|
||||
{
|
||||
[Required(ErrorMessage = "样式是必填项")]
|
||||
[StringLength(50, ErrorMessage = "样式长度不能超过50个字符")]
|
||||
public string Style { get; set; } = string.Empty;
|
||||
|
||||
[Range(0.0, 1.0, ErrorMessage = "发光强度必须在0.0-1.0之间")]
|
||||
public double? GlowIntensity { get; set; }
|
||||
}
|
||||
}
|
||||
58
FutureMailAPI/DTOs/UserDTOs.cs
Normal file
58
FutureMailAPI/DTOs/UserDTOs.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class UserRegisterDto
|
||||
{
|
||||
[Required(ErrorMessage = "用户名是必填项")]
|
||||
[StringLength(100, MinimumLength = 3, ErrorMessage = "用户名长度必须在3-100个字符之间")]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "邮箱是必填项")]
|
||||
[EmailAddress(ErrorMessage = "请输入有效的邮箱地址")]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "密码是必填项")]
|
||||
[StringLength(100, MinimumLength = 6, ErrorMessage = "密码长度必须在6-100个字符之间")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
[StringLength(100, ErrorMessage = "昵称长度不能超过100个字符")]
|
||||
public string? Nickname { get; set; }
|
||||
}
|
||||
|
||||
public class UserResponseDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Username { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string? Nickname { get; set; }
|
||||
public string? Avatar { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? LastLoginAt { get; set; }
|
||||
}
|
||||
|
||||
public class UserUpdateDto
|
||||
{
|
||||
[StringLength(100, ErrorMessage = "昵称长度不能超过100个字符")]
|
||||
public string? Nickname { get; set; }
|
||||
|
||||
[StringLength(500, ErrorMessage = "头像URL长度不能超过500个字符")]
|
||||
public string? Avatar { get; set; }
|
||||
}
|
||||
|
||||
public class ChangePasswordDto
|
||||
{
|
||||
[Required(ErrorMessage = "当前密码是必填项")]
|
||||
public string CurrentPassword { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "新密码是必填项")]
|
||||
[StringLength(100, MinimumLength = 6, ErrorMessage = "密码长度必须在6-100个字符之间")]
|
||||
public string NewPassword { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public class RefreshTokenRequestDto
|
||||
{
|
||||
[Required(ErrorMessage = "令牌是必填项")]
|
||||
public string Token { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
13
FutureMailAPI/DTOs/UserLoginDto.cs
Normal file
13
FutureMailAPI/DTOs/UserLoginDto.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace FutureMailAPI.DTOs
|
||||
{
|
||||
public class UserLoginDto
|
||||
{
|
||||
[Required(ErrorMessage = "用户名或邮箱是必填项")]
|
||||
public string UsernameOrEmail { get; set; } = string.Empty;
|
||||
|
||||
[Required(ErrorMessage = "密码是必填项")]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
138
FutureMailAPI/Data/FutureMailDbContext.cs
Normal file
138
FutureMailAPI/Data/FutureMailDbContext.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Data
|
||||
{
|
||||
public class FutureMailDbContext : DbContext
|
||||
{
|
||||
public FutureMailDbContext(DbContextOptions<FutureMailDbContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public DbSet<User> Users { get; set; }
|
||||
public DbSet<SentMail> SentMails { get; set; }
|
||||
public DbSet<ReceivedMail> ReceivedMails { get; set; }
|
||||
public DbSet<TimeCapsule> TimeCapsules { get; set; }
|
||||
public DbSet<OAuthClient> OAuthClients { get; set; }
|
||||
public DbSet<OAuthAuthorizationCode> OAuthAuthorizationCodes { get; set; }
|
||||
public DbSet<OAuthAccessToken> OAuthAccessTokens { get; set; }
|
||||
public DbSet<OAuthRefreshToken> OAuthRefreshTokens { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// 配置User实体
|
||||
modelBuilder.Entity<User>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => e.Email).IsUnique();
|
||||
entity.HasIndex(e => e.Username).IsUnique();
|
||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置SentMail实体
|
||||
modelBuilder.Entity<SentMail>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.Sender)
|
||||
.WithMany(u => u.SentMails)
|
||||
.HasForeignKey(e => e.SenderId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
entity.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.RecipientId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
entity.Property(e => e.SentAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置ReceivedMail实体
|
||||
modelBuilder.Entity<ReceivedMail>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.SentMail)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.SentMailId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne<User>()
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.RecipientId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.Property(e => e.ReceivedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置TimeCapsule实体
|
||||
modelBuilder.Entity<TimeCapsule>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.User)
|
||||
.WithMany(u => u.TimeCapsules)
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne<SentMail>()
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.SentMailId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置OAuthClient实体
|
||||
modelBuilder.Entity<OAuthClient>(entity =>
|
||||
{
|
||||
entity.HasIndex(e => e.ClientId).IsUnique();
|
||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
entity.Property(e => e.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置OAuthAuthorizationCode实体
|
||||
modelBuilder.Entity<OAuthAuthorizationCode>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.Client)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.ClientId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(e => e.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置OAuthAccessToken实体
|
||||
modelBuilder.Entity<OAuthAccessToken>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.Client)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.ClientId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(e => e.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
|
||||
// 配置OAuthRefreshToken实体
|
||||
modelBuilder.Entity<OAuthRefreshToken>(entity =>
|
||||
{
|
||||
entity.HasOne(e => e.Client)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.ClientId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(e => e.User)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.UserId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
46
FutureMailAPI/Extensions/HttpContextExtensions.cs
Normal file
46
FutureMailAPI/Extensions/HttpContextExtensions.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Extensions
|
||||
{
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取当前用户ID
|
||||
/// </summary>
|
||||
public static int? GetCurrentUserId(this HttpContext context)
|
||||
{
|
||||
if (context.Items.TryGetValue("UserId", out var userIdObj) && userIdObj is int userId)
|
||||
{
|
||||
return userId;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前用户邮箱
|
||||
/// </summary>
|
||||
public static string? GetCurrentUserEmail(this HttpContext context)
|
||||
{
|
||||
if (context.Items.TryGetValue("UserEmail", out var userEmailObj) && userEmailObj is string userEmail)
|
||||
{
|
||||
return userEmail;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前访问令牌
|
||||
/// </summary>
|
||||
public static OAuthAccessToken? GetCurrentAccessToken(this HttpContext context)
|
||||
{
|
||||
if (context.Items.TryGetValue("AccessToken", out var accessTokenObj) && accessTokenObj is OAuthAccessToken accessToken)
|
||||
{
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
FutureMailAPI/FutureMail.db
Normal file
BIN
FutureMailAPI/FutureMail.db
Normal file
Binary file not shown.
BIN
FutureMailAPI/FutureMail.db-shm
Normal file
BIN
FutureMailAPI/FutureMail.db-shm
Normal file
Binary file not shown.
BIN
FutureMailAPI/FutureMail.db-wal
Normal file
BIN
FutureMailAPI/FutureMail.db-wal
Normal file
Binary file not shown.
30
FutureMailAPI/FutureMailAPI.csproj
Normal file
30
FutureMailAPI/FutureMailAPI.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<NoWarn>$(NoWarn);1591</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.9">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
|
||||
<PackageReference Include="Quartz" Version="3.15.0" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
FutureMailAPI/FutureMailAPI.http
Normal file
6
FutureMailAPI/FutureMailAPI.http
Normal file
@@ -0,0 +1,6 @@
|
||||
@FutureMailAPI_HostAddress = http://localhost:5054
|
||||
|
||||
GET {{FutureMailAPI_HostAddress}}/weatherforecast/
|
||||
Accept: application/json
|
||||
|
||||
###
|
||||
36
FutureMailAPI/Helpers/FileUploadOperationFilter.cs
Normal file
36
FutureMailAPI/Helpers/FileUploadOperationFilter.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Swagger文件上传操作过滤器
|
||||
/// </summary>
|
||||
public class FileUploadOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
var fileUploadMime = "multipart/form-data";
|
||||
if (operation.RequestBody == null || !operation.RequestBody.Content.Any(x => x.Key.Equals(fileUploadMime, StringComparison.InvariantCultureIgnoreCase)))
|
||||
return;
|
||||
|
||||
var fileParams = context.MethodInfo.GetParameters()
|
||||
.Where(p => p.ParameterType == typeof(IFormFile) ||
|
||||
(p.ParameterType.IsClass && p.ParameterType.GetProperties().Any(prop => prop.PropertyType == typeof(IFormFile))))
|
||||
.ToList();
|
||||
|
||||
if (!fileParams.Any())
|
||||
return;
|
||||
|
||||
operation.RequestBody.Content[fileUploadMime].Schema.Properties =
|
||||
fileParams.ToDictionary(k => k.Name!, v => new OpenApiSchema()
|
||||
{
|
||||
Type = "string",
|
||||
Format = "binary"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
97
FutureMailAPI/Helpers/JwtHelper.cs
Normal file
97
FutureMailAPI/Helpers/JwtHelper.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
public interface IJwtHelper
|
||||
{
|
||||
string GenerateToken(User user);
|
||||
string GenerateToken(int userId, string username, string email);
|
||||
ClaimsPrincipal? ValidateToken(string token);
|
||||
}
|
||||
|
||||
public class JwtHelper : IJwtHelper
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public JwtHelper(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public string GenerateToken(User user)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
|
||||
new Claim(ClaimTypes.Name, user.Username),
|
||||
new Claim(ClaimTypes.Email, user.Email)
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddDays(7),
|
||||
Issuer = _configuration["Jwt:Issuer"],
|
||||
Audience = _configuration["Jwt:Audience"],
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
public string GenerateToken(int userId, string username, string email)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
|
||||
new Claim(ClaimTypes.Name, username),
|
||||
new Claim(ClaimTypes.Email, email)
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddDays(7),
|
||||
Issuer = _configuration["Jwt:Issuer"],
|
||||
Audience = _configuration["Jwt:Audience"],
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
return tokenHandler.WriteToken(token);
|
||||
}
|
||||
|
||||
public ClaimsPrincipal? ValidateToken(string token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
|
||||
|
||||
var validationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = _configuration["Jwt:Issuer"],
|
||||
ValidateAudience = true,
|
||||
ValidAudience = _configuration["Jwt:Audience"],
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
};
|
||||
|
||||
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
|
||||
return principal;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
FutureMailAPI/Helpers/MailDeliveryJob.cs
Normal file
102
FutureMailAPI/Helpers/MailDeliveryJob.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Quartz;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
[DisallowConcurrentExecution]
|
||||
public class MailDeliveryJob : IJob
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
private readonly ILogger<MailDeliveryJob> _logger;
|
||||
|
||||
public MailDeliveryJob(FutureMailDbContext context, ILogger<MailDeliveryJob> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation("开始执行邮件投递任务: {time}", DateTime.Now);
|
||||
|
||||
try
|
||||
{
|
||||
// 查找所有需要投递的邮件
|
||||
var mailsToDeliver = await _context.SentMails
|
||||
.Where(m => m.Status == 1 && m.DeliveryTime <= DateTime.UtcNow)
|
||||
.Include(m => m.Sender)
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogInformation("找到 {count} 封待投递邮件", mailsToDeliver.Count);
|
||||
|
||||
foreach (var mail in mailsToDeliver)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 更新邮件状态为投递中
|
||||
mail.Status = 2;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 根据收件人类型创建接收邮件记录
|
||||
if (mail.RecipientType == 0) // 发给自己
|
||||
{
|
||||
var receivedMail = new ReceivedMail
|
||||
{
|
||||
SentMailId = mail.Id,
|
||||
RecipientId = mail.SenderId,
|
||||
ReceivedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.ReceivedMails.Add(receivedMail);
|
||||
}
|
||||
else if (mail.RecipientType == 1 && mail.RecipientId.HasValue) // 发给指定用户
|
||||
{
|
||||
var receivedMail = new ReceivedMail
|
||||
{
|
||||
SentMailId = mail.Id,
|
||||
RecipientId = mail.RecipientId.Value,
|
||||
ReceivedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.ReceivedMails.Add(receivedMail);
|
||||
}
|
||||
else if (mail.RecipientType == 2) // 公开时间胶囊
|
||||
{
|
||||
// 公开时间胶囊的处理逻辑,可能需要更复杂的机制
|
||||
// 这里简化处理,暂时不实现
|
||||
}
|
||||
|
||||
// 更新邮件状态为已送达
|
||||
mail.Status = 3;
|
||||
|
||||
// 更新对应的时间胶囊状态
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.SentMailId == mail.Id);
|
||||
|
||||
if (timeCapsule != null)
|
||||
{
|
||||
timeCapsule.Status = 3; // 已开启
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("邮件 {mailId} 投递成功", mail.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "投递邮件 {mailId} 时发生错误", mail.Id);
|
||||
// 可以添加重试逻辑或错误通知
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("邮件投递任务完成: {time}", DateTime.Now);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行邮件投递任务时发生错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
FutureMailAPI/Helpers/PasswordHelper.cs
Normal file
86
FutureMailAPI/Helpers/PasswordHelper.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
public interface IPasswordHelper
|
||||
{
|
||||
string HashPassword(string password);
|
||||
bool VerifyPassword(string password, string hash);
|
||||
string HashPassword(string password, string salt);
|
||||
string GenerateSalt();
|
||||
}
|
||||
|
||||
public class PasswordHelper : IPasswordHelper
|
||||
{
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
// 生成随机盐值
|
||||
byte[] salt;
|
||||
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
|
||||
|
||||
// 使用PBKDF2算法生成哈希
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
|
||||
byte[] hash = pbkdf2.GetBytes(20);
|
||||
|
||||
// 组合盐值和哈希值
|
||||
byte[] hashBytes = new byte[36];
|
||||
Array.Copy(salt, 0, hashBytes, 0, 16);
|
||||
Array.Copy(hash, 0, hashBytes, 16, 20);
|
||||
|
||||
// 转换为Base64字符串
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
public string GenerateSalt()
|
||||
{
|
||||
// 生成随机盐值
|
||||
byte[] salt;
|
||||
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
|
||||
|
||||
// 转换为Base64字符串
|
||||
return Convert.ToBase64String(salt);
|
||||
}
|
||||
|
||||
public string HashPassword(string password, string salt)
|
||||
{
|
||||
// 将Base64盐值转换为字节数组
|
||||
byte[] saltBytes = Convert.FromBase64String(salt);
|
||||
|
||||
// 使用PBKDF2算法生成哈希
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000);
|
||||
byte[] hash = pbkdf2.GetBytes(20);
|
||||
|
||||
// 转换为Base64字符串
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
public bool VerifyPassword(string password, string hash)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从存储的哈希中提取盐值
|
||||
byte[] hashBytes = Convert.FromBase64String(hash);
|
||||
byte[] salt = new byte[16];
|
||||
Array.Copy(hashBytes, 0, salt, 0, 16);
|
||||
|
||||
// 使用相同的盐值计算密码的哈希
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
|
||||
byte[] computedHash = pbkdf2.GetBytes(20);
|
||||
|
||||
// 比较两个哈希值
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
if (hashBytes[i + 16] != computedHash[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
62
FutureMailAPI/Middleware/OAuthAuthenticationMiddleware.cs
Normal file
62
FutureMailAPI/Middleware/OAuthAuthenticationMiddleware.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Middleware
|
||||
{
|
||||
public class OAuthAuthenticationMiddleware
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly ILogger<OAuthAuthenticationMiddleware> _logger;
|
||||
|
||||
public OAuthAuthenticationMiddleware(RequestDelegate next, ILogger<OAuthAuthenticationMiddleware> logger)
|
||||
{
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, IOAuthService oauthService)
|
||||
{
|
||||
// 检查是否需要OAuth认证
|
||||
var endpoint = context.GetEndpoint();
|
||||
if (endpoint != null)
|
||||
{
|
||||
// 如果端点标记为AllowAnonymous,则跳过认证
|
||||
var allowAnonymousAttribute = endpoint.Metadata.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>();
|
||||
if (allowAnonymousAttribute != null)
|
||||
{
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查Authorization头
|
||||
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
|
||||
if (authHeader != null && authHeader.StartsWith("Bearer "))
|
||||
{
|
||||
var token = authHeader.Substring("Bearer ".Length).Trim();
|
||||
|
||||
// 验证令牌
|
||||
var validationResult = await oauthService.ValidateTokenAsync(token);
|
||||
if (validationResult.Success)
|
||||
{
|
||||
// 获取访问令牌信息
|
||||
var accessToken = await oauthService.GetAccessTokenAsync(token);
|
||||
if (accessToken != null)
|
||||
{
|
||||
// 将用户信息添加到HttpContext
|
||||
context.Items["UserId"] = accessToken.UserId;
|
||||
context.Items["UserEmail"] = accessToken.User.Email;
|
||||
context.Items["AccessToken"] = accessToken;
|
||||
|
||||
await _next(context);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有有效的令牌,返回401未授权
|
||||
context.Response.StatusCode = 401;
|
||||
await context.Response.WriteAsync("未授权访问");
|
||||
}
|
||||
}
|
||||
}
|
||||
327
FutureMailAPI/Migrations/20251014071025_InitialCreate.Designer.cs
generated
Normal file
327
FutureMailAPI/Migrations/20251014071025_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,327 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FutureMailAPI.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
[DbContext(typeof(FutureMailDbContext))]
|
||||
[Migration("20251014071025_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsReplied")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("ReadAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReceivedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ReplyMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.ToTable("ReceivedMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Attachments")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DeliveryTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptionKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SenderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Theme")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TriggerDetails")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TriggerType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SentMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<double>("Opacity")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionX")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionY")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionZ")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("Rotation")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Size")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.HasIndex("SentMailId1");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TimeCapsules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany("ReceivedMails")
|
||||
.HasForeignKey("RecipientId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("SentMail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId1");
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Sender")
|
||||
.WithMany("SentMails")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany("TimeCapsules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SentMail");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Navigation("ReceivedMails");
|
||||
|
||||
b.Navigation("SentMails");
|
||||
|
||||
b.Navigation("TimeCapsules");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
233
FutureMailAPI/Migrations/20251014071025_InitialCreate.cs
Normal file
233
FutureMailAPI/Migrations/20251014071025_InitialCreate.cs
Normal file
@@ -0,0 +1,233 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Username = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
Email = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "TEXT", maxLength: 255, nullable: false),
|
||||
Nickname = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
Avatar = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
LastLoginAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "SentMails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Title = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
Content = table.Column<string>(type: "TEXT", nullable: false),
|
||||
SenderId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RecipientType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RecipientId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
SentAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
DeliveryTime = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TriggerType = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
TriggerDetails = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Attachments = table.Column<string>(type: "TEXT", nullable: true),
|
||||
IsEncrypted = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
EncryptionKey = table.Column<string>(type: "TEXT", nullable: true),
|
||||
Theme = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
|
||||
RecipientId1 = table.Column<int>(type: "INTEGER", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_SentMails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_SentMails_Users_RecipientId",
|
||||
column: x => x.RecipientId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_SentMails_Users_RecipientId1",
|
||||
column: x => x.RecipientId1,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_SentMails_Users_SenderId",
|
||||
column: x => x.SenderId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReceivedMails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SentMailId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RecipientId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
ReceivedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
IsRead = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ReadAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
IsReplied = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ReplyMailId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
RecipientId1 = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReceivedMails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedMails_SentMails_SentMailId",
|
||||
column: x => x.SentMailId,
|
||||
principalTable: "SentMails",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedMails_Users_RecipientId",
|
||||
column: x => x.RecipientId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReceivedMails_Users_RecipientId1",
|
||||
column: x => x.RecipientId1,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TimeCapsules",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
UserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
SentMailId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
PositionX = table.Column<double>(type: "REAL", nullable: false),
|
||||
PositionY = table.Column<double>(type: "REAL", nullable: false),
|
||||
PositionZ = table.Column<double>(type: "REAL", nullable: false),
|
||||
Size = table.Column<double>(type: "REAL", nullable: false),
|
||||
Color = table.Column<string>(type: "TEXT", maxLength: 20, nullable: true),
|
||||
Opacity = table.Column<double>(type: "REAL", nullable: false),
|
||||
Rotation = table.Column<double>(type: "REAL", nullable: false),
|
||||
Status = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Type = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
SentMailId1 = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TimeCapsules", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TimeCapsules_SentMails_SentMailId",
|
||||
column: x => x.SentMailId,
|
||||
principalTable: "SentMails",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_TimeCapsules_SentMails_SentMailId1",
|
||||
column: x => x.SentMailId1,
|
||||
principalTable: "SentMails",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_TimeCapsules_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedMails_RecipientId",
|
||||
table: "ReceivedMails",
|
||||
column: "RecipientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedMails_RecipientId1",
|
||||
table: "ReceivedMails",
|
||||
column: "RecipientId1");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReceivedMails_SentMailId",
|
||||
table: "ReceivedMails",
|
||||
column: "SentMailId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SentMails_RecipientId",
|
||||
table: "SentMails",
|
||||
column: "RecipientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SentMails_RecipientId1",
|
||||
table: "SentMails",
|
||||
column: "RecipientId1");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_SentMails_SenderId",
|
||||
table: "SentMails",
|
||||
column: "SenderId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TimeCapsules_SentMailId",
|
||||
table: "TimeCapsules",
|
||||
column: "SentMailId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TimeCapsules_SentMailId1",
|
||||
table: "TimeCapsules",
|
||||
column: "SentMailId1");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TimeCapsules_UserId",
|
||||
table: "TimeCapsules",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_Email",
|
||||
table: "Users",
|
||||
column: "Email",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_Username",
|
||||
table: "Users",
|
||||
column: "Username",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReceivedMails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TimeCapsules");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "SentMails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
335
FutureMailAPI/Migrations/20251015003104_AddUserPreferences.Designer.cs
generated
Normal file
335
FutureMailAPI/Migrations/20251015003104_AddUserPreferences.Designer.cs
generated
Normal file
@@ -0,0 +1,335 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FutureMailAPI.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
[DbContext(typeof(FutureMailDbContext))]
|
||||
[Migration("20251015003104_AddUserPreferences")]
|
||||
partial class AddUserPreferences
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsReplied")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("ReadAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReceivedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ReplyMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.ToTable("ReceivedMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Attachments")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DeliveryTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptionKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SenderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Theme")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TriggerDetails")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TriggerType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SentMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<double>("Opacity")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionX")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionY")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionZ")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("Rotation")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Size")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.HasIndex("SentMailId1");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TimeCapsules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredBackground")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredScene")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany("ReceivedMails")
|
||||
.HasForeignKey("RecipientId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("SentMail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId1");
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Sender")
|
||||
.WithMany("SentMails")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany("TimeCapsules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SentMail");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Navigation("ReceivedMails");
|
||||
|
||||
b.Navigation("SentMails");
|
||||
|
||||
b.Navigation("TimeCapsules");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddUserPreferences : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PreferredBackground",
|
||||
table: "Users",
|
||||
type: "TEXT",
|
||||
maxLength: 50,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "PreferredScene",
|
||||
table: "Users",
|
||||
type: "TEXT",
|
||||
maxLength: 20,
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PreferredBackground",
|
||||
table: "Users");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "PreferredScene",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
559
FutureMailAPI/Migrations/20251016011551_AddOAuthEntities.Designer.cs
generated
Normal file
559
FutureMailAPI/Migrations/20251016011551_AddOAuthEntities.Designer.cs
generated
Normal file
@@ -0,0 +1,559 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FutureMailAPI.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
[DbContext(typeof(FutureMailDbContext))]
|
||||
[Migration("20251016011551_AddOAuthEntities")]
|
||||
partial class AddOAuthEntities
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsRevoked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthAccessTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RedirectUri")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthAuthorizationCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientSecret")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RedirectUris")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OAuthClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthRefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsReplied")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("ReadAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReceivedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ReplyMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.ToTable("ReceivedMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Attachments")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DeliveryTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptionKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SenderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Theme")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TriggerDetails")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TriggerType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SentMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<double>("Opacity")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionX")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionY")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionZ")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("Rotation")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Size")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.HasIndex("SentMailId1");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TimeCapsules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredBackground")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredScene")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany("ReceivedMails")
|
||||
.HasForeignKey("RecipientId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("SentMail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId1");
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Sender")
|
||||
.WithMany("SentMails")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany("TimeCapsules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SentMail");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Navigation("ReceivedMails");
|
||||
|
||||
b.Navigation("SentMails");
|
||||
|
||||
b.Navigation("TimeCapsules");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
180
FutureMailAPI/Migrations/20251016011551_AddOAuthEntities.cs
Normal file
180
FutureMailAPI/Migrations/20251016011551_AddOAuthEntities.cs
Normal file
@@ -0,0 +1,180 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddOAuthEntities : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OAuthClients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ClientId = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClientSecret = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Name = table.Column<string>(type: "TEXT", nullable: false),
|
||||
RedirectUris = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Scopes = table.Column<string>(type: "TEXT", nullable: false),
|
||||
IsActive = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OAuthClients", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OAuthAccessTokens",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Token = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
UserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
Scopes = table.Column<string>(type: "TEXT", nullable: false),
|
||||
IsRevoked = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OAuthAccessTokens", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OAuthAccessTokens_OAuthClients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "OAuthClients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OAuthAccessTokens_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OAuthAuthorizationCodes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Code = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
UserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
RedirectUri = table.Column<string>(type: "TEXT", nullable: false),
|
||||
Scopes = table.Column<string>(type: "TEXT", nullable: false),
|
||||
IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OAuthAuthorizationCodes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OAuthAuthorizationCodes_OAuthClients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "OAuthClients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OAuthAuthorizationCodes_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OAuthRefreshTokens",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Token = table.Column<string>(type: "TEXT", nullable: false),
|
||||
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
UserId = table.Column<int>(type: "INTEGER", nullable: false),
|
||||
IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
|
||||
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OAuthRefreshTokens", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OAuthRefreshTokens_OAuthClients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "OAuthClients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_OAuthRefreshTokens_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthAccessTokens_ClientId",
|
||||
table: "OAuthAccessTokens",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthAccessTokens_UserId",
|
||||
table: "OAuthAccessTokens",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthAuthorizationCodes_ClientId",
|
||||
table: "OAuthAuthorizationCodes",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthAuthorizationCodes_UserId",
|
||||
table: "OAuthAuthorizationCodes",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthClients_ClientId",
|
||||
table: "OAuthClients",
|
||||
column: "ClientId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthRefreshTokens_ClientId",
|
||||
table: "OAuthRefreshTokens",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OAuthRefreshTokens_UserId",
|
||||
table: "OAuthRefreshTokens",
|
||||
column: "UserId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "OAuthAccessTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OAuthAuthorizationCodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OAuthRefreshTokens");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OAuthClients");
|
||||
}
|
||||
}
|
||||
}
|
||||
564
FutureMailAPI/Migrations/20251016012504_AddSaltToUser.Designer.cs
generated
Normal file
564
FutureMailAPI/Migrations/20251016012504_AddSaltToUser.Designer.cs
generated
Normal file
@@ -0,0 +1,564 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FutureMailAPI.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
[DbContext(typeof(FutureMailDbContext))]
|
||||
[Migration("20251016012504_AddSaltToUser")]
|
||||
partial class AddSaltToUser
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsRevoked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthAccessTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RedirectUri")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthAuthorizationCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientSecret")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RedirectUris")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OAuthClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthRefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsReplied")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("ReadAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReceivedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ReplyMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.ToTable("ReceivedMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Attachments")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DeliveryTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptionKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SenderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Theme")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TriggerDetails")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TriggerType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SentMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<double>("Opacity")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionX")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionY")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionZ")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("Rotation")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Size")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.HasIndex("SentMailId1");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TimeCapsules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredBackground")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredScene")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany("ReceivedMails")
|
||||
.HasForeignKey("RecipientId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("SentMail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId1");
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Sender")
|
||||
.WithMany("SentMails")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany("TimeCapsules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SentMail");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Navigation("ReceivedMails");
|
||||
|
||||
b.Navigation("SentMails");
|
||||
|
||||
b.Navigation("TimeCapsules");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
30
FutureMailAPI/Migrations/20251016012504_AddSaltToUser.cs
Normal file
30
FutureMailAPI/Migrations/20251016012504_AddSaltToUser.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddSaltToUser : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Salt",
|
||||
table: "Users",
|
||||
type: "TEXT",
|
||||
maxLength: 255,
|
||||
nullable: false,
|
||||
defaultValue: "");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Salt",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
||||
561
FutureMailAPI/Migrations/FutureMailDbContextModelSnapshot.cs
Normal file
561
FutureMailAPI/Migrations/FutureMailDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,561 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FutureMailAPI.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FutureMailAPI.Migrations
|
||||
{
|
||||
[DbContext(typeof(FutureMailDbContext))]
|
||||
partial class FutureMailDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsRevoked")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthAccessTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Code")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RedirectUri")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthAuthorizationCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("ClientSecret")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RedirectUris")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Scopes")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("UpdatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("OAuthClients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<DateTime>("ExpiresAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Token")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("OAuthRefreshTokens");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsRead")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("IsReplied")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("ReadAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("ReceivedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("ReplyMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.ToTable("ReceivedMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Attachments")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Content")
|
||||
.IsRequired()
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DeliveryTime")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptionKey")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsEncrypted")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int?>("RecipientId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("RecipientType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SenderId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("SentAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Theme")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("TriggerDetails")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TriggerType")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RecipientId");
|
||||
|
||||
b.HasIndex("RecipientId1");
|
||||
|
||||
b.HasIndex("SenderId");
|
||||
|
||||
b.ToTable("SentMails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Color")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<double>("Opacity")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionX")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionY")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("PositionZ")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<double>("Rotation")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("SentMailId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("SentMailId1")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<double>("Size")
|
||||
.HasColumnType("REAL");
|
||||
|
||||
b.Property<int>("Status")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("UserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("SentMailId");
|
||||
|
||||
b.HasIndex("SentMailId1");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("TimeCapsules");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("Avatar")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime?>("LastLoginAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Nickname")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredBackground")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("PreferredScene")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Salt")
|
||||
.IsRequired()
|
||||
.HasMaxLength(255)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Email")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Username")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
|
||||
.WithMany()
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany("ReceivedMails")
|
||||
.HasForeignKey("RecipientId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("SentMail");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Recipient")
|
||||
.WithMany()
|
||||
.HasForeignKey("RecipientId1");
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "Sender")
|
||||
.WithMany("SentMails")
|
||||
.HasForeignKey("SenderId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Recipient");
|
||||
|
||||
b.Navigation("Sender");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
|
||||
{
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
|
||||
.WithMany()
|
||||
.HasForeignKey("SentMailId1")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("FutureMailAPI.Models.User", "User")
|
||||
.WithMany("TimeCapsules")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("SentMail");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
|
||||
{
|
||||
b.Navigation("ReceivedMails");
|
||||
|
||||
b.Navigation("SentMails");
|
||||
|
||||
b.Navigation("TimeCapsules");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
63
FutureMailAPI/Models/OAuthModels.cs
Normal file
63
FutureMailAPI/Models/OAuthModels.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace FutureMailAPI.Models
|
||||
{
|
||||
public class OAuthClient
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
public string ClientSecret { get; set; } = string.Empty;
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string RedirectUris { get; set; } = string.Empty; // JSON array
|
||||
public string Scopes { get; set; } = string.Empty; // JSON array
|
||||
public bool IsActive { get; set; } = true;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class OAuthAuthorizationCode
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Code { get; set; } = string.Empty;
|
||||
public int ClientId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string RedirectUri { get; set; } = string.Empty;
|
||||
public string Scopes { get; set; } = string.Empty;
|
||||
public bool IsUsed { get; set; } = false;
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public virtual OAuthClient Client { get; set; } = null!;
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class OAuthRefreshToken
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public int ClientId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public bool IsUsed { get; set; } = false;
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public virtual OAuthClient Client { get; set; } = null!;
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
|
||||
public class OAuthAccessToken
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Token { get; set; } = string.Empty;
|
||||
public int ClientId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string Scopes { get; set; } = string.Empty;
|
||||
public bool IsRevoked { get; set; } = false;
|
||||
public DateTime ExpiresAt { get; set; }
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation properties
|
||||
public virtual OAuthClient Client { get; set; } = null!;
|
||||
public virtual User User { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
39
FutureMailAPI/Models/ReceivedMail.cs
Normal file
39
FutureMailAPI/Models/ReceivedMail.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FutureMailAPI.Models
|
||||
{
|
||||
public class ReceivedMail
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public int SentMailId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int RecipientId { get; set; }
|
||||
|
||||
// 接收时间
|
||||
public DateTime ReceivedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// 是否已读
|
||||
public bool IsRead { get; set; } = false;
|
||||
|
||||
// 阅读时间
|
||||
public DateTime? ReadAt { get; set; }
|
||||
|
||||
// 是否已回复
|
||||
public bool IsReplied { get; set; } = false;
|
||||
|
||||
// 回复邮件ID
|
||||
public int? ReplyMailId { get; set; }
|
||||
|
||||
// 导航属性
|
||||
[ForeignKey("SentMailId")]
|
||||
public virtual SentMail SentMail { get; set; } = null!;
|
||||
|
||||
[ForeignKey("RecipientId")]
|
||||
public virtual User Recipient { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
65
FutureMailAPI/Models/SentMail.cs
Normal file
65
FutureMailAPI/Models/SentMail.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FutureMailAPI.Models
|
||||
{
|
||||
public class SentMail
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public string Content { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
public int SenderId { get; set; }
|
||||
|
||||
// 收件人类型: 0-自己, 1-指定用户, 2-公开时间胶囊
|
||||
[Required]
|
||||
public int RecipientType { get; set; }
|
||||
|
||||
// 如果是指定用户,存储用户ID
|
||||
public int? RecipientId { get; set; }
|
||||
|
||||
// 发送时间
|
||||
public DateTime SentAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// 投递时间
|
||||
[Required]
|
||||
public DateTime DeliveryTime { get; set; }
|
||||
|
||||
// 邮件状态: 0-草稿, 1-已发送(待投递), 2-投递中, 3-已送达
|
||||
[Required]
|
||||
public int Status { get; set; } = 0;
|
||||
|
||||
// 触发条件类型: 0-时间, 1-地点, 2-事件
|
||||
[Required]
|
||||
public int TriggerType { get; set; } = 0;
|
||||
|
||||
// 触发条件详情(JSON格式存储)
|
||||
public string? TriggerDetails { get; set; }
|
||||
|
||||
// 附件路径(JSON数组格式)
|
||||
public string? Attachments { get; set; }
|
||||
|
||||
// 是否加密
|
||||
public bool IsEncrypted { get; set; } = false;
|
||||
|
||||
// 加密密钥(如果使用端到端加密)
|
||||
public string? EncryptionKey { get; set; }
|
||||
|
||||
// 邮件主题/胶囊皮肤
|
||||
[MaxLength(50)]
|
||||
public string? Theme { get; set; }
|
||||
|
||||
// 导航属性
|
||||
[ForeignKey("SenderId")]
|
||||
public virtual User Sender { get; set; } = null!;
|
||||
|
||||
public virtual User? Recipient { get; set; }
|
||||
}
|
||||
}
|
||||
53
FutureMailAPI/Models/TimeCapsule.cs
Normal file
53
FutureMailAPI/Models/TimeCapsule.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FutureMailAPI.Models
|
||||
{
|
||||
public class TimeCapsule
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
public int UserId { get; set; }
|
||||
|
||||
[Required]
|
||||
public int SentMailId { get; set; }
|
||||
|
||||
// 胶囊位置信息(X, Y, Z坐标)
|
||||
public double PositionX { get; set; }
|
||||
public double PositionY { get; set; }
|
||||
public double PositionZ { get; set; }
|
||||
|
||||
// 胶囊大小
|
||||
public double Size { get; set; } = 1.0;
|
||||
|
||||
// 胶囊颜色
|
||||
[MaxLength(20)]
|
||||
public string? Color { get; set; }
|
||||
|
||||
// 胶囊透明度
|
||||
public double Opacity { get; set; } = 1.0;
|
||||
|
||||
// 胶囊旋转角度
|
||||
public double Rotation { get; set; } = 0;
|
||||
|
||||
// 胶囊状态: 0-未激活, 1-漂浮中, 2-即将到达, 3-已开启
|
||||
[Required]
|
||||
public int Status { get; set; } = 0;
|
||||
|
||||
// 胶囊类型: 0-普通, 1-特殊, 2-限时
|
||||
[Required]
|
||||
public int Type { get; set; } = 0;
|
||||
|
||||
// 创建时间
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// 导航属性
|
||||
[ForeignKey("UserId")]
|
||||
public virtual User User { get; set; } = null!;
|
||||
|
||||
[ForeignKey("SentMailId")]
|
||||
public virtual SentMail SentMail { get; set; } = null!;
|
||||
}
|
||||
}
|
||||
50
FutureMailAPI/Models/User.cs
Normal file
50
FutureMailAPI/Models/User.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace FutureMailAPI.Models
|
||||
{
|
||||
public class User
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public string Username { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(255)]
|
||||
public string Email { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(255)]
|
||||
public string PasswordHash { get; set; } = string.Empty;
|
||||
|
||||
[Required]
|
||||
[MaxLength(255)]
|
||||
public string Salt { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(100)]
|
||||
public string? Nickname { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Avatar { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? LastLoginAt { get; set; }
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
[MaxLength(20)]
|
||||
public string? PreferredScene { get; set; } = "SPACE";
|
||||
|
||||
[MaxLength(50)]
|
||||
public string? PreferredBackground { get; set; } = "default";
|
||||
|
||||
// 导航属性
|
||||
public virtual ICollection<SentMail> SentMails { get; set; } = new List<SentMail>();
|
||||
public virtual ICollection<ReceivedMail> ReceivedMails { get; set; } = new List<ReceivedMail>();
|
||||
public virtual ICollection<TimeCapsule> TimeCapsules { get; set; } = new List<TimeCapsule>();
|
||||
}
|
||||
}
|
||||
12
FutureMailAPI/OAuthPasswordTest.http
Normal file
12
FutureMailAPI/OAuthPasswordTest.http
Normal file
@@ -0,0 +1,12 @@
|
||||
// 测试OAuth 2.0密码授权流程
|
||||
// 1. 使用密码授权获取访问令牌
|
||||
POST http://localhost:5001/api/v1/oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=password&username=testuser3&password=password123&client_id=futuremail-client&client_secret=futuremail-secret
|
||||
|
||||
###
|
||||
|
||||
// 2. 使用访问令牌访问受保护的API
|
||||
GET http://localhost:5001/api/v1/mails
|
||||
Authorization: Bearer YOUR_ACCESS_TOKEN
|
||||
37
FutureMailAPI/OAuthTest.http
Normal file
37
FutureMailAPI/OAuthTest.http
Normal file
@@ -0,0 +1,37 @@
|
||||
// 测试OAuth 2.0认证流程
|
||||
// 1. 创建OAuth客户端
|
||||
POST http://localhost:5001/api/v1/oauth/clients
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"clientName": "TestClient",
|
||||
"redirectUris": ["http://localhost:3000/callback"],
|
||||
"scopes": ["read", "write"]
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
// 2. 获取授权码(在浏览器中访问以下URL)
|
||||
// http://localhost:5001/api/v1/oauth/authorize?response_type=code&client_id=test_client&redirect_uri=http://localhost:3000/callback&scope=read&state=xyz
|
||||
|
||||
###
|
||||
|
||||
// 3. 使用授权码获取访问令牌
|
||||
POST http://localhost:5001/api/v1/oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=authorization_code&code=YOUR_AUTHORIZATION_CODE&redirect_uri=http://localhost:3000/callback&client_id=test_client&client_secret=YOUR_CLIENT_SECRET
|
||||
|
||||
###
|
||||
|
||||
// 4. 使用访问令牌访问受保护的API
|
||||
GET http://localhost:5001/api/v1/mails
|
||||
Authorization: Bearer YOUR_ACCESS_TOKEN
|
||||
|
||||
###
|
||||
|
||||
// 5. 刷新访问令牌
|
||||
POST http://localhost:5001/api/v1/oauth/token
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=test_client&client_secret=YOUR_CLIENT_SECRET
|
||||
116
FutureMailAPI/Program.cs
Normal file
116
FutureMailAPI/Program.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Quartz;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Helpers;
|
||||
using FutureMailAPI.Services;
|
||||
using FutureMailAPI.Middleware;
|
||||
using FutureMailAPI.Extensions;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// 配置服务器监听所有网络接口的5001端口
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
options.ListenAnyIP(5001);
|
||||
});
|
||||
|
||||
// 配置数据库连接
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
|
||||
builder.Services.AddDbContext<FutureMailDbContext>(options =>
|
||||
options.UseSqlite(connectionString));
|
||||
|
||||
// 配置OAuth 2.0认证
|
||||
// 注意:我们使用自定义中间件实现OAuth 2.0认证,而不是使用内置的JWT认证
|
||||
|
||||
// 配置Swagger/OpenAPI
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo { Title = "FutureMail API", Version = "v1" });
|
||||
c.ResolveConflictingActions(apiDescriptions => apiDescriptions.First());
|
||||
|
||||
// 添加文件上传支持
|
||||
c.OperationFilter<FileUploadOperationFilter>();
|
||||
});
|
||||
|
||||
// 注册服务
|
||||
builder.Services.AddScoped<IPasswordHelper, PasswordHelper>();
|
||||
builder.Services.AddScoped<IUserService, UserService>();
|
||||
builder.Services.AddScoped<IMailService, MailService>();
|
||||
builder.Services.AddScoped<ITimeCapsuleService, TimeCapsuleService>();
|
||||
builder.Services.AddScoped<IAuthService, AuthService>();
|
||||
builder.Services.AddScoped<IAIAssistantService, AIAssistantService>();
|
||||
builder.Services.AddScoped<IPersonalSpaceService, PersonalSpaceService>();
|
||||
builder.Services.AddScoped<IFileUploadService, FileUploadService>();
|
||||
builder.Services.AddScoped<INotificationService, NotificationService>();
|
||||
builder.Services.AddScoped<IOAuthService, OAuthService>();
|
||||
builder.Services.AddScoped<IInitializationService, InitializationService>();
|
||||
|
||||
// 配置Quartz任务调度
|
||||
builder.Services.AddQuartz(q =>
|
||||
{
|
||||
// 注册邮件投递任务
|
||||
var jobKey = new JobKey("MailDeliveryJob");
|
||||
q.AddJob<MailDeliveryJob>(opts => opts.WithIdentity(jobKey));
|
||||
|
||||
// 创建触发器 - 每分钟执行一次
|
||||
q.AddTrigger(opts => opts
|
||||
.ForJob(jobKey)
|
||||
.WithIdentity("MailDeliveryJob-trigger")
|
||||
.WithCronSchedule("0 * * ? * *")); // 每分钟执行一次
|
||||
});
|
||||
|
||||
// 添加Quartz主机服务
|
||||
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
|
||||
|
||||
// 添加控制器
|
||||
builder.Services.AddControllers();
|
||||
|
||||
// 添加CORS
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll", builder =>
|
||||
{
|
||||
builder.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.AllowAnyHeader();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// 初始化系统数据
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var initializationService = scope.ServiceProvider.GetRequiredService<IInitializationService>();
|
||||
await initializationService.InitializeAsync();
|
||||
}
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
|
||||
// 配置静态文件服务
|
||||
app.UseStaticFiles();
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(Path.Combine(builder.Environment.ContentRootPath, "uploads")),
|
||||
RequestPath = "/uploads"
|
||||
});
|
||||
|
||||
app.UseCors("AllowAll");
|
||||
|
||||
// 添加OAuth 2.0认证中间件
|
||||
app.UseMiddleware<OAuthAuthenticationMiddleware>();
|
||||
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
app.Run();
|
||||
23
FutureMailAPI/Properties/launchSettings.json
Normal file
23
FutureMailAPI/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://0.0.0.0:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "https://localhost:7236;http://0.0.0.0:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
307
FutureMailAPI/Services/AIAssistantService.cs
Normal file
307
FutureMailAPI/Services/AIAssistantService.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class AIAssistantService : IAIAssistantService
|
||||
{
|
||||
private readonly ILogger<AIAssistantService> _logger;
|
||||
|
||||
public AIAssistantService(ILogger<AIAssistantService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<WritingAssistantResponseDto>> GetWritingAssistanceAsync(WritingAssistantRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里会调用真实的AI服务(如OpenAI GPT)
|
||||
// 目前我们使用模拟数据
|
||||
|
||||
var response = new WritingAssistantResponseDto
|
||||
{
|
||||
Content = GenerateWritingContent(request),
|
||||
Suggestions = GenerateWritingSuggestions(request),
|
||||
EstimatedTime = EstimateWritingTime(request)
|
||||
};
|
||||
|
||||
return ApiResponse<WritingAssistantResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取写作辅助时发生错误");
|
||||
return ApiResponse<WritingAssistantResponseDto>.ErrorResult("获取写作辅助失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<SentimentAnalysisResponseDto>> AnalyzeSentimentAsync(SentimentAnalysisRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里会调用真实的情感分析服务
|
||||
// 目前我们使用模拟数据
|
||||
|
||||
var response = AnalyzeSentiment(request.Content);
|
||||
|
||||
return ApiResponse<SentimentAnalysisResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "分析情感时发生错误");
|
||||
return ApiResponse<SentimentAnalysisResponseDto>.ErrorResult("情感分析失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<FuturePredictionResponseDto>> PredictFutureAsync(FuturePredictionRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里会调用真实的预测服务
|
||||
// 目前我们使用模拟数据
|
||||
|
||||
var response = GenerateFuturePrediction(request);
|
||||
|
||||
return ApiResponse<FuturePredictionResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "预测未来时发生错误");
|
||||
return ApiResponse<FuturePredictionResponseDto>.ErrorResult("未来预测失败");
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateWritingContent(WritingAssistantRequestDto request)
|
||||
{
|
||||
// 模拟AI生成内容
|
||||
return request.Type switch
|
||||
{
|
||||
WritingAssistantType.OUTLINE => GenerateOutline(request.Prompt, request.Tone, request.Length),
|
||||
WritingAssistantType.DRAFT => GenerateDraft(request.Prompt, request.Tone, request.Length),
|
||||
WritingAssistantType.COMPLETE => GenerateCompleteContent(request.Prompt, request.Tone, request.Length),
|
||||
_ => "抱歉,无法生成内容。"
|
||||
};
|
||||
}
|
||||
|
||||
private string GenerateOutline(string prompt, WritingTone tone, WritingLength length)
|
||||
{
|
||||
return $"基于您的提示\"{prompt}\",我为您生成了以下大纲:\n\n1. 引言\n2. 主要观点\n3. 支持论据\n4. 结论\n\n这个大纲适合{GetToneDescription(tone)}的写作风格,预计可以写成{GetLengthDescription(length)}的内容。";
|
||||
}
|
||||
|
||||
private string GenerateDraft(string prompt, WritingTone tone, WritingLength length)
|
||||
{
|
||||
return $"关于\"{prompt}\"的草稿:\n\n{GetToneDescription(tone)}的开场白...\n\n主要内容的初步构思...\n\n需要进一步完善的结尾部分。\n\n这是一个初步草稿,您可以根据需要进一步修改和完善。";
|
||||
}
|
||||
|
||||
private string GenerateCompleteContent(string prompt, WritingTone tone, WritingLength length)
|
||||
{
|
||||
return $"关于\"{prompt}\"的完整内容:\n\n{GetToneDescription(tone)}的开场白,引出主题...\n\n详细阐述主要观点,包含丰富的细节和例子...\n\n深入分析并提供有力的支持论据...\n\n{GetToneDescription(tone)}的结尾,总结全文并留下深刻印象。\n\n这是一篇完整的{GetLengthDescription(length)}文章,您可以直接使用或根据需要进行微调。";
|
||||
}
|
||||
|
||||
private List<string> GenerateWritingSuggestions(WritingAssistantRequestDto request)
|
||||
{
|
||||
var suggestions = new List<string>();
|
||||
|
||||
switch (request.Type)
|
||||
{
|
||||
case WritingAssistantType.OUTLINE:
|
||||
suggestions.Add("考虑添加更多子论点来丰富大纲结构");
|
||||
suggestions.Add("为每个主要观点添加关键词或简短描述");
|
||||
break;
|
||||
case WritingAssistantType.DRAFT:
|
||||
suggestions.Add("添加更多具体例子来支持您的观点");
|
||||
suggestions.Add("考虑调整段落顺序以改善逻辑流程");
|
||||
break;
|
||||
case WritingAssistantType.COMPLETE:
|
||||
suggestions.Add("检查语法和拼写错误");
|
||||
suggestions.Add("考虑添加过渡词来改善段落间的连接");
|
||||
break;
|
||||
}
|
||||
|
||||
suggestions.Add($"尝试使用{GetToneDescription(request.Tone)}的表达方式");
|
||||
suggestions.Add($"考虑将内容调整到{GetLengthDescription(request.Length)}的长度");
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private int EstimateWritingTime(WritingAssistantRequestDto request)
|
||||
{
|
||||
return request.Type switch
|
||||
{
|
||||
WritingAssistantType.OUTLINE => 5,
|
||||
WritingAssistantType.DRAFT => 15,
|
||||
WritingAssistantType.COMPLETE => 30,
|
||||
_ => 10
|
||||
};
|
||||
}
|
||||
|
||||
private string GetToneDescription(WritingTone tone)
|
||||
{
|
||||
return tone switch
|
||||
{
|
||||
WritingTone.FORMAL => "正式",
|
||||
WritingTone.CASUAL => "轻松随意",
|
||||
WritingTone.EMOTIONAL => "情感丰富",
|
||||
WritingTone.INSPIRATIONAL => "鼓舞人心",
|
||||
_ => "中性"
|
||||
};
|
||||
}
|
||||
|
||||
private string GetLengthDescription(WritingLength length)
|
||||
{
|
||||
return length switch
|
||||
{
|
||||
WritingLength.SHORT => "简短",
|
||||
WritingLength.MEDIUM => "中等长度",
|
||||
WritingLength.LONG => "长篇",
|
||||
_ => "适中"
|
||||
};
|
||||
}
|
||||
|
||||
private SentimentAnalysisResponseDto AnalyzeSentiment(string content)
|
||||
{
|
||||
// 模拟情感分析
|
||||
var random = new Random();
|
||||
|
||||
// 简单的关键词分析(实际应用中应使用更复杂的算法)
|
||||
var positiveKeywords = new[] { "开心", "快乐", "爱", "美好", "成功", "希望", "感谢", "幸福" };
|
||||
var negativeKeywords = new[] { "悲伤", "难过", "失败", "痛苦", "失望", "愤怒", "焦虑", "恐惧" };
|
||||
|
||||
var positiveCount = positiveKeywords.Count(keyword => content.Contains(keyword));
|
||||
var negativeCount = negativeKeywords.Count(keyword => content.Contains(keyword));
|
||||
|
||||
SentimentType sentiment;
|
||||
if (positiveCount > negativeCount)
|
||||
sentiment = SentimentType.POSITIVE;
|
||||
else if (negativeCount > positiveCount)
|
||||
sentiment = SentimentType.NEGATIVE;
|
||||
else if (positiveCount > 0 && negativeCount > 0)
|
||||
sentiment = SentimentType.MIXED;
|
||||
else
|
||||
sentiment = SentimentType.NEUTRAL;
|
||||
|
||||
var emotions = new List<EmotionScore>();
|
||||
|
||||
// 根据情感类型生成情绪分数
|
||||
switch (sentiment)
|
||||
{
|
||||
case SentimentType.POSITIVE:
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.HAPPY, Score = 0.8 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.HOPEFUL, Score = 0.6 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.GRATEFUL, Score = 0.5 });
|
||||
break;
|
||||
case SentimentType.NEGATIVE:
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.SAD, Score = 0.8 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.ANXIOUS, Score = 0.6 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.CONFUSED, Score = 0.4 });
|
||||
break;
|
||||
case SentimentType.MIXED:
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.NOSTALGIC, Score = 0.7 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.HOPEFUL, Score = 0.5 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.SAD, Score = 0.4 });
|
||||
break;
|
||||
default:
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.CONFUSED, Score = 0.3 });
|
||||
emotions.Add(new EmotionScore { Type = EmotionType.HOPEFUL, Score = 0.3 });
|
||||
break;
|
||||
}
|
||||
|
||||
// 提取关键词(简单实现)
|
||||
var words = content.Split(new[] { ' ', ',', '.', '!', '?', ';', ':', ',', '。', '!', '?', ';', ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var keywords = words.Where(word => word.Length > 3).GroupBy(word => word)
|
||||
.OrderByDescending(g => g.Count())
|
||||
.Take(5)
|
||||
.Select(g => g.Key)
|
||||
.ToList();
|
||||
|
||||
// 生成摘要
|
||||
var summary = content.Length > 100 ? content.Substring(0, 100) + "..." : content;
|
||||
|
||||
return new SentimentAnalysisResponseDto
|
||||
{
|
||||
Sentiment = sentiment,
|
||||
Confidence = 0.7 + random.NextDouble() * 0.3, // 0.7-1.0之间的随机数
|
||||
Emotions = emotions,
|
||||
Keywords = keywords,
|
||||
Summary = summary
|
||||
};
|
||||
}
|
||||
|
||||
private FuturePredictionResponseDto GenerateFuturePrediction(FuturePredictionRequestDto request)
|
||||
{
|
||||
// 模拟未来预测
|
||||
var random = new Random();
|
||||
|
||||
var prediction = request.Type switch
|
||||
{
|
||||
PredictionType.CAREER => GenerateCareerPrediction(request.Content, request.DaysAhead),
|
||||
PredictionType.RELATIONSHIP => GenerateRelationshipPrediction(request.Content, request.DaysAhead),
|
||||
PredictionType.HEALTH => GenerateHealthPrediction(request.Content, request.DaysAhead),
|
||||
PredictionType.FINANCIAL => GenerateFinancialPrediction(request.Content, request.DaysAhead),
|
||||
PredictionType.PERSONAL_GROWTH => GeneratePersonalGrowthPrediction(request.Content, request.DaysAhead),
|
||||
_ => "无法进行预测。"
|
||||
};
|
||||
|
||||
var factors = GeneratePredictionFactors(request.Type);
|
||||
var suggestions = GeneratePredictionSuggestions(request.Type);
|
||||
|
||||
return new FuturePredictionResponseDto
|
||||
{
|
||||
Prediction = prediction,
|
||||
Confidence = 0.6 + random.NextDouble() * 0.4, // 0.6-1.0之间的随机数
|
||||
Factors = factors,
|
||||
Suggestions = suggestions
|
||||
};
|
||||
}
|
||||
|
||||
private string GenerateCareerPrediction(string content, int daysAhead)
|
||||
{
|
||||
return $"基于您提供的信息\"{content}\",预测在未来{daysAhead}天内,您可能会遇到新的职业机会。这可能是一个晋升机会、一个新项目或是一个学习新技能的机会。建议您保持开放的心态,积极接受挑战,这将有助于您的职业发展。";
|
||||
}
|
||||
|
||||
private string GenerateRelationshipPrediction(string content, int daysAhead)
|
||||
{
|
||||
return $"根据您描述的\"{content}\",预测在未来{daysAhead}天内,您的人际关系可能会有积极的变化。可能会与老朋友重新联系,或者结识新的朋友。建议您保持真诚和开放的态度,这将有助于建立更深厚的人际关系。";
|
||||
}
|
||||
|
||||
private string GenerateHealthPrediction(string content, int daysAhead)
|
||||
{
|
||||
return $"基于您提供的\"{content}\"信息,预测在未来{daysAhead}天内,您的健康状况可能会有所改善。建议您保持良好的作息习惯,适当运动,并注意饮食均衡。这些小的改变可能会带来显著的健康效益。";
|
||||
}
|
||||
|
||||
private string GenerateFinancialPrediction(string content, int daysAhead)
|
||||
{
|
||||
return $"根据您描述的\"{content}\",预测在未来{daysAhead}天内,您的财务状况可能会趋于稳定。可能会有意外的收入或节省开支的机会。建议您制定合理的预算计划,并考虑长期投资策略。";
|
||||
}
|
||||
|
||||
private string GeneratePersonalGrowthPrediction(string content, int daysAhead)
|
||||
{
|
||||
return $"基于您分享的\"{content}\",预测在未来{daysAhead}天内,您将有机会在个人成长方面取得进展。可能会发现新的兴趣爱好,或者在学习新技能方面取得突破。建议您保持好奇心,勇于尝试新事物。";
|
||||
}
|
||||
|
||||
private List<string> GeneratePredictionFactors(PredictionType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PredictionType.CAREER => new List<string> { "行业趋势", "个人技能", "经济环境", "人脉资源" },
|
||||
PredictionType.RELATIONSHIP => new List<string> { "沟通方式", "共同兴趣", "价值观", "情感需求" },
|
||||
PredictionType.HEALTH => new List<string> { "生活习惯", "遗传因素", "环境因素", "心理状态" },
|
||||
PredictionType.FINANCIAL => new List<string> { "收入水平", "消费习惯", "投资决策", "市场环境" },
|
||||
PredictionType.PERSONAL_GROWTH => new List<string> { "学习能力", "自我认知", "生活经历", "目标设定" },
|
||||
_ => new List<string> { "未知因素" }
|
||||
};
|
||||
}
|
||||
|
||||
private List<string> GeneratePredictionSuggestions(PredictionType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
PredictionType.CAREER => new List<string> { "持续学习新技能", "扩展人脉网络", "设定明确的职业目标", "保持积极的工作态度" },
|
||||
PredictionType.RELATIONSHIP => new List<string> { "保持真诚沟通", "尊重他人观点", "定期维护关系", "表达感激之情" },
|
||||
PredictionType.HEALTH => new List<string> { "保持规律作息", "均衡饮食", "适量运动", "定期体检" },
|
||||
PredictionType.FINANCIAL => new List<string> { "制定预算计划", "减少不必要开支", "考虑长期投资", "建立应急基金" },
|
||||
PredictionType.PERSONAL_GROWTH => new List<string> { "设定学习目标", "尝试新体验", "反思自我", "寻求反馈" },
|
||||
_ => new List<string> { "保持积极态度" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
168
FutureMailAPI/Services/AuthService.cs
Normal file
168
FutureMailAPI/Services/AuthService.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using FutureMailAPI.Helpers;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IAuthService
|
||||
{
|
||||
Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
|
||||
Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto);
|
||||
Task<ApiResponse<bool>> ValidateTokenAsync(string token);
|
||||
Task<ApiResponse<string>> RefreshTokenAsync(string token);
|
||||
Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret);
|
||||
}
|
||||
|
||||
public class AuthService : IAuthService
|
||||
{
|
||||
private readonly IUserService _userService;
|
||||
private readonly IPasswordHelper _passwordHelper;
|
||||
private readonly IOAuthService _oauthService;
|
||||
|
||||
public AuthService(
|
||||
IUserService userService,
|
||||
IPasswordHelper passwordHelper,
|
||||
IOAuthService oauthService)
|
||||
{
|
||||
_userService = userService;
|
||||
_passwordHelper = passwordHelper;
|
||||
_oauthService = oauthService;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
|
||||
{
|
||||
// 使用默认客户端ID和密钥进行OAuth登录
|
||||
// 在实际应用中,这些应该从配置中获取
|
||||
var defaultClientId = "futuremail_default_client";
|
||||
var defaultClientSecret = "futuremail_default_secret";
|
||||
|
||||
return await LoginWithOAuthAsync(loginDto, defaultClientId, defaultClientSecret);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret)
|
||||
{
|
||||
// 创建OAuth登录请求
|
||||
var oauthLoginDto = new OAuthLoginDto
|
||||
{
|
||||
UsernameOrEmail = loginDto.UsernameOrEmail,
|
||||
Password = loginDto.Password,
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret,
|
||||
Scope = "read write" // 默认权限范围
|
||||
};
|
||||
|
||||
// 使用OAuth服务进行登录
|
||||
var oauthResult = await _oauthService.LoginAsync(oauthLoginDto);
|
||||
|
||||
if (!oauthResult.Success)
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult(oauthResult.Message ?? "登录失败");
|
||||
}
|
||||
|
||||
// 获取用户信息
|
||||
var userResult = await _userService.GetUserByUsernameOrEmailAsync(loginDto.UsernameOrEmail);
|
||||
|
||||
if (!userResult.Success || userResult.Data == null)
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult("获取用户信息失败");
|
||||
}
|
||||
|
||||
var user = userResult.Data;
|
||||
|
||||
// 创建用户响应DTO
|
||||
var userResponse = new UserResponseDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
Email = user.Email,
|
||||
Nickname = user.Nickname,
|
||||
Avatar = user.Avatar,
|
||||
CreatedAt = user.CreatedAt,
|
||||
LastLoginAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
// 创建认证响应DTO,使用OAuth令牌
|
||||
var authResponse = new AuthResponseDto
|
||||
{
|
||||
Token = oauthResult.Data.AccessToken,
|
||||
RefreshToken = oauthResult.Data.RefreshToken,
|
||||
Expires = DateTime.UtcNow.AddSeconds(oauthResult.Data.ExpiresIn),
|
||||
User = userResponse
|
||||
};
|
||||
|
||||
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto)
|
||||
{
|
||||
// 检查用户名是否已存在
|
||||
var existingUserResult = await _userService.GetUserByUsernameAsync(registerDto.Username);
|
||||
|
||||
if (existingUserResult.Success && existingUserResult.Data != null)
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
var existingEmailResult = await _userService.GetUserByEmailAsync(registerDto.Email);
|
||||
|
||||
if (existingEmailResult.Success && existingEmailResult.Data != null)
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult("邮箱已被注册");
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
var createUserResult = await _userService.CreateUserAsync(registerDto);
|
||||
|
||||
if (!createUserResult.Success)
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
|
||||
}
|
||||
|
||||
// 注册成功后,自动使用OAuth登录
|
||||
var loginDto = new UserLoginDto
|
||||
{
|
||||
UsernameOrEmail = registerDto.Username,
|
||||
Password = registerDto.Password
|
||||
};
|
||||
|
||||
return await LoginAsync(loginDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
|
||||
{
|
||||
// 注意:在OAuth 2.0中,令牌验证应该由OAuth中间件处理
|
||||
// 这里我们暂时返回成功,实际使用时应该通过OAuth 2.0的令牌验证流程
|
||||
return ApiResponse<bool>.SuccessResult(true);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<string>> RefreshTokenAsync(string token)
|
||||
{
|
||||
// 在OAuth 2.0中,刷新令牌需要客户端ID和密钥
|
||||
// 这里我们使用默认客户端凭据
|
||||
var defaultClientId = "futuremail_default_client";
|
||||
var defaultClientSecret = "futuremail_default_secret";
|
||||
|
||||
// 创建OAuth刷新令牌请求
|
||||
var oauthTokenRequest = new OAuthTokenRequestDto
|
||||
{
|
||||
ClientId = defaultClientId,
|
||||
ClientSecret = defaultClientSecret,
|
||||
RefreshToken = token,
|
||||
GrantType = "refresh_token",
|
||||
Scope = "read write"
|
||||
};
|
||||
|
||||
// 使用OAuth服务刷新令牌
|
||||
var oauthResult = await _oauthService.RefreshTokenAsync(oauthTokenRequest);
|
||||
|
||||
if (!oauthResult.Success)
|
||||
{
|
||||
return ApiResponse<string>.ErrorResult(oauthResult.Message ?? "刷新令牌失败");
|
||||
}
|
||||
|
||||
// 返回新的访问令牌
|
||||
return ApiResponse<string>.SuccessResult(oauthResult.Data.AccessToken, "令牌刷新成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
319
FutureMailAPI/Services/FileUploadService.cs
Normal file
319
FutureMailAPI/Services/FileUploadService.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class FileUploadService : IFileUploadService
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILogger<FileUploadService> _logger;
|
||||
private readonly string _uploadPath;
|
||||
private readonly string _baseUrl;
|
||||
private readonly long _maxFileSize;
|
||||
private readonly List<string> _allowedImageTypes;
|
||||
private readonly List<string> _allowedVideoTypes;
|
||||
private readonly List<string> _allowedDocumentTypes;
|
||||
private readonly List<string> _allowedAudioTypes;
|
||||
|
||||
public FileUploadService(IConfiguration configuration, ILogger<FileUploadService> logger)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_logger = logger;
|
||||
|
||||
// 从配置中获取上传路径和基础URL
|
||||
_uploadPath = configuration["FileUpload:UploadPath"] ?? "uploads";
|
||||
_baseUrl = configuration["FileUpload:BaseUrl"] ?? "http://localhost:5054/uploads";
|
||||
_maxFileSize = long.Parse(configuration["FileUpload:MaxFileSize"] ?? "104857600"); // 默认100MB
|
||||
|
||||
// 允许的文件类型
|
||||
_allowedImageTypes = new List<string> { "image/jpeg", "image/png", "image/gif", "image/webp" };
|
||||
_allowedVideoTypes = new List<string> { "video/mp4", "video/avi", "video/mov", "video/wmv" };
|
||||
_allowedDocumentTypes = new List<string> { "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" };
|
||||
_allowedAudioTypes = new List<string> { "audio/mpeg", "audio/wav", "audio/ogg" };
|
||||
|
||||
// 确保上传目录存在
|
||||
EnsureUploadDirectoryExists();
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<FileUploadResponseDto>> UploadFileAsync(IFormFile file, int userId, FileUploadRequestDto request)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
{
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult("请选择要上传的文件");
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
if (file.Length > _maxFileSize)
|
||||
{
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult($"文件大小不能超过 {_maxFileSize / (1024 * 1024)}MB");
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
var contentType = file.ContentType.ToLower();
|
||||
if (!IsAllowedFileType(contentType, request.Type))
|
||||
{
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult("不支持的文件类型");
|
||||
}
|
||||
|
||||
// 生成唯一文件名
|
||||
var fileId = GenerateFileId();
|
||||
var fileExtension = Path.GetExtension(file.FileName);
|
||||
var fileName = $"{fileId}{fileExtension}";
|
||||
|
||||
// 创建用户目录
|
||||
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
|
||||
var categoryDirectory = string.IsNullOrEmpty(request.Category)
|
||||
? userDirectory
|
||||
: Path.Combine(userDirectory, request.Category);
|
||||
|
||||
Directory.CreateDirectory(categoryDirectory);
|
||||
|
||||
// 保存文件
|
||||
var filePath = Path.Combine(categoryDirectory, fileName);
|
||||
using (var stream = new FileStream(filePath, FileMode.Create))
|
||||
{
|
||||
await file.CopyToAsync(stream);
|
||||
}
|
||||
|
||||
// 生成缩略图(如果是图片)
|
||||
string thumbnailUrl = string.Empty;
|
||||
if (request.Type == AttachmentType.IMAGE)
|
||||
{
|
||||
thumbnailUrl = await GenerateThumbnailAsync(filePath, fileName, categoryDirectory);
|
||||
}
|
||||
|
||||
// 构建文件URL
|
||||
var relativePath = Path.GetRelativePath(_uploadPath, filePath).Replace("\\", "/");
|
||||
var fileUrl = $"{_baseUrl}/{relativePath}";
|
||||
|
||||
// 构建缩略图URL
|
||||
if (!string.IsNullOrEmpty(thumbnailUrl))
|
||||
{
|
||||
var relativeThumbnailPath = Path.GetRelativePath(_uploadPath, thumbnailUrl).Replace("\\", "/");
|
||||
thumbnailUrl = $"{_baseUrl}/{relativeThumbnailPath}";
|
||||
}
|
||||
|
||||
var response = new FileUploadResponseDto
|
||||
{
|
||||
FileId = fileId,
|
||||
FileName = file.FileName,
|
||||
FileUrl = fileUrl,
|
||||
ThumbnailUrl = thumbnailUrl,
|
||||
FileSize = file.Length,
|
||||
ContentType = file.ContentType,
|
||||
Type = request.Type,
|
||||
UploadedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
return ApiResponse<FileUploadResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "上传文件时发生错误");
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult("上传文件失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> DeleteFileAsync(string fileId, int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里应该从数据库中查找文件信息
|
||||
// 目前我们只是简单地根据文件ID删除文件
|
||||
|
||||
// 查找用户目录下的所有文件
|
||||
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
|
||||
if (!Directory.Exists(userDirectory))
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("文件不存在");
|
||||
}
|
||||
|
||||
// 查找匹配的文件
|
||||
var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories);
|
||||
if (files.Length == 0)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("文件不存在");
|
||||
}
|
||||
|
||||
// 删除所有相关文件(包括缩略图)
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(file);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, $"删除文件 {file} 失败");
|
||||
}
|
||||
}
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "删除文件时发生错误");
|
||||
return ApiResponse<bool>.ErrorResult("删除文件失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<FileUploadResponseDto>> GetFileAsync(string fileId, int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里应该从数据库中查找文件信息
|
||||
// 目前我们只是简单地根据文件ID返回文件信息
|
||||
|
||||
// 查找用户目录下的所有文件
|
||||
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
|
||||
if (!Directory.Exists(userDirectory))
|
||||
{
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult("文件不存在");
|
||||
}
|
||||
|
||||
// 查找匹配的文件
|
||||
var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories);
|
||||
if (files.Length == 0)
|
||||
{
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult("文件不存在");
|
||||
}
|
||||
|
||||
// 获取主文件(排除缩略图)
|
||||
var mainFile = files.FirstOrDefault(f => !f.Contains("_thumb."));
|
||||
if (mainFile == null)
|
||||
{
|
||||
mainFile = files[0]; // 如果没有找到主文件,使用第一个文件
|
||||
}
|
||||
|
||||
var fileInfo = new FileInfo(mainFile);
|
||||
var relativePath = Path.GetRelativePath(_uploadPath, mainFile).Replace("\\", "/");
|
||||
var fileUrl = $"{_baseUrl}/{relativePath}";
|
||||
|
||||
// 查找缩略图
|
||||
var thumbnailFile = files.FirstOrDefault(f => f.Contains("_thumb."));
|
||||
string thumbnailUrl = string.Empty;
|
||||
if (thumbnailFile != null)
|
||||
{
|
||||
var relativeThumbnailPath = Path.GetRelativePath(_uploadPath, thumbnailFile).Replace("\\", "/");
|
||||
thumbnailUrl = $"{_baseUrl}/{relativeThumbnailPath}";
|
||||
}
|
||||
|
||||
// 确定文件类型
|
||||
var contentType = GetContentType(fileInfo.Extension);
|
||||
var attachmentType = GetAttachmentType(contentType);
|
||||
|
||||
var response = new FileUploadResponseDto
|
||||
{
|
||||
FileId = fileId,
|
||||
FileName = fileInfo.Name,
|
||||
FileUrl = fileUrl,
|
||||
ThumbnailUrl = thumbnailUrl,
|
||||
FileSize = fileInfo.Length,
|
||||
ContentType = contentType,
|
||||
Type = attachmentType,
|
||||
UploadedAt = fileInfo.CreationTimeUtc
|
||||
};
|
||||
|
||||
return ApiResponse<FileUploadResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取文件信息时发生错误");
|
||||
return ApiResponse<FileUploadResponseDto>.ErrorResult("获取文件信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureUploadDirectoryExists()
|
||||
{
|
||||
if (!Directory.Exists(_uploadPath))
|
||||
{
|
||||
Directory.CreateDirectory(_uploadPath);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAllowedFileType(string contentType, AttachmentType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
AttachmentType.IMAGE => _allowedImageTypes.Contains(contentType),
|
||||
AttachmentType.VIDEO => _allowedVideoTypes.Contains(contentType),
|
||||
AttachmentType.DOCUMENT => _allowedDocumentTypes.Contains(contentType),
|
||||
AttachmentType.VOICE => _allowedAudioTypes.Contains(contentType),
|
||||
_ => true // 其他类型暂时允许
|
||||
};
|
||||
}
|
||||
|
||||
private string GenerateFileId()
|
||||
{
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
private async Task<string> GenerateThumbnailAsync(string filePath, string fileName, string directory)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里应该使用图像处理库(如ImageSharp)生成缩略图
|
||||
// 目前我们只是创建一个简单的缩略图文件名
|
||||
|
||||
var fileExtension = Path.GetExtension(fileName);
|
||||
var thumbnailFileName = $"{Path.GetFileNameWithoutExtension(fileName)}_thumb{fileExtension}";
|
||||
var thumbnailPath = Path.Combine(directory, thumbnailFileName);
|
||||
|
||||
// 这里应该添加实际的缩略图生成代码
|
||||
// 暂时只是复制原文件作为缩略图
|
||||
System.IO.File.Copy(filePath, thumbnailPath);
|
||||
|
||||
return thumbnailPath;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "生成缩略图失败");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetContentType(string extension)
|
||||
{
|
||||
return extension.ToLower() switch
|
||||
{
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".png" => "image/png",
|
||||
".gif" => "image/gif",
|
||||
".webp" => "image/webp",
|
||||
".mp4" => "video/mp4",
|
||||
".avi" => "video/avi",
|
||||
".mov" => "video/mov",
|
||||
".wmv" => "video/wmv",
|
||||
".mp3" => "audio/mpeg",
|
||||
".wav" => "audio/wav",
|
||||
".ogg" => "audio/ogg",
|
||||
".pdf" => "application/pdf",
|
||||
".doc" => "application/msword",
|
||||
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
}
|
||||
|
||||
private AttachmentType GetAttachmentType(string contentType)
|
||||
{
|
||||
if (_allowedImageTypes.Contains(contentType))
|
||||
return AttachmentType.IMAGE;
|
||||
|
||||
if (_allowedVideoTypes.Contains(contentType))
|
||||
return AttachmentType.VIDEO;
|
||||
|
||||
if (_allowedAudioTypes.Contains(contentType))
|
||||
return AttachmentType.VOICE;
|
||||
|
||||
if (_allowedDocumentTypes.Contains(contentType))
|
||||
return AttachmentType.DOCUMENT;
|
||||
|
||||
return AttachmentType.OTHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
FutureMailAPI/Services/IAIAssistantService.cs
Normal file
11
FutureMailAPI/Services/IAIAssistantService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IAIAssistantService
|
||||
{
|
||||
Task<ApiResponse<WritingAssistantResponseDto>> GetWritingAssistanceAsync(WritingAssistantRequestDto request);
|
||||
Task<ApiResponse<SentimentAnalysisResponseDto>> AnalyzeSentimentAsync(SentimentAnalysisRequestDto request);
|
||||
Task<ApiResponse<FuturePredictionResponseDto>> PredictFutureAsync(FuturePredictionRequestDto request);
|
||||
}
|
||||
}
|
||||
12
FutureMailAPI/Services/IFileUploadService.cs
Normal file
12
FutureMailAPI/Services/IFileUploadService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IFileUploadService
|
||||
{
|
||||
Task<ApiResponse<FileUploadResponseDto>> UploadFileAsync(IFormFile file, int userId, FileUploadRequestDto request);
|
||||
Task<ApiResponse<bool>> DeleteFileAsync(string fileId, int userId);
|
||||
Task<ApiResponse<FileUploadResponseDto>> GetFileAsync(string fileId, int userId);
|
||||
}
|
||||
}
|
||||
21
FutureMailAPI/Services/IMailService.cs
Normal file
21
FutureMailAPI/Services/IMailService.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IMailService
|
||||
{
|
||||
Task<ApiResponse<SentMailResponseDto>> CreateMailAsync(int userId, SentMailCreateDto createDto);
|
||||
Task<ApiResponse<SentMailResponseDto>> GetSentMailByIdAsync(int userId, int mailId);
|
||||
Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetSentMailsAsync(int userId, MailListQueryDto queryDto);
|
||||
Task<ApiResponse<SentMailResponseDto>> GetMailByIdAsync(int userId, int mailId);
|
||||
Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetMailsAsync(int userId, MailListQueryDto queryDto);
|
||||
Task<ApiResponse<SentMailResponseDto>> UpdateMailAsync(int userId, int mailId, SentMailUpdateDto updateDto);
|
||||
Task<ApiResponse<bool>> DeleteMailAsync(int userId, int mailId);
|
||||
Task<ApiResponse<PagedResponse<ReceivedMailResponseDto>>> GetReceivedMailsAsync(int userId, MailListQueryDto queryDto);
|
||||
Task<ApiResponse<ReceivedMailResponseDto>> GetReceivedMailByIdAsync(int userId, int mailId);
|
||||
Task<ApiResponse<bool>> MarkReceivedMailAsReadAsync(int userId, int mailId);
|
||||
Task<ApiResponse<bool>> MarkAsReadAsync(int userId, int mailId);
|
||||
Task<ApiResponse<bool>> RevokeMailAsync(int userId, int mailId);
|
||||
}
|
||||
}
|
||||
16
FutureMailAPI/Services/INotificationService.cs
Normal file
16
FutureMailAPI/Services/INotificationService.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface INotificationService
|
||||
{
|
||||
Task<ApiResponse<NotificationDeviceResponseDto>> RegisterDeviceAsync(int userId, NotificationDeviceRequestDto request);
|
||||
Task<ApiResponse<bool>> UnregisterDeviceAsync(int userId, string deviceId);
|
||||
Task<ApiResponse<NotificationSettingsDto>> GetNotificationSettingsAsync(int userId);
|
||||
Task<ApiResponse<bool>> UpdateNotificationSettingsAsync(int userId, NotificationSettingsDto settings);
|
||||
Task<ApiResponse<NotificationListResponseDto>> GetNotificationsAsync(int userId, NotificationListQueryDto query);
|
||||
Task<ApiResponse<bool>> MarkNotificationAsReadAsync(int userId, string notificationId);
|
||||
Task<ApiResponse<bool>> MarkAllNotificationsAsReadAsync(int userId);
|
||||
Task<ApiResponse<bool>> SendNotificationAsync(int userId, NotificationMessageDto notification);
|
||||
}
|
||||
}
|
||||
19
FutureMailAPI/Services/IOAuthService.cs
Normal file
19
FutureMailAPI/Services/IOAuthService.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IOAuthService
|
||||
{
|
||||
Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto);
|
||||
Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId);
|
||||
Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request);
|
||||
Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request);
|
||||
Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request);
|
||||
Task<ApiResponse<bool>> RevokeTokenAsync(string token);
|
||||
Task<ApiResponse<bool>> ValidateTokenAsync(string token);
|
||||
Task<OAuthAccessToken?> GetAccessTokenAsync(string token);
|
||||
Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret);
|
||||
Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto);
|
||||
}
|
||||
}
|
||||
12
FutureMailAPI/Services/IPersonalSpaceService.cs
Normal file
12
FutureMailAPI/Services/IPersonalSpaceService.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IPersonalSpaceService
|
||||
{
|
||||
Task<ApiResponse<TimelineResponseDto>> GetTimelineAsync(int userId, TimelineQueryDto query);
|
||||
Task<ApiResponse<StatisticsResponseDto>> GetStatisticsAsync(int userId);
|
||||
Task<ApiResponse<SubscriptionResponseDto>> GetSubscriptionAsync(int userId);
|
||||
Task<ApiResponse<UserProfileResponseDto>> GetUserProfileAsync(int userId);
|
||||
}
|
||||
}
|
||||
17
FutureMailAPI/Services/ITimeCapsuleService.cs
Normal file
17
FutureMailAPI/Services/ITimeCapsuleService.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface ITimeCapsuleService
|
||||
{
|
||||
Task<ApiResponse<TimeCapsuleResponseDto>> CreateTimeCapsuleAsync(int userId, TimeCapsuleCreateDto createDto);
|
||||
Task<ApiResponse<TimeCapsuleResponseDto>> GetTimeCapsuleByIdAsync(int userId, int capsuleId);
|
||||
Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetTimeCapsulesAsync(int userId, TimeCapsuleListQueryDto queryDto);
|
||||
Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleAsync(int userId, int capsuleId, TimeCapsuleUpdateDto updateDto);
|
||||
Task<ApiResponse<bool>> DeleteTimeCapsuleAsync(int userId, int capsuleId);
|
||||
Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetPublicTimeCapsulesAsync(TimeCapsuleListQueryDto queryDto);
|
||||
Task<ApiResponse<TimeCapsuleResponseDto>> ClaimPublicCapsuleAsync(int userId, int capsuleId);
|
||||
Task<ApiResponse<TimeCapsuleViewResponseDto>> GetTimeCapsuleViewAsync(int userId);
|
||||
Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleStyleAsync(int userId, int capsuleId, TimeCapsuleStyleUpdateDto updateDto);
|
||||
}
|
||||
}
|
||||
69
FutureMailAPI/Services/InitializationService.cs
Normal file
69
FutureMailAPI/Services/InitializationService.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IInitializationService
|
||||
{
|
||||
Task InitializeAsync();
|
||||
}
|
||||
|
||||
public class InitializationService : IInitializationService
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
private readonly ILogger<InitializationService> _logger;
|
||||
|
||||
public InitializationService(FutureMailDbContext context, ILogger<InitializationService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// 确保数据库已创建
|
||||
await _context.Database.EnsureCreatedAsync();
|
||||
|
||||
// 创建默认OAuth客户端(如果不存在)
|
||||
await CreateDefaultOAuthClientAsync();
|
||||
|
||||
_logger.LogInformation("系统初始化完成");
|
||||
}
|
||||
|
||||
private async Task CreateDefaultOAuthClientAsync()
|
||||
{
|
||||
var defaultClientId = "futuremail_default_client";
|
||||
|
||||
// 检查默认客户端是否已存在
|
||||
var existingClient = await _context.OAuthClients
|
||||
.FirstOrDefaultAsync(c => c.ClientId == defaultClientId);
|
||||
|
||||
if (existingClient != null)
|
||||
{
|
||||
_logger.LogInformation("默认OAuth客户端已存在");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建默认OAuth客户端
|
||||
var defaultClient = new OAuthClient
|
||||
{
|
||||
ClientId = defaultClientId,
|
||||
ClientSecret = "futuremail_default_secret",
|
||||
Name = "FutureMail默认客户端",
|
||||
RedirectUris = JsonSerializer.Serialize(new[] { "http://localhost:3000/callback", "http://localhost:8080/callback" }),
|
||||
Scopes = JsonSerializer.Serialize(new[] { "read", "write" }),
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.OAuthClients.Add(defaultClient);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("默认OAuth客户端创建成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
475
FutureMailAPI/Services/MailService.cs
Normal file
475
FutureMailAPI/Services/MailService.cs
Normal file
@@ -0,0 +1,475 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class MailService : IMailService
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
|
||||
public MailService(FutureMailDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<SentMailResponseDto>> CreateMailAsync(int userId, SentMailCreateDto createDto)
|
||||
{
|
||||
// 检查投递时间是否在未来
|
||||
if (createDto.DeliveryTime <= DateTime.UtcNow)
|
||||
{
|
||||
return ApiResponse<SentMailResponseDto>.ErrorResult("投递时间必须是未来时间");
|
||||
}
|
||||
|
||||
// 如果是指定用户,检查用户是否存在
|
||||
int? recipientId = null;
|
||||
if (createDto.RecipientType == 1 && !string.IsNullOrEmpty(createDto.RecipientEmail))
|
||||
{
|
||||
var recipient = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == createDto.RecipientEmail);
|
||||
|
||||
if (recipient == null)
|
||||
{
|
||||
return ApiResponse<SentMailResponseDto>.ErrorResult("收件人不存在");
|
||||
}
|
||||
|
||||
recipientId = recipient.Id;
|
||||
}
|
||||
|
||||
// 创建邮件
|
||||
var mail = new SentMail
|
||||
{
|
||||
Title = createDto.Title,
|
||||
Content = createDto.Content,
|
||||
SenderId = userId,
|
||||
RecipientType = createDto.RecipientType,
|
||||
RecipientId = recipientId,
|
||||
DeliveryTime = createDto.DeliveryTime,
|
||||
SentAt = DateTime.UtcNow, // 显式设置发送时间
|
||||
Status = createDto.DeliveryTime > DateTime.UtcNow ? 1 : 0, // 根据投递时间直接设置状态
|
||||
TriggerType = createDto.TriggerType,
|
||||
TriggerDetails = createDto.TriggerDetails,
|
||||
Attachments = createDto.Attachments,
|
||||
IsEncrypted = createDto.IsEncrypted,
|
||||
EncryptionKey = createDto.EncryptionKey,
|
||||
Theme = createDto.Theme
|
||||
};
|
||||
|
||||
_context.SentMails.Add(mail);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 如果是发送状态(不是草稿),创建时间胶囊
|
||||
if (mail.Status == 1) // 已发送(待投递)
|
||||
{
|
||||
// 创建时间胶囊
|
||||
var timeCapsule = new TimeCapsule
|
||||
{
|
||||
UserId = userId,
|
||||
SentMailId = mail.Id,
|
||||
PositionX = 0,
|
||||
PositionY = 0,
|
||||
PositionZ = 0,
|
||||
Status = 1, // 漂浮中
|
||||
Type = 0 // 普通
|
||||
};
|
||||
|
||||
_context.TimeCapsules.Add(timeCapsule);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var mailResponse = await GetSentMailWithDetailsAsync(mail.Id);
|
||||
|
||||
return ApiResponse<SentMailResponseDto>.SuccessResult(mailResponse, "邮件创建成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetSentMailsAsync(int userId, MailListQueryDto queryDto)
|
||||
{
|
||||
var query = _context.SentMails
|
||||
.Where(m => m.SenderId == userId)
|
||||
.Include(m => m.Sender)
|
||||
.Include(m => m.Recipient)
|
||||
.AsQueryable();
|
||||
|
||||
// 应用筛选条件
|
||||
if (queryDto.Status.HasValue)
|
||||
{
|
||||
query = query.Where(m => m.Status == queryDto.Status.Value);
|
||||
}
|
||||
|
||||
if (queryDto.RecipientType.HasValue)
|
||||
{
|
||||
query = query.Where(m => m.RecipientType == queryDto.RecipientType.Value);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(queryDto.Keyword))
|
||||
{
|
||||
query = query.Where(m => m.Title.Contains(queryDto.Keyword) || m.Content.Contains(queryDto.Keyword));
|
||||
}
|
||||
|
||||
if (queryDto.StartDate.HasValue)
|
||||
{
|
||||
query = query.Where(m => m.SentAt >= queryDto.StartDate.Value);
|
||||
}
|
||||
|
||||
if (queryDto.EndDate.HasValue)
|
||||
{
|
||||
query = query.Where(m => m.SentAt <= queryDto.EndDate.Value);
|
||||
}
|
||||
|
||||
// 排序
|
||||
query = query.OrderByDescending(m => m.SentAt);
|
||||
|
||||
// 分页
|
||||
var totalCount = await query.CountAsync();
|
||||
var mails = await query
|
||||
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
|
||||
.Take(queryDto.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var mailDtos = mails.Select(MapToSentMailResponseDto).ToList();
|
||||
|
||||
var pagedResponse = new PagedResponse<SentMailResponseDto>(
|
||||
mailDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
|
||||
|
||||
return ApiResponse<PagedResponse<SentMailResponseDto>>.SuccessResult(pagedResponse);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<SentMailResponseDto>> GetSentMailByIdAsync(int userId, int mailId)
|
||||
{
|
||||
var mail = await _context.SentMails
|
||||
.Include(m => m.Sender)
|
||||
.Include(m => m.Recipient)
|
||||
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
|
||||
|
||||
if (mail == null)
|
||||
{
|
||||
return ApiResponse<SentMailResponseDto>.ErrorResult("邮件不存在");
|
||||
}
|
||||
|
||||
var mailDto = MapToSentMailResponseDto(mail);
|
||||
|
||||
return ApiResponse<SentMailResponseDto>.SuccessResult(mailDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<SentMailResponseDto>> UpdateMailAsync(int userId, int mailId, SentMailUpdateDto updateDto)
|
||||
{
|
||||
var mail = await _context.SentMails
|
||||
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
|
||||
|
||||
if (mail == null)
|
||||
{
|
||||
return ApiResponse<SentMailResponseDto>.ErrorResult("邮件不存在");
|
||||
}
|
||||
|
||||
// 检查邮件是否已投递,已投递的邮件不能修改
|
||||
if (mail.Status >= 2)
|
||||
{
|
||||
return ApiResponse<SentMailResponseDto>.ErrorResult("已投递的邮件不能修改");
|
||||
}
|
||||
|
||||
// 更新邮件信息
|
||||
if (updateDto.Title != null)
|
||||
{
|
||||
mail.Title = updateDto.Title;
|
||||
}
|
||||
|
||||
if (updateDto.Content != null)
|
||||
{
|
||||
mail.Content = updateDto.Content;
|
||||
}
|
||||
|
||||
if (updateDto.DeliveryTime.HasValue)
|
||||
{
|
||||
if (updateDto.DeliveryTime.Value <= DateTime.UtcNow)
|
||||
{
|
||||
return ApiResponse<SentMailResponseDto>.ErrorResult("投递时间必须是未来时间");
|
||||
}
|
||||
|
||||
mail.DeliveryTime = updateDto.DeliveryTime.Value;
|
||||
}
|
||||
|
||||
if (updateDto.TriggerDetails != null)
|
||||
{
|
||||
mail.TriggerDetails = updateDto.TriggerDetails;
|
||||
}
|
||||
|
||||
if (updateDto.Attachments != null)
|
||||
{
|
||||
mail.Attachments = updateDto.Attachments;
|
||||
}
|
||||
|
||||
if (updateDto.Theme != null)
|
||||
{
|
||||
mail.Theme = updateDto.Theme;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var mailResponse = await GetSentMailWithDetailsAsync(mail.Id);
|
||||
|
||||
return ApiResponse<SentMailResponseDto>.SuccessResult(mailResponse, "邮件更新成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> DeleteMailAsync(int userId, int mailId)
|
||||
{
|
||||
var mail = await _context.SentMails
|
||||
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
|
||||
|
||||
if (mail == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("邮件不存在");
|
||||
}
|
||||
|
||||
// 检查邮件是否已投递,已投递的邮件不能删除
|
||||
if (mail.Status >= 2)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("已投递的邮件不能删除");
|
||||
}
|
||||
|
||||
// 删除相关的时间胶囊
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.SentMailId == mailId);
|
||||
|
||||
if (timeCapsule != null)
|
||||
{
|
||||
_context.TimeCapsules.Remove(timeCapsule);
|
||||
}
|
||||
|
||||
// 删除邮件
|
||||
_context.SentMails.Remove(mail);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true, "邮件删除成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<PagedResponse<ReceivedMailResponseDto>>> GetReceivedMailsAsync(int userId, MailListQueryDto queryDto)
|
||||
{
|
||||
var query = _context.ReceivedMails
|
||||
.Where(r => r.RecipientId == userId)
|
||||
.Include(r => r.SentMail)
|
||||
.ThenInclude(m => m.Sender)
|
||||
.AsQueryable();
|
||||
|
||||
// 应用筛选条件
|
||||
if (queryDto.Status.HasValue)
|
||||
{
|
||||
if (queryDto.Status.Value == 0) // 未读
|
||||
{
|
||||
query = query.Where(r => !r.IsRead);
|
||||
}
|
||||
else if (queryDto.Status.Value == 1) // 已读
|
||||
{
|
||||
query = query.Where(r => r.IsRead);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(queryDto.Keyword))
|
||||
{
|
||||
query = query.Where(r => r.SentMail.Title.Contains(queryDto.Keyword) || r.SentMail.Content.Contains(queryDto.Keyword));
|
||||
}
|
||||
|
||||
if (queryDto.StartDate.HasValue)
|
||||
{
|
||||
query = query.Where(r => r.ReceivedAt >= queryDto.StartDate.Value);
|
||||
}
|
||||
|
||||
if (queryDto.EndDate.HasValue)
|
||||
{
|
||||
query = query.Where(r => r.ReceivedAt <= queryDto.EndDate.Value);
|
||||
}
|
||||
|
||||
// 排序
|
||||
query = query.OrderByDescending(r => r.ReceivedAt);
|
||||
|
||||
// 分页
|
||||
var totalCount = await query.CountAsync();
|
||||
var receivedMails = await query
|
||||
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
|
||||
.Take(queryDto.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var mailDtos = receivedMails.Select(MapToReceivedMailResponseDto).ToList();
|
||||
|
||||
var pagedResponse = new PagedResponse<ReceivedMailResponseDto>(
|
||||
mailDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
|
||||
|
||||
return ApiResponse<PagedResponse<ReceivedMailResponseDto>>.SuccessResult(pagedResponse);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<ReceivedMailResponseDto>> GetReceivedMailByIdAsync(int userId, int mailId)
|
||||
{
|
||||
var receivedMail = await _context.ReceivedMails
|
||||
.Include(r => r.SentMail)
|
||||
.ThenInclude(m => m.Sender)
|
||||
.FirstOrDefaultAsync(r => r.Id == mailId && r.RecipientId == userId);
|
||||
|
||||
if (receivedMail == null)
|
||||
{
|
||||
return ApiResponse<ReceivedMailResponseDto>.ErrorResult("邮件不存在");
|
||||
}
|
||||
|
||||
var mailDto = MapToReceivedMailResponseDto(receivedMail);
|
||||
|
||||
return ApiResponse<ReceivedMailResponseDto>.SuccessResult(mailDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> MarkReceivedMailAsReadAsync(int userId, int mailId)
|
||||
{
|
||||
var receivedMail = await _context.ReceivedMails
|
||||
.FirstOrDefaultAsync(r => r.Id == mailId && r.RecipientId == userId);
|
||||
|
||||
if (receivedMail == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("邮件不存在");
|
||||
}
|
||||
|
||||
if (!receivedMail.IsRead)
|
||||
{
|
||||
receivedMail.IsRead = true;
|
||||
receivedMail.ReadAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true, "邮件已标记为已读");
|
||||
}
|
||||
|
||||
public Task<ApiResponse<SentMailResponseDto>> GetMailByIdAsync(int userId, int mailId)
|
||||
{
|
||||
return GetSentMailByIdAsync(userId, mailId);
|
||||
}
|
||||
|
||||
public Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetMailsAsync(int userId, MailListQueryDto queryDto)
|
||||
{
|
||||
return GetSentMailsAsync(userId, queryDto);
|
||||
}
|
||||
|
||||
public Task<ApiResponse<bool>> MarkAsReadAsync(int userId, int mailId)
|
||||
{
|
||||
return MarkReceivedMailAsReadAsync(userId, mailId);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> RevokeMailAsync(int userId, int mailId)
|
||||
{
|
||||
var mail = await _context.SentMails
|
||||
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
|
||||
|
||||
if (mail == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("邮件不存在");
|
||||
}
|
||||
|
||||
// 检查邮件是否已投递,已投递的邮件不能撤销
|
||||
if (mail.Status >= 2)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("已投递的邮件不能撤销");
|
||||
}
|
||||
|
||||
// 更新邮件状态为已撤销
|
||||
mail.Status = 4; // 4-已撤销
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 更新相关的时间胶囊状态
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.SentMailId == mailId);
|
||||
|
||||
if (timeCapsule != null)
|
||||
{
|
||||
timeCapsule.Status = 3; // 3-已撤销
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true, "邮件已撤销");
|
||||
}
|
||||
|
||||
private async Task<SentMailResponseDto> GetSentMailWithDetailsAsync(int mailId)
|
||||
{
|
||||
var mail = await _context.SentMails
|
||||
.Include(m => m.Sender)
|
||||
.Include(m => m.Recipient)
|
||||
.FirstOrDefaultAsync(m => m.Id == mailId);
|
||||
|
||||
return MapToSentMailResponseDto(mail!);
|
||||
}
|
||||
|
||||
private static SentMailResponseDto MapToSentMailResponseDto(SentMail mail)
|
||||
{
|
||||
return new SentMailResponseDto
|
||||
{
|
||||
Id = mail.Id,
|
||||
Title = mail.Title,
|
||||
Content = mail.Content,
|
||||
SenderId = mail.SenderId,
|
||||
SenderUsername = mail.Sender?.Username ?? "",
|
||||
RecipientType = mail.RecipientType,
|
||||
RecipientId = mail.RecipientId,
|
||||
RecipientUsername = mail.Recipient?.Username ?? "",
|
||||
SentAt = mail.SentAt,
|
||||
DeliveryTime = mail.DeliveryTime,
|
||||
Status = mail.Status,
|
||||
StatusText = GetStatusText(mail.Status),
|
||||
TriggerType = mail.TriggerType,
|
||||
TriggerTypeText = GetTriggerTypeText(mail.TriggerType),
|
||||
TriggerDetails = mail.TriggerDetails,
|
||||
Attachments = mail.Attachments,
|
||||
IsEncrypted = mail.IsEncrypted,
|
||||
Theme = mail.Theme,
|
||||
RecipientTypeText = GetRecipientTypeText(mail.RecipientType),
|
||||
DaysUntilDelivery = (int)(mail.DeliveryTime - DateTime.UtcNow).TotalDays
|
||||
};
|
||||
}
|
||||
|
||||
private static ReceivedMailResponseDto MapToReceivedMailResponseDto(ReceivedMail receivedMail)
|
||||
{
|
||||
return new ReceivedMailResponseDto
|
||||
{
|
||||
Id = receivedMail.Id,
|
||||
SentMailId = receivedMail.SentMailId,
|
||||
Title = receivedMail.SentMail.Title,
|
||||
Content = receivedMail.SentMail.Content,
|
||||
SenderUsername = receivedMail.SentMail.Sender?.Username ?? "",
|
||||
SentAt = receivedMail.SentMail.SentAt,
|
||||
ReceivedAt = receivedMail.ReceivedAt,
|
||||
IsRead = receivedMail.IsRead,
|
||||
ReadAt = receivedMail.ReadAt,
|
||||
IsReplied = receivedMail.IsReplied,
|
||||
ReplyMailId = receivedMail.ReplyMailId,
|
||||
Theme = receivedMail.SentMail.Theme
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "草稿",
|
||||
1 => "已发送(待投递)",
|
||||
2 => "投递中",
|
||||
3 => "已送达",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetRecipientTypeText(int recipientType)
|
||||
{
|
||||
return recipientType switch
|
||||
{
|
||||
0 => "自己",
|
||||
1 => "指定用户",
|
||||
2 => "公开时间胶囊",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetTriggerTypeText(int triggerType)
|
||||
{
|
||||
return triggerType switch
|
||||
{
|
||||
0 => "时间",
|
||||
1 => "地点",
|
||||
2 => "事件",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
104
FutureMailAPI/Services/NotificationService.cs
Normal file
104
FutureMailAPI/Services/NotificationService.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class NotificationService : INotificationService
|
||||
{
|
||||
private readonly ILogger<NotificationService> _logger;
|
||||
|
||||
public NotificationService(ILogger<NotificationService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Task<ApiResponse<NotificationDeviceResponseDto>> RegisterDeviceAsync(int userId, NotificationDeviceRequestDto request)
|
||||
{
|
||||
_logger.LogInformation($"Registering device for user {userId}");
|
||||
var response = new NotificationDeviceResponseDto
|
||||
{
|
||||
DeviceId = "device_" + Guid.NewGuid().ToString("N"),
|
||||
DeviceType = request.DeviceType,
|
||||
IsActive = true,
|
||||
RegisteredAt = DateTime.UtcNow
|
||||
};
|
||||
return Task.FromResult(ApiResponse<NotificationDeviceResponseDto>.SuccessResult(response));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<bool>> UnregisterDeviceAsync(int userId, string deviceId)
|
||||
{
|
||||
_logger.LogInformation($"Unregistering device {deviceId} for user {userId}");
|
||||
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<NotificationSettingsDto>> GetNotificationSettingsAsync(int userId)
|
||||
{
|
||||
_logger.LogInformation($"Getting notification settings for user {userId}");
|
||||
var settings = new NotificationSettingsDto
|
||||
{
|
||||
EmailDelivery = true,
|
||||
PushNotification = true,
|
||||
InAppNotification = true,
|
||||
DeliveryReminder = true,
|
||||
ReceivedNotification = true,
|
||||
SystemUpdates = false,
|
||||
QuietHoursStart = "22:00",
|
||||
QuietHoursEnd = "08:00",
|
||||
EnableQuietHours = false
|
||||
};
|
||||
return Task.FromResult(ApiResponse<NotificationSettingsDto>.SuccessResult(settings));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<bool>> UpdateNotificationSettingsAsync(int userId, NotificationSettingsDto settings)
|
||||
{
|
||||
_logger.LogInformation($"Updating notification settings for user {userId}");
|
||||
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<NotificationListResponseDto>> GetNotificationsAsync(int userId, NotificationListQueryDto query)
|
||||
{
|
||||
_logger.LogInformation($"Getting notifications for user {userId}");
|
||||
var notifications = new List<NotificationMessageDto>
|
||||
{
|
||||
new NotificationMessageDto
|
||||
{
|
||||
Id = "notif_1",
|
||||
UserId = userId,
|
||||
Title = "Test Notification",
|
||||
Body = "This is a test notification",
|
||||
Type = "Info",
|
||||
RelatedEntityId = "mail_1",
|
||||
IsRead = false,
|
||||
CreatedAt = DateTime.UtcNow.AddDays(-1)
|
||||
}
|
||||
};
|
||||
var response = new NotificationListResponseDto
|
||||
{
|
||||
Notifications = notifications,
|
||||
Total = notifications.Count,
|
||||
UnreadCount = 1,
|
||||
Page = query.Page,
|
||||
Size = query.Size
|
||||
};
|
||||
return Task.FromResult(ApiResponse<NotificationListResponseDto>.SuccessResult(response));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<bool>> MarkNotificationAsReadAsync(int userId, string notificationId)
|
||||
{
|
||||
_logger.LogInformation($"Marking notification {notificationId} as read for user {userId}");
|
||||
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<bool>> MarkAllNotificationsAsReadAsync(int userId)
|
||||
{
|
||||
_logger.LogInformation($"Marking all notifications as read for user {userId}");
|
||||
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
|
||||
}
|
||||
|
||||
public Task<ApiResponse<bool>> SendNotificationAsync(int userId, NotificationMessageDto notification)
|
||||
{
|
||||
_logger.LogInformation($"Sending notification to user {userId}");
|
||||
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
416
FutureMailAPI/Services/OAuthService.cs
Normal file
416
FutureMailAPI/Services/OAuthService.cs
Normal file
@@ -0,0 +1,416 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Helpers;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class OAuthService : IOAuthService
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
private readonly ILogger<OAuthService> _logger;
|
||||
private readonly IPasswordHelper _passwordHelper;
|
||||
|
||||
public OAuthService(FutureMailDbContext context, ILogger<OAuthService> logger, IPasswordHelper passwordHelper)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_passwordHelper = passwordHelper;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto)
|
||||
{
|
||||
var clientId = GenerateRandomString(32);
|
||||
var clientSecret = GenerateRandomString(64);
|
||||
|
||||
var client = new OAuthClient
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret,
|
||||
Name = createDto.Name,
|
||||
RedirectUris = JsonSerializer.Serialize(createDto.RedirectUris),
|
||||
Scopes = JsonSerializer.Serialize(createDto.Scopes),
|
||||
IsActive = true,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
UpdatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.OAuthClients.Add(client);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var result = new OAuthClientSecretDto
|
||||
{
|
||||
ClientId = clientId,
|
||||
ClientSecret = clientSecret
|
||||
};
|
||||
|
||||
return ApiResponse<OAuthClientSecretDto>.SuccessResult(result, "OAuth客户端创建成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId)
|
||||
{
|
||||
var client = await _context.OAuthClients
|
||||
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return ApiResponse<OAuthClientDto>.ErrorResult("客户端不存在");
|
||||
}
|
||||
|
||||
var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
|
||||
var scopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
|
||||
|
||||
var result = new OAuthClientDto
|
||||
{
|
||||
Id = client.Id,
|
||||
ClientId = client.ClientId,
|
||||
Name = client.Name,
|
||||
RedirectUris = redirectUris,
|
||||
Scopes = scopes,
|
||||
IsActive = client.IsActive,
|
||||
CreatedAt = client.CreatedAt,
|
||||
UpdatedAt = client.UpdatedAt
|
||||
};
|
||||
|
||||
return ApiResponse<OAuthClientDto>.SuccessResult(result);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request)
|
||||
{
|
||||
// 验证客户端
|
||||
var client = await _context.OAuthClients
|
||||
.FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.IsActive);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的客户端ID");
|
||||
}
|
||||
|
||||
// 验证重定向URI
|
||||
var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
|
||||
if (!redirectUris.Contains(request.RedirectUri))
|
||||
{
|
||||
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的重定向URI");
|
||||
}
|
||||
|
||||
// 验证范围
|
||||
var clientScopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
|
||||
var requestedScopes = request.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
foreach (var scope in requestedScopes)
|
||||
{
|
||||
if (!clientScopes.Contains(scope))
|
||||
{
|
||||
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult($"无效的范围: {scope}");
|
||||
}
|
||||
}
|
||||
|
||||
// 生成授权码
|
||||
var code = GenerateRandomString(64);
|
||||
var authorizationCode = new OAuthAuthorizationCode
|
||||
{
|
||||
Code = code,
|
||||
ClientId = client.Id,
|
||||
UserId = userId,
|
||||
RedirectUri = request.RedirectUri,
|
||||
Scopes = request.Scope,
|
||||
IsUsed = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddMinutes(10), // 授权码10分钟后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.OAuthAuthorizationCodes.Add(authorizationCode);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var result = new OAuthAuthorizationResponseDto
|
||||
{
|
||||
Code = code,
|
||||
State = request.State
|
||||
};
|
||||
|
||||
return ApiResponse<OAuthAuthorizationResponseDto>.SuccessResult(result);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request)
|
||||
{
|
||||
// 验证客户端
|
||||
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
|
||||
if (client == null)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
|
||||
}
|
||||
|
||||
// 验证授权码
|
||||
var authCode = await _context.OAuthAuthorizationCodes
|
||||
.Include(c => c.Client)
|
||||
.Include(c => c.User)
|
||||
.FirstOrDefaultAsync(c => c.Code == request.Code && c.ClientId == client.Id);
|
||||
|
||||
if (authCode == null || authCode.IsUsed || authCode.ExpiresAt < DateTime.UtcNow)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的授权码");
|
||||
}
|
||||
|
||||
// 验证重定向URI
|
||||
if (authCode.RedirectUri != request.RedirectUri)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("重定向URI不匹配");
|
||||
}
|
||||
|
||||
// 标记授权码为已使用
|
||||
authCode.IsUsed = true;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 生成访问令牌和刷新令牌
|
||||
var accessToken = GenerateRandomString(64);
|
||||
var refreshToken = GenerateRandomString(64);
|
||||
|
||||
var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : authCode.Scopes;
|
||||
|
||||
var oauthAccessToken = new OAuthAccessToken
|
||||
{
|
||||
Token = accessToken,
|
||||
ClientId = client.Id,
|
||||
UserId = authCode.UserId,
|
||||
Scopes = scopes,
|
||||
IsRevoked = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var oauthRefreshToken = new OAuthRefreshToken
|
||||
{
|
||||
Token = refreshToken,
|
||||
ClientId = client.Id,
|
||||
UserId = authCode.UserId,
|
||||
IsUsed = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.OAuthAccessTokens.Add(oauthAccessToken);
|
||||
_context.OAuthRefreshTokens.Add(oauthRefreshToken);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var result = new OAuthTokenResponseDto
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TokenType = "Bearer",
|
||||
ExpiresIn = 3600, // 1小时
|
||||
RefreshToken = refreshToken,
|
||||
Scope = scopes
|
||||
};
|
||||
|
||||
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request)
|
||||
{
|
||||
// 验证客户端
|
||||
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
|
||||
if (client == null)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
|
||||
}
|
||||
|
||||
// 验证刷新令牌
|
||||
var refreshToken = await _context.OAuthRefreshTokens
|
||||
.Include(t => t.Client)
|
||||
.Include(t => t.User)
|
||||
.FirstOrDefaultAsync(t => t.Token == request.RefreshToken && t.ClientId == client.Id);
|
||||
|
||||
if (refreshToken == null || refreshToken.IsUsed || refreshToken.ExpiresAt < DateTime.UtcNow)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
|
||||
}
|
||||
|
||||
// 标记旧刷新令牌为已使用
|
||||
refreshToken.IsUsed = true;
|
||||
|
||||
// 撤销旧访问令牌
|
||||
var oldAccessTokens = await _context.OAuthAccessTokens
|
||||
.Where(t => t.UserId == refreshToken.UserId && t.ClientId == client.Id && !t.IsRevoked)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var token in oldAccessTokens)
|
||||
{
|
||||
token.IsRevoked = true;
|
||||
}
|
||||
|
||||
// 生成新的访问令牌和刷新令牌
|
||||
var newAccessToken = GenerateRandomString(64);
|
||||
var newRefreshToken = GenerateRandomString(64);
|
||||
|
||||
var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : "";
|
||||
|
||||
var newOAuthAccessToken = new OAuthAccessToken
|
||||
{
|
||||
Token = newAccessToken,
|
||||
ClientId = client.Id,
|
||||
UserId = refreshToken.UserId,
|
||||
Scopes = scopes,
|
||||
IsRevoked = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var newOAuthRefreshToken = new OAuthRefreshToken
|
||||
{
|
||||
Token = newRefreshToken,
|
||||
ClientId = client.Id,
|
||||
UserId = refreshToken.UserId,
|
||||
IsUsed = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.OAuthAccessTokens.Add(newOAuthAccessToken);
|
||||
_context.OAuthRefreshTokens.Add(newOAuthRefreshToken);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var result = new OAuthTokenResponseDto
|
||||
{
|
||||
AccessToken = newAccessToken,
|
||||
TokenType = "Bearer",
|
||||
ExpiresIn = 3600, // 1小时
|
||||
RefreshToken = newRefreshToken,
|
||||
Scope = scopes
|
||||
};
|
||||
|
||||
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> RevokeTokenAsync(string token)
|
||||
{
|
||||
var accessToken = await _context.OAuthAccessTokens
|
||||
.FirstOrDefaultAsync(t => t.Token == token);
|
||||
|
||||
if (accessToken == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("令牌不存在");
|
||||
}
|
||||
|
||||
accessToken.IsRevoked = true;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true, "令牌已撤销");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
|
||||
{
|
||||
var accessToken = await _context.OAuthAccessTokens
|
||||
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
|
||||
|
||||
if (accessToken == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("无效的令牌");
|
||||
}
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true);
|
||||
}
|
||||
|
||||
public async Task<OAuthAccessToken?> GetAccessTokenAsync(string token)
|
||||
{
|
||||
return await _context.OAuthAccessTokens
|
||||
.Include(t => t.Client)
|
||||
.Include(t => t.User)
|
||||
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
|
||||
}
|
||||
|
||||
public async Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret)
|
||||
{
|
||||
if (string.IsNullOrEmpty(clientSecret))
|
||||
{
|
||||
// 公开客户端,只验证客户端ID
|
||||
return await _context.OAuthClients
|
||||
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
|
||||
}
|
||||
|
||||
// 机密客户端,验证客户端ID和密钥
|
||||
return await _context.OAuthClients
|
||||
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.ClientSecret == clientSecret && c.IsActive);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto)
|
||||
{
|
||||
// 验证客户端
|
||||
var client = await GetClientByCredentialsAsync(loginDto.ClientId, loginDto.ClientSecret);
|
||||
if (client == null)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
|
||||
}
|
||||
|
||||
// 验证用户凭据
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => (u.Email == loginDto.UsernameOrEmail || u.Nickname == loginDto.UsernameOrEmail));
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
|
||||
{
|
||||
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 生成访问令牌和刷新令牌
|
||||
var accessToken = GenerateRandomString(64);
|
||||
var refreshToken = GenerateRandomString(64);
|
||||
|
||||
var oauthAccessToken = new OAuthAccessToken
|
||||
{
|
||||
Token = accessToken,
|
||||
ClientId = client.Id,
|
||||
UserId = user.Id,
|
||||
Scopes = loginDto.Scope,
|
||||
IsRevoked = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var oauthRefreshToken = new OAuthRefreshToken
|
||||
{
|
||||
Token = refreshToken,
|
||||
ClientId = client.Id,
|
||||
UserId = user.Id,
|
||||
IsUsed = false,
|
||||
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.OAuthAccessTokens.Add(oauthAccessToken);
|
||||
_context.OAuthRefreshTokens.Add(oauthRefreshToken);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var result = new OAuthTokenResponseDto
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TokenType = "Bearer",
|
||||
ExpiresIn = 3600, // 1小时
|
||||
RefreshToken = refreshToken,
|
||||
Scope = loginDto.Scope
|
||||
};
|
||||
|
||||
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
|
||||
}
|
||||
|
||||
private string GenerateRandomString(int length)
|
||||
{
|
||||
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var random = new Random();
|
||||
var result = new char[length];
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
result[i] = chars[random.Next(chars.Length)];
|
||||
}
|
||||
|
||||
return new string(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
419
FutureMailAPI/Services/PersonalSpaceService.cs
Normal file
419
FutureMailAPI/Services/PersonalSpaceService.cs
Normal file
@@ -0,0 +1,419 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.DTOs;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class PersonalSpaceService : IPersonalSpaceService
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
private readonly ILogger<PersonalSpaceService> _logger;
|
||||
|
||||
public PersonalSpaceService(FutureMailDbContext context, ILogger<PersonalSpaceService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimelineResponseDto>> GetTimelineAsync(int userId, TimelineQueryDto query)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = new TimelineResponseDto();
|
||||
var timelineDict = new Dictionary<string, TimelineDateDto>();
|
||||
|
||||
// 获取发送的邮件
|
||||
if (query.Type == TimelineType.ALL || query.Type == TimelineType.SENT)
|
||||
{
|
||||
var sentMailsQuery = _context.SentMails
|
||||
.Where(m => m.SenderId == userId)
|
||||
.Include(m => m.Recipient)
|
||||
.AsQueryable();
|
||||
|
||||
if (query.StartDate.HasValue)
|
||||
{
|
||||
sentMailsQuery = sentMailsQuery.Where(m => m.SentAt >= query.StartDate.Value);
|
||||
}
|
||||
|
||||
if (query.EndDate.HasValue)
|
||||
{
|
||||
sentMailsQuery = sentMailsQuery.Where(m => m.SentAt <= query.EndDate.Value);
|
||||
}
|
||||
|
||||
var sentMails = await sentMailsQuery
|
||||
.OrderBy(m => m.SentAt)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var mail in sentMails)
|
||||
{
|
||||
var dateKey = mail.SentAt.ToString("yyyy-MM-dd");
|
||||
|
||||
if (!timelineDict.ContainsKey(dateKey))
|
||||
{
|
||||
timelineDict[dateKey] = new TimelineDateDto
|
||||
{
|
||||
Date = dateKey,
|
||||
Events = new List<TimelineEventDto>()
|
||||
};
|
||||
}
|
||||
|
||||
var recipientName = mail.Recipient?.Username ?? "自己";
|
||||
var recipientAvatar = mail.Recipient?.Avatar;
|
||||
|
||||
timelineDict[dateKey].Events.Add(new TimelineEventDto
|
||||
{
|
||||
Type = TimelineEventType.SENT,
|
||||
MailId = mail.Id,
|
||||
Title = mail.Title,
|
||||
Time = mail.SentAt.ToString("HH:mm"),
|
||||
WithUser = new UserInfoDto
|
||||
{
|
||||
UserId = mail.RecipientId ?? 0,
|
||||
Username = recipientName,
|
||||
Avatar = recipientAvatar
|
||||
},
|
||||
Emotion = AnalyzeMailEmotion(mail.Content)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 获取接收的邮件
|
||||
if (query.Type == TimelineType.ALL || query.Type == TimelineType.RECEIVED)
|
||||
{
|
||||
var receivedMailsQuery = _context.ReceivedMails
|
||||
.Where(r => r.RecipientId == userId)
|
||||
.Include(r => r.SentMail)
|
||||
.ThenInclude(m => m.Sender)
|
||||
.AsQueryable();
|
||||
|
||||
if (query.StartDate.HasValue)
|
||||
{
|
||||
receivedMailsQuery = receivedMailsQuery.Where(r => r.ReceivedAt >= query.StartDate.Value);
|
||||
}
|
||||
|
||||
if (query.EndDate.HasValue)
|
||||
{
|
||||
receivedMailsQuery = receivedMailsQuery.Where(r => r.ReceivedAt <= query.EndDate.Value);
|
||||
}
|
||||
|
||||
var receivedMails = await receivedMailsQuery
|
||||
.OrderBy(r => r.ReceivedAt)
|
||||
.ToListAsync();
|
||||
|
||||
foreach (var receivedMail in receivedMails)
|
||||
{
|
||||
var dateKey = receivedMail.ReceivedAt.ToString("yyyy-MM-dd");
|
||||
|
||||
if (!timelineDict.ContainsKey(dateKey))
|
||||
{
|
||||
timelineDict[dateKey] = new TimelineDateDto
|
||||
{
|
||||
Date = dateKey,
|
||||
Events = new List<TimelineEventDto>()
|
||||
};
|
||||
}
|
||||
|
||||
var senderName = receivedMail.SentMail.Sender.Username;
|
||||
var senderAvatar = receivedMail.SentMail.Sender.Avatar;
|
||||
|
||||
timelineDict[dateKey].Events.Add(new TimelineEventDto
|
||||
{
|
||||
Type = TimelineEventType.RECEIVED,
|
||||
MailId = receivedMail.SentMailId,
|
||||
Title = receivedMail.SentMail.Title,
|
||||
Time = receivedMail.ReceivedAt.ToString("HH:mm"),
|
||||
WithUser = new UserInfoDto
|
||||
{
|
||||
UserId = receivedMail.SentMail.SenderId,
|
||||
Username = senderName,
|
||||
Avatar = senderAvatar
|
||||
},
|
||||
Emotion = AnalyzeMailEmotion(receivedMail.SentMail.Content)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 对每个日期的事件按时间排序
|
||||
foreach (var dateEntry in timelineDict)
|
||||
{
|
||||
dateEntry.Value.Events = dateEntry.Value.Events
|
||||
.OrderBy(e => e.Time)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// 按日期排序
|
||||
response.Timeline = timelineDict
|
||||
.OrderBy(kvp => kvp.Key)
|
||||
.Select(kvp => kvp.Value)
|
||||
.ToList();
|
||||
|
||||
return ApiResponse<TimelineResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取时间线时发生错误");
|
||||
return ApiResponse<TimelineResponseDto>.ErrorResult("获取时间线失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<StatisticsResponseDto>> GetStatisticsAsync(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = new StatisticsResponseDto();
|
||||
|
||||
// 获取发送的邮件统计
|
||||
var sentMails = await _context.SentMails
|
||||
.Where(m => m.SenderId == userId)
|
||||
.ToListAsync();
|
||||
|
||||
response.TotalSent = sentMails.Count;
|
||||
|
||||
// 获取接收的邮件统计
|
||||
var receivedMails = await _context.ReceivedMails
|
||||
.Where(r => r.RecipientId == userId)
|
||||
.Include(r => r.SentMail)
|
||||
.ThenInclude(m => m.Sender)
|
||||
.ToListAsync();
|
||||
|
||||
response.TotalReceived = receivedMails.Count;
|
||||
|
||||
// 计算时间旅行时长(天)
|
||||
if (sentMails.Any())
|
||||
{
|
||||
var earliestDelivery = sentMails.Min(m => m.DeliveryTime);
|
||||
var latestDelivery = sentMails.Max(m => m.DeliveryTime);
|
||||
response.TimeTravelDuration = (latestDelivery - earliestDelivery).Days;
|
||||
}
|
||||
|
||||
// 找出最频繁的收件人
|
||||
var recipientCounts = sentMails
|
||||
.Where(m => m.RecipientId.HasValue)
|
||||
.GroupBy(m => m.RecipientId)
|
||||
.Select(g => new { RecipientId = g.Key, Count = g.Count() })
|
||||
.OrderByDescending(g => g.Count)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (recipientCounts != null)
|
||||
{
|
||||
var recipient = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == recipientCounts.RecipientId);
|
||||
|
||||
response.MostFrequentRecipient = recipient?.Username ?? "未知用户";
|
||||
}
|
||||
|
||||
// 找出最常见的年份(投递年份)
|
||||
if (sentMails.Any())
|
||||
{
|
||||
var yearCounts = sentMails
|
||||
.GroupBy(m => m.DeliveryTime.Year)
|
||||
.Select(g => new { Year = g.Key, Count = g.Count() })
|
||||
.OrderByDescending(g => g.Count)
|
||||
.FirstOrDefault();
|
||||
|
||||
response.MostCommonYear = yearCounts?.Year ?? DateTime.Now.Year;
|
||||
}
|
||||
|
||||
// 生成关键词云
|
||||
var allContents = sentMails.Select(m => m.Content).ToList();
|
||||
allContents.AddRange(receivedMails.Select(r => r.SentMail.Content));
|
||||
|
||||
response.KeywordCloud = GenerateKeywordCloud(allContents);
|
||||
|
||||
// 生成月度统计
|
||||
response.MonthlyStats = GenerateMonthlyStats(sentMails, receivedMails);
|
||||
|
||||
return ApiResponse<StatisticsResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取统计数据时发生错误");
|
||||
return ApiResponse<StatisticsResponseDto>.ErrorResult("获取统计数据失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<SubscriptionResponseDto>> GetSubscriptionAsync(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在实际应用中,这里会从数据库或订阅服务中获取用户的订阅信息
|
||||
// 目前我们使用模拟数据
|
||||
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<SubscriptionResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
// 模拟订阅数据
|
||||
var response = new SubscriptionResponseDto
|
||||
{
|
||||
Plan = SubscriptionPlan.FREE, // 默认为免费计划
|
||||
RemainingMails = 10 - await _context.SentMails.CountAsync(m => m.SenderId == userId && m.SentAt.Month == DateTime.Now.Month),
|
||||
MaxAttachmentSize = 5 * 1024 * 1024, // 5MB
|
||||
Features = new SubscriptionFeaturesDto
|
||||
{
|
||||
AdvancedTriggers = false,
|
||||
CustomCapsules = false,
|
||||
AIAssistant = true,
|
||||
UnlimitedStorage = false,
|
||||
PriorityDelivery = false
|
||||
},
|
||||
ExpireDate = null // 免费计划没有过期时间
|
||||
};
|
||||
|
||||
// 如果用户创建时间超过30天,可以升级为高级用户(模拟)
|
||||
if (user.CreatedAt.AddDays(30) < DateTime.UtcNow)
|
||||
{
|
||||
response.Plan = SubscriptionPlan.PREMIUM;
|
||||
response.RemainingMails = 100;
|
||||
response.MaxAttachmentSize = 50 * 1024 * 1024; // 50MB
|
||||
response.Features.AdvancedTriggers = true;
|
||||
response.Features.CustomCapsules = true;
|
||||
response.Features.UnlimitedStorage = true;
|
||||
response.ExpireDate = DateTime.UtcNow.AddMonths(1);
|
||||
}
|
||||
|
||||
return ApiResponse<SubscriptionResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取订阅信息时发生错误");
|
||||
return ApiResponse<SubscriptionResponseDto>.ErrorResult("获取订阅信息失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserProfileResponseDto>> GetUserProfileAsync(int userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<UserProfileResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
// 获取订阅信息
|
||||
var subscriptionResult = await GetSubscriptionAsync(userId);
|
||||
|
||||
var response = new UserProfileResponseDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
Email = user.Email,
|
||||
Nickname = user.Nickname,
|
||||
Avatar = user.Avatar,
|
||||
CreatedAt = user.CreatedAt,
|
||||
LastLoginAt = user.LastLoginAt,
|
||||
Subscription = subscriptionResult.Data ?? new SubscriptionResponseDto()
|
||||
};
|
||||
|
||||
return ApiResponse<UserProfileResponseDto>.SuccessResult(response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "获取用户资料时发生错误");
|
||||
return ApiResponse<UserProfileResponseDto>.ErrorResult("获取用户资料失败");
|
||||
}
|
||||
}
|
||||
|
||||
private string AnalyzeMailEmotion(string content)
|
||||
{
|
||||
// 简单的情感分析
|
||||
var positiveKeywords = new[] { "开心", "快乐", "爱", "美好", "成功", "希望", "感谢", "幸福" };
|
||||
var negativeKeywords = new[] { "悲伤", "难过", "失败", "痛苦", "失望", "愤怒", "焦虑", "恐惧" };
|
||||
|
||||
var positiveCount = positiveKeywords.Count(keyword => content.Contains(keyword));
|
||||
var negativeCount = negativeKeywords.Count(keyword => content.Contains(keyword));
|
||||
|
||||
if (positiveCount > negativeCount)
|
||||
return "积极";
|
||||
else if (negativeCount > positiveCount)
|
||||
return "消极";
|
||||
else
|
||||
return "中性";
|
||||
}
|
||||
|
||||
private List<KeywordCloudDto> GenerateKeywordCloud(List<string> contents)
|
||||
{
|
||||
// 简单的关键词提取
|
||||
var allWords = new List<string>();
|
||||
|
||||
foreach (var content in contents)
|
||||
{
|
||||
var words = content.Split(new[] { ' ', ',', '.', '!', '?', ';', ':', ',', '。', '!', '?', ';', ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
allWords.AddRange(words.Where(word => word.Length > 3));
|
||||
}
|
||||
|
||||
var wordCounts = allWords
|
||||
.GroupBy(word => word)
|
||||
.Select(g => new { Word = g.Key, Count = g.Count() })
|
||||
.OrderByDescending(g => g.Count)
|
||||
.Take(20)
|
||||
.ToList();
|
||||
|
||||
var maxCount = wordCounts.FirstOrDefault()?.Count ?? 1;
|
||||
|
||||
return wordCounts.Select(w => new KeywordCloudDto
|
||||
{
|
||||
Word = w.Word,
|
||||
Count = w.Count,
|
||||
Size = (int)((double)w.Count / maxCount * 10) + 1 // 1-10的大小
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private List<MonthlyStatsDto> GenerateMonthlyStats(List<SentMail> sentMails, List<ReceivedMail> receivedMails)
|
||||
{
|
||||
var monthlyStats = new Dictionary<string, MonthlyStatsDto>();
|
||||
|
||||
// 处理发送的邮件
|
||||
foreach (var mail in sentMails)
|
||||
{
|
||||
var monthKey = mail.SentAt.ToString("yyyy-MM");
|
||||
|
||||
if (!monthlyStats.ContainsKey(monthKey))
|
||||
{
|
||||
monthlyStats[monthKey] = new MonthlyStatsDto
|
||||
{
|
||||
Month = monthKey,
|
||||
Sent = 0,
|
||||
Received = 0
|
||||
};
|
||||
}
|
||||
|
||||
monthlyStats[monthKey].Sent++;
|
||||
}
|
||||
|
||||
// 处理接收的邮件
|
||||
foreach (var receivedMail in receivedMails)
|
||||
{
|
||||
var monthKey = receivedMail.ReceivedAt.ToString("yyyy-MM");
|
||||
|
||||
if (!monthlyStats.ContainsKey(monthKey))
|
||||
{
|
||||
monthlyStats[monthKey] = new MonthlyStatsDto
|
||||
{
|
||||
Month = monthKey,
|
||||
Sent = 0,
|
||||
Received = 0
|
||||
};
|
||||
}
|
||||
|
||||
monthlyStats[monthKey].Received++;
|
||||
}
|
||||
|
||||
// 按月份排序,只返回最近12个月的数据
|
||||
return monthlyStats
|
||||
.OrderBy(kvp => kvp.Key)
|
||||
.TakeLast(12)
|
||||
.Select(kvp => kvp.Value)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
436
FutureMailAPI/Services/TimeCapsuleService.cs
Normal file
436
FutureMailAPI/Services/TimeCapsuleService.cs
Normal file
@@ -0,0 +1,436 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.DTOs;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public class TimeCapsuleService : ITimeCapsuleService
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
|
||||
public TimeCapsuleService(FutureMailDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimeCapsuleResponseDto>> CreateTimeCapsuleAsync(int userId, TimeCapsuleCreateDto createDto)
|
||||
{
|
||||
// 检查邮件是否存在且属于当前用户
|
||||
var mail = await _context.SentMails
|
||||
.FirstOrDefaultAsync(m => m.Id == createDto.SentMailId && m.SenderId == userId);
|
||||
|
||||
if (mail == null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("邮件不存在或无权限");
|
||||
}
|
||||
|
||||
// 检查是否已存在时间胶囊
|
||||
var existingCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.SentMailId == createDto.SentMailId);
|
||||
|
||||
if (existingCapsule != null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("该邮件已创建时间胶囊");
|
||||
}
|
||||
|
||||
// 创建时间胶囊
|
||||
var timeCapsule = new TimeCapsule
|
||||
{
|
||||
UserId = userId,
|
||||
SentMailId = createDto.SentMailId,
|
||||
PositionX = createDto.PositionX,
|
||||
PositionY = createDto.PositionY,
|
||||
PositionZ = createDto.PositionZ,
|
||||
Size = createDto.Size,
|
||||
Color = createDto.Color,
|
||||
Opacity = createDto.Opacity,
|
||||
Rotation = createDto.Rotation,
|
||||
Status = 1, // 漂浮中
|
||||
Type = createDto.Type
|
||||
};
|
||||
|
||||
_context.TimeCapsules.Add(timeCapsule);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
|
||||
|
||||
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊创建成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetTimeCapsulesAsync(int userId, TimeCapsuleListQueryDto queryDto)
|
||||
{
|
||||
var query = _context.TimeCapsules
|
||||
.Where(tc => tc.UserId == userId)
|
||||
.Include(tc => tc.User)
|
||||
.Include(tc => tc.SentMail)
|
||||
.AsQueryable();
|
||||
|
||||
// 应用筛选条件
|
||||
if (queryDto.Status.HasValue)
|
||||
{
|
||||
query = query.Where(tc => tc.Status == queryDto.Status.Value);
|
||||
}
|
||||
|
||||
if (queryDto.Type.HasValue)
|
||||
{
|
||||
query = query.Where(tc => tc.Type == queryDto.Type.Value);
|
||||
}
|
||||
|
||||
if (queryDto.IncludeExpired.HasValue && !queryDto.IncludeExpired.Value)
|
||||
{
|
||||
query = query.Where(tc => tc.SentMail.DeliveryTime > DateTime.UtcNow);
|
||||
}
|
||||
|
||||
// 排序
|
||||
query = query.OrderByDescending(tc => tc.CreatedAt);
|
||||
|
||||
// 分页
|
||||
var totalCount = await query.CountAsync();
|
||||
var capsules = await query
|
||||
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
|
||||
.Take(queryDto.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var capsuleDtos = capsules.Select(MapToTimeCapsuleResponseDto).ToList();
|
||||
|
||||
var pagedResponse = new PagedResponse<TimeCapsuleResponseDto>(
|
||||
capsuleDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
|
||||
|
||||
return ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.SuccessResult(pagedResponse);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimeCapsuleResponseDto>> GetTimeCapsuleByIdAsync(int userId, int capsuleId)
|
||||
{
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.Include(tc => tc.User)
|
||||
.Include(tc => tc.SentMail)
|
||||
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
|
||||
|
||||
if (timeCapsule == null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在");
|
||||
}
|
||||
|
||||
var capsuleDto = MapToTimeCapsuleResponseDto(timeCapsule);
|
||||
|
||||
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleAsync(int userId, int capsuleId, TimeCapsuleUpdateDto updateDto)
|
||||
{
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
|
||||
|
||||
if (timeCapsule == null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在");
|
||||
}
|
||||
|
||||
// 检查胶囊是否已开启,已开启的胶囊不能修改
|
||||
if (timeCapsule.Status >= 3)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("已开启的时间胶囊不能修改");
|
||||
}
|
||||
|
||||
// 更新胶囊信息
|
||||
if (updateDto.PositionX.HasValue)
|
||||
{
|
||||
timeCapsule.PositionX = updateDto.PositionX.Value;
|
||||
}
|
||||
|
||||
if (updateDto.PositionY.HasValue)
|
||||
{
|
||||
timeCapsule.PositionY = updateDto.PositionY.Value;
|
||||
}
|
||||
|
||||
if (updateDto.PositionZ.HasValue)
|
||||
{
|
||||
timeCapsule.PositionZ = updateDto.PositionZ.Value;
|
||||
}
|
||||
|
||||
if (updateDto.Size.HasValue)
|
||||
{
|
||||
timeCapsule.Size = updateDto.Size.Value;
|
||||
}
|
||||
|
||||
if (updateDto.Color != null)
|
||||
{
|
||||
timeCapsule.Color = updateDto.Color;
|
||||
}
|
||||
|
||||
if (updateDto.Opacity.HasValue)
|
||||
{
|
||||
timeCapsule.Opacity = updateDto.Opacity.Value;
|
||||
}
|
||||
|
||||
if (updateDto.Rotation.HasValue)
|
||||
{
|
||||
timeCapsule.Rotation = updateDto.Rotation.Value;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
|
||||
|
||||
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊更新成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> DeleteTimeCapsuleAsync(int userId, int capsuleId)
|
||||
{
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
|
||||
|
||||
if (timeCapsule == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("时间胶囊不存在");
|
||||
}
|
||||
|
||||
// 检查胶囊是否已开启,已开启的胶囊不能删除
|
||||
if (timeCapsule.Status >= 3)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("已开启的时间胶囊不能删除");
|
||||
}
|
||||
|
||||
// 删除时间胶囊
|
||||
_context.TimeCapsules.Remove(timeCapsule);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true, "时间胶囊删除成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetPublicTimeCapsulesAsync(TimeCapsuleListQueryDto queryDto)
|
||||
{
|
||||
var query = _context.TimeCapsules
|
||||
.Where(tc => tc.SentMail.RecipientType == 2) // 公开时间胶囊
|
||||
.Include(tc => tc.User)
|
||||
.Include(tc => tc.SentMail)
|
||||
.AsQueryable();
|
||||
|
||||
// 应用筛选条件
|
||||
if (queryDto.Status.HasValue)
|
||||
{
|
||||
query = query.Where(tc => tc.Status == queryDto.Status.Value);
|
||||
}
|
||||
|
||||
if (queryDto.Type.HasValue)
|
||||
{
|
||||
query = query.Where(tc => tc.Type == queryDto.Type.Value);
|
||||
}
|
||||
|
||||
if (queryDto.IncludeExpired.HasValue && !queryDto.IncludeExpired.Value)
|
||||
{
|
||||
query = query.Where(tc => tc.SentMail.DeliveryTime > DateTime.UtcNow);
|
||||
}
|
||||
|
||||
// 排序
|
||||
query = query.OrderByDescending(tc => tc.CreatedAt);
|
||||
|
||||
// 分页
|
||||
var totalCount = await query.CountAsync();
|
||||
var capsules = await query
|
||||
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
|
||||
.Take(queryDto.PageSize)
|
||||
.ToListAsync();
|
||||
|
||||
var capsuleDtos = capsules.Select(MapToTimeCapsuleResponseDto).ToList();
|
||||
|
||||
var pagedResponse = new PagedResponse<TimeCapsuleResponseDto>(
|
||||
capsuleDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
|
||||
|
||||
return ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.SuccessResult(pagedResponse);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimeCapsuleResponseDto>> ClaimPublicCapsuleAsync(int userId, int capsuleId)
|
||||
{
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.Include(tc => tc.SentMail)
|
||||
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.SentMail.RecipientType == 2);
|
||||
|
||||
if (timeCapsule == null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在或不是公开胶囊");
|
||||
}
|
||||
|
||||
// 检查胶囊是否已开启
|
||||
if (timeCapsule.Status >= 3)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊已开启");
|
||||
}
|
||||
|
||||
// 检查是否已认领
|
||||
var existingReceivedMail = await _context.ReceivedMails
|
||||
.FirstOrDefaultAsync(rm => rm.SentMailId == timeCapsule.SentMailId && rm.RecipientId == userId);
|
||||
|
||||
if (existingReceivedMail != null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("您已认领此时间胶囊");
|
||||
}
|
||||
|
||||
// 创建接收邮件记录
|
||||
var receivedMail = new ReceivedMail
|
||||
{
|
||||
SentMailId = timeCapsule.SentMailId,
|
||||
RecipientId = userId,
|
||||
ReceivedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.ReceivedMails.Add(receivedMail);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
|
||||
|
||||
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊认领成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimeCapsuleViewResponseDto>> GetTimeCapsuleViewAsync(int userId)
|
||||
{
|
||||
// 获取用户的所有时间胶囊
|
||||
var capsules = await _context.TimeCapsules
|
||||
.Where(tc => tc.UserId == userId)
|
||||
.Include(tc => tc.SentMail)
|
||||
.ToListAsync();
|
||||
|
||||
// 转换为视图DTO
|
||||
var capsuleViews = capsules.Select(capsule =>
|
||||
{
|
||||
var totalDays = (capsule.SentMail.DeliveryTime - capsule.SentMail.SentAt).TotalDays;
|
||||
var elapsedDays = (DateTime.UtcNow - capsule.SentMail.SentAt).TotalDays;
|
||||
var progress = totalDays > 0 ? Math.Min(1, elapsedDays / totalDays) : 0;
|
||||
|
||||
return new TimeCapsuleViewDto
|
||||
{
|
||||
CapsuleId = capsule.Id,
|
||||
MailId = capsule.SentMailId,
|
||||
Title = capsule.SentMail.Title,
|
||||
SendTime = capsule.SentMail.SentAt,
|
||||
DeliveryTime = capsule.SentMail.DeliveryTime,
|
||||
Progress = progress,
|
||||
Position = new TimeCapsulePosition
|
||||
{
|
||||
X = capsule.PositionX,
|
||||
Y = capsule.PositionY,
|
||||
Z = capsule.PositionZ
|
||||
},
|
||||
Style = capsule.Color ?? "#FFFFFF",
|
||||
GlowIntensity = capsule.Status == 1 ? 0.8 : 0.3 // 漂浮中的胶囊发光更亮
|
||||
};
|
||||
}).ToList();
|
||||
|
||||
// 获取用户偏好设置(如果有)
|
||||
var user = await _context.Users.FindAsync(userId);
|
||||
var scene = user?.PreferredScene ?? "SPACE";
|
||||
var background = user?.PreferredBackground ?? "default";
|
||||
|
||||
var viewResponse = new TimeCapsuleViewResponseDto
|
||||
{
|
||||
Capsules = capsuleViews,
|
||||
Scene = scene,
|
||||
Background = background
|
||||
};
|
||||
|
||||
return ApiResponse<TimeCapsuleViewResponseDto>.SuccessResult(viewResponse);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleStyleAsync(int userId, int capsuleId, TimeCapsuleStyleUpdateDto updateDto)
|
||||
{
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
|
||||
|
||||
if (timeCapsule == null)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在");
|
||||
}
|
||||
|
||||
// 检查胶囊是否已开启,已开启的胶囊不能修改
|
||||
if (timeCapsule.Status >= 3)
|
||||
{
|
||||
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("已开启的时间胶囊不能修改");
|
||||
}
|
||||
|
||||
// 更新胶囊样式
|
||||
timeCapsule.Color = updateDto.Style;
|
||||
|
||||
if (updateDto.GlowIntensity.HasValue)
|
||||
{
|
||||
// 将发光强度转换为透明度(反向关系)
|
||||
timeCapsule.Opacity = 1.0 - (updateDto.GlowIntensity.Value * 0.5);
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
|
||||
|
||||
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊样式更新成功");
|
||||
}
|
||||
|
||||
private async Task<TimeCapsuleResponseDto> GetTimeCapsuleWithDetailsAsync(int capsuleId)
|
||||
{
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.Include(tc => tc.User)
|
||||
.Include(tc => tc.SentMail)
|
||||
.FirstOrDefaultAsync(tc => tc.Id == capsuleId);
|
||||
|
||||
return MapToTimeCapsuleResponseDto(timeCapsule!);
|
||||
}
|
||||
|
||||
private static TimeCapsuleResponseDto MapToTimeCapsuleResponseDto(TimeCapsule timeCapsule)
|
||||
{
|
||||
var daysUntilDelivery = (int)(timeCapsule.SentMail.DeliveryTime - DateTime.UtcNow).TotalDays;
|
||||
var distanceFromNow = CalculateDistanceFromNow(daysUntilDelivery);
|
||||
|
||||
return new TimeCapsuleResponseDto
|
||||
{
|
||||
Id = timeCapsule.Id,
|
||||
UserId = timeCapsule.UserId,
|
||||
SentMailId = timeCapsule.SentMailId,
|
||||
MailTitle = timeCapsule.SentMail.Title,
|
||||
PositionX = timeCapsule.PositionX,
|
||||
PositionY = timeCapsule.PositionY,
|
||||
PositionZ = timeCapsule.PositionZ,
|
||||
Size = timeCapsule.Size,
|
||||
Color = timeCapsule.Color,
|
||||
Opacity = timeCapsule.Opacity,
|
||||
Rotation = timeCapsule.Rotation,
|
||||
Status = timeCapsule.Status,
|
||||
StatusText = GetStatusText(timeCapsule.Status),
|
||||
Type = timeCapsule.Type,
|
||||
TypeText = GetTypeText(timeCapsule.Type),
|
||||
CreatedAt = timeCapsule.CreatedAt,
|
||||
DeliveryTime = timeCapsule.SentMail.DeliveryTime,
|
||||
DaysUntilDelivery = daysUntilDelivery,
|
||||
DistanceFromNow = distanceFromNow
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetStatusText(int status)
|
||||
{
|
||||
return status switch
|
||||
{
|
||||
0 => "未激活",
|
||||
1 => "漂浮中",
|
||||
2 => "即将到达",
|
||||
3 => "已开启",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetTypeText(int type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
0 => "普通",
|
||||
1 => "特殊",
|
||||
2 => "限时",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculateDistanceFromNow(int daysUntilDelivery)
|
||||
{
|
||||
// 简单的距离计算,可以根据需要调整
|
||||
// 距离"现在"越远,距离值越大
|
||||
return Math.Max(1, daysUntilDelivery / 30.0); // 以月为单位
|
||||
}
|
||||
}
|
||||
}
|
||||
298
FutureMailAPI/Services/UserService.cs
Normal file
298
FutureMailAPI/Services/UserService.cs
Normal file
@@ -0,0 +1,298 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
using FutureMailAPI.DTOs;
|
||||
using FutureMailAPI.Helpers;
|
||||
|
||||
namespace FutureMailAPI.Services
|
||||
{
|
||||
public interface IUserService
|
||||
{
|
||||
Task<ApiResponse<UserResponseDto>> RegisterAsync(UserRegisterDto registerDto);
|
||||
Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
|
||||
Task<ApiResponse<UserResponseDto>> GetUserByIdAsync(int userId);
|
||||
Task<ApiResponse<UserResponseDto>> GetUserByUsernameAsync(string username);
|
||||
Task<ApiResponse<UserResponseDto>> GetUserByEmailAsync(string email);
|
||||
Task<ApiResponse<UserResponseDto>> GetUserByUsernameOrEmailAsync(string usernameOrEmail);
|
||||
Task<ApiResponse<UserResponseDto>> UpdateUserAsync(int userId, UserUpdateDto updateDto);
|
||||
Task<ApiResponse<bool>> ChangePasswordAsync(int userId, ChangePasswordDto changePasswordDto);
|
||||
Task<ApiResponse<UserResponseDto>> CreateUserAsync(UserRegisterDto registerDto);
|
||||
}
|
||||
|
||||
public class UserService : IUserService
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
private readonly IPasswordHelper _passwordHelper;
|
||||
|
||||
public UserService(FutureMailDbContext context, IPasswordHelper passwordHelper)
|
||||
{
|
||||
_context = context;
|
||||
_passwordHelper = passwordHelper;
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> RegisterAsync(UserRegisterDto registerDto)
|
||||
{
|
||||
// 检查用户名是否已存在
|
||||
var existingUserByUsername = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == registerDto.Username);
|
||||
|
||||
if (existingUserByUsername != null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
var existingUserByEmail = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == registerDto.Email);
|
||||
|
||||
if (existingUserByEmail != null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
|
||||
}
|
||||
|
||||
// 生成随机盐值
|
||||
var salt = _passwordHelper.GenerateSalt();
|
||||
|
||||
// 创建新用户
|
||||
var user = new User
|
||||
{
|
||||
Username = registerDto.Username,
|
||||
Email = registerDto.Email,
|
||||
PasswordHash = _passwordHelper.HashPassword(registerDto.Password, salt),
|
||||
Salt = salt,
|
||||
Nickname = registerDto.Nickname ?? registerDto.Username,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.Users.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto, "注册成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
|
||||
{
|
||||
// 查找用户(通过用户名或邮箱)
|
||||
User? user;
|
||||
|
||||
if (loginDto.UsernameOrEmail.Contains("@"))
|
||||
{
|
||||
user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == loginDto.UsernameOrEmail);
|
||||
}
|
||||
else
|
||||
{
|
||||
user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == loginDto.UsernameOrEmail);
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
|
||||
{
|
||||
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
|
||||
}
|
||||
|
||||
// 更新最后登录时间
|
||||
user.LastLoginAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 注意:这里不再生成JWT令牌,因为我们将使用OAuth 2.0
|
||||
// 在OAuth 2.0流程中,令牌是通过OAuth端点生成的
|
||||
|
||||
var authResponse = new AuthResponseDto
|
||||
{
|
||||
Token = "", // 临时空字符串,实际使用OAuth 2.0令牌
|
||||
Expires = DateTime.UtcNow.AddDays(7),
|
||||
User = MapToUserResponseDto(user)
|
||||
};
|
||||
|
||||
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> GetUserByIdAsync(int userId)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> GetUserByUsernameAsync(string username)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> GetUserByEmailAsync(string email)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == email);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> GetUserByUsernameOrEmailAsync(string usernameOrEmail)
|
||||
{
|
||||
User? user;
|
||||
|
||||
if (usernameOrEmail.Contains("@"))
|
||||
{
|
||||
user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == usernameOrEmail);
|
||||
}
|
||||
else
|
||||
{
|
||||
user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == usernameOrEmail);
|
||||
}
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> UpdateUserAsync(int userId, UserUpdateDto updateDto)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
if (updateDto.Nickname != null)
|
||||
{
|
||||
user.Nickname = updateDto.Nickname;
|
||||
}
|
||||
|
||||
if (updateDto.Avatar != null)
|
||||
{
|
||||
user.Avatar = updateDto.Avatar;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto, "更新成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<bool>> ChangePasswordAsync(int userId, ChangePasswordDto changePasswordDto)
|
||||
{
|
||||
var user = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("用户不存在");
|
||||
}
|
||||
|
||||
// 验证当前密码
|
||||
if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash))
|
||||
{
|
||||
return ApiResponse<bool>.ErrorResult("当前密码错误");
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
var salt = _passwordHelper.GenerateSalt();
|
||||
user.PasswordHash = _passwordHelper.HashPassword(changePasswordDto.NewPassword, salt);
|
||||
user.Salt = salt;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return ApiResponse<bool>.SuccessResult(true, "密码修改成功");
|
||||
}
|
||||
|
||||
public async Task<ApiResponse<UserResponseDto>> CreateUserAsync(UserRegisterDto registerDto)
|
||||
{
|
||||
// 检查用户名是否已存在
|
||||
var existingUserByUsername = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Username == registerDto.Username);
|
||||
|
||||
if (existingUserByUsername != null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
var existingUserByEmail = await _context.Users
|
||||
.FirstOrDefaultAsync(u => u.Email == registerDto.Email);
|
||||
|
||||
if (existingUserByEmail != null)
|
||||
{
|
||||
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
|
||||
}
|
||||
|
||||
// 生成随机盐值
|
||||
var salt = _passwordHelper.GenerateSalt();
|
||||
|
||||
// 创建新用户
|
||||
var user = new User
|
||||
{
|
||||
Username = registerDto.Username,
|
||||
Email = registerDto.Email,
|
||||
PasswordHash = _passwordHelper.HashPassword(registerDto.Password, salt),
|
||||
Salt = salt,
|
||||
Nickname = registerDto.Nickname ?? registerDto.Username,
|
||||
CreatedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.Users.Add(user);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var userDto = MapToUserResponseDto(user);
|
||||
|
||||
return ApiResponse<UserResponseDto>.SuccessResult(userDto, "用户创建成功");
|
||||
}
|
||||
|
||||
private static UserResponseDto MapToUserResponseDto(User user)
|
||||
{
|
||||
return new UserResponseDto
|
||||
{
|
||||
Id = user.Id,
|
||||
Username = user.Username,
|
||||
Email = user.Email,
|
||||
Nickname = user.Nickname,
|
||||
Avatar = user.Avatar,
|
||||
CreatedAt = user.CreatedAt,
|
||||
LastLoginAt = user.LastLoginAt
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
53
FutureMailAPI/TestRegister.cs
Normal file
53
FutureMailAPI/TestRegister.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
class TestRegisterProgram
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
|
||||
// 生成随机用户名
|
||||
var random = new Random();
|
||||
var username = $"testuser{random.Next(1000, 9999)}";
|
||||
var email = $"{username}@example.com";
|
||||
|
||||
Console.WriteLine($"尝试注册用户: {username}, 邮箱: {email}");
|
||||
|
||||
// 创建注册请求
|
||||
var registerData = new
|
||||
{
|
||||
Username = username,
|
||||
Email = email,
|
||||
Password = "password123"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(registerData);
|
||||
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
// 发送注册请求
|
||||
var response = await client.PostAsync("http://localhost:5001/api/v1/auth/register", content);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
var responseContent = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"注册成功! 响应内容: {responseContent}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var errorContent = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"注册失败! 状态码: {response.StatusCode}");
|
||||
Console.WriteLine($"错误内容: {errorContent}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"发生异常: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
FutureMailAPI/appsettings.Development.json
Normal file
8
FutureMailAPI/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
28
FutureMailAPI/appsettings.json
Normal file
28
FutureMailAPI/appsettings.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Data Source=FutureMail.db"
|
||||
},
|
||||
"JwtSettings": {
|
||||
"SecretKey": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
|
||||
"Issuer": "FutureMailAPI",
|
||||
"Audience": "FutureMailClient",
|
||||
"ExpirationInMinutes": 1440
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
|
||||
"Issuer": "FutureMailAPI",
|
||||
"Audience": "FutureMailClient"
|
||||
},
|
||||
"FileUpload": {
|
||||
"UploadPath": "uploads",
|
||||
"BaseUrl": "http://localhost:5054/uploads",
|
||||
"MaxFileSize": 104857600
|
||||
}
|
||||
}
|
||||
1343
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.deps.json
Normal file
1343
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.deps.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.dll
Normal file
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.exe
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.exe
Normal file
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.pdb
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.pdb
Normal file
Binary file not shown.
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"runtimeOptions": {
|
||||
"tfm": "net9.0",
|
||||
"frameworks": [
|
||||
{
|
||||
"name": "Microsoft.NETCore.App",
|
||||
"version": "9.0.0"
|
||||
},
|
||||
{
|
||||
"name": "Microsoft.AspNetCore.App",
|
||||
"version": "9.0.0"
|
||||
}
|
||||
],
|
||||
"configProperties": {
|
||||
"System.GC.Server": true,
|
||||
"System.Reflection.NullabilityInfoContext.IsSupported": true,
|
||||
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
{"Version":1,"ManifestType":"Build","Endpoints":[]}
|
||||
@@ -0,0 +1 @@
|
||||
{"ContentRoots":["C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\wwwroot\\"],"Root":{"Children":null,"Asset":null,"Patterns":[{"ContentRootIndex":0,"Pattern":"**","Depth":0}]}}
|
||||
289
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.xml
Normal file
289
FutureMailAPI/bin/Debug/net9.0/FutureMailAPI.xml
Normal file
@@ -0,0 +1,289 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>FutureMailAPI</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="M:FutureMailAPI.Controllers.AIController.WritingAssistant(FutureMailAPI.DTOs.WritingAssistantRequestDto)">
|
||||
<summary>
|
||||
AI写作辅助
|
||||
</summary>
|
||||
<param name="request">写作辅助请求</param>
|
||||
<returns>AI生成的内容和建议</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.AIController.SentimentAnalysis(FutureMailAPI.DTOs.SentimentAnalysisRequestDto)">
|
||||
<summary>
|
||||
情感分析
|
||||
</summary>
|
||||
<param name="request">情感分析请求</param>
|
||||
<returns>情感分析结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.AIController.FuturePrediction(FutureMailAPI.DTOs.FuturePredictionRequestDto)">
|
||||
<summary>
|
||||
未来预测
|
||||
</summary>
|
||||
<param name="request">未来预测请求</param>
|
||||
<returns>未来预测结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.AIController.GetCurrentUserId">
|
||||
<summary>
|
||||
从JWT令牌中获取当前用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
|
||||
<summary>
|
||||
上传附件
|
||||
</summary>
|
||||
<param name="request">文件上传请求</param>
|
||||
<returns>上传结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAvatar(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
|
||||
<summary>
|
||||
上传头像
|
||||
</summary>
|
||||
<param name="request">文件上传请求</param>
|
||||
<returns>上传结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.FileUploadController.DeleteFile(System.String)">
|
||||
<summary>
|
||||
删除文件
|
||||
</summary>
|
||||
<param name="fileId">文件ID</param>
|
||||
<returns>删除结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.FileUploadController.GetFile(System.String)">
|
||||
<summary>
|
||||
获取文件信息
|
||||
</summary>
|
||||
<param name="fileId">文件ID</param>
|
||||
<returns>文件信息</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.FileUploadController.GetCurrentUserId">
|
||||
<summary>
|
||||
从当前请求中获取用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
|
||||
<summary>
|
||||
注册设备
|
||||
</summary>
|
||||
<param name="request">设备注册请求</param>
|
||||
<returns>注册结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.NotificationController.GetNotificationSettings">
|
||||
<summary>
|
||||
获取通知设置
|
||||
</summary>
|
||||
<returns>通知设置</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.NotificationController.GetCurrentUserId">
|
||||
<summary>
|
||||
从JWT令牌中获取当前用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.Login(FutureMailAPI.DTOs.OAuthLoginDto)">
|
||||
<summary>
|
||||
OAuth登录端点
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.CreateClient(FutureMailAPI.DTOs.OAuthClientCreateDto)">
|
||||
<summary>
|
||||
创建OAuth客户端
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.GetClient(System.String)">
|
||||
<summary>
|
||||
获取OAuth客户端信息
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.Authorize(FutureMailAPI.DTOs.OAuthAuthorizationRequestDto)">
|
||||
<summary>
|
||||
OAuth授权端点
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.ExchangeToken(FutureMailAPI.DTOs.OAuthTokenRequestDto)">
|
||||
<summary>
|
||||
OAuth令牌端点
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.RevokeToken(System.String,System.String)">
|
||||
<summary>
|
||||
撤销令牌
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.OAuthController.IntrospectToken(System.String)">
|
||||
<summary>
|
||||
验证令牌
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
|
||||
<summary>
|
||||
获取用户时间线
|
||||
</summary>
|
||||
<param name="type">时间线类型</param>
|
||||
<param name="startDate">开始日期</param>
|
||||
<param name="endDate">结束日期</param>
|
||||
<returns>用户时间线</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetStatistics">
|
||||
<summary>
|
||||
获取用户统计数据
|
||||
</summary>
|
||||
<returns>用户统计数据</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetSubscription">
|
||||
<summary>
|
||||
获取用户订阅信息
|
||||
</summary>
|
||||
<returns>用户订阅信息</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetUserProfile">
|
||||
<summary>
|
||||
获取用户资料
|
||||
</summary>
|
||||
<returns>用户资料</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetCurrentUserId">
|
||||
<summary>
|
||||
从当前请求中获取用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
|
||||
<summary>
|
||||
获取用户统计数据
|
||||
</summary>
|
||||
<returns>用户统计数据</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetCurrentUserId">
|
||||
<summary>
|
||||
从JWT令牌中获取当前用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
|
||||
<summary>
|
||||
获取用户时间线
|
||||
</summary>
|
||||
<param name="type">时间线类型</param>
|
||||
<param name="startDate">开始日期</param>
|
||||
<param name="endDate">结束日期</param>
|
||||
<returns>用户时间线</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.TimelineController.GetCurrentUserId">
|
||||
<summary>
|
||||
从JWT令牌中获取当前用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
|
||||
<summary>
|
||||
上传附件
|
||||
</summary>
|
||||
<param name="request">文件上传请求</param>
|
||||
<returns>上传结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.UploadController.UploadAvatar(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
|
||||
<summary>
|
||||
上传头像
|
||||
</summary>
|
||||
<param name="request">文件上传请求</param>
|
||||
<returns>上传结果</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.UploadController.GetCurrentUserId">
|
||||
<summary>
|
||||
从JWT令牌中获取当前用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
|
||||
<summary>
|
||||
获取用户订阅信息
|
||||
</summary>
|
||||
<returns>用户订阅信息</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.UserController.GetUserProfile">
|
||||
<summary>
|
||||
获取用户资料
|
||||
</summary>
|
||||
<returns>用户资料</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Controllers.UserController.GetCurrentUserId">
|
||||
<summary>
|
||||
从JWT令牌中获取当前用户ID
|
||||
</summary>
|
||||
<returns>用户ID</returns>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
|
||||
<summary>
|
||||
获取当前用户ID
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserEmail(Microsoft.AspNetCore.Http.HttpContext)">
|
||||
<summary>
|
||||
获取当前用户邮箱
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentAccessToken(Microsoft.AspNetCore.Http.HttpContext)">
|
||||
<summary>
|
||||
获取当前访问令牌
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
|
||||
<summary>
|
||||
Swagger文件上传操作过滤器
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:FutureMailAPI.Migrations.InitialCreate">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.InitialCreate.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.InitialCreate.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.InitialCreate.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:FutureMailAPI.Migrations.AddUserPreferences">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:FutureMailAPI.Migrations.AddOAuthEntities">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="T:FutureMailAPI.Migrations.AddSaltToUser">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
BIN
FutureMailAPI/bin/Debug/net9.0/Humanizer.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Humanizer.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.AspNetCore.OpenApi.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.AspNetCore.OpenApi.dll
Normal file
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.Bcl.AsyncInterfaces.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.Bcl.AsyncInterfaces.dll
Normal file
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.Build.Locator.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.Build.Locator.dll
Normal file
Binary file not shown.
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.CodeAnalysis.CSharp.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.CodeAnalysis.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.CodeAnalysis.dll
Normal file
Binary file not shown.
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.Data.Sqlite.dll
Normal file
BIN
FutureMailAPI/bin/Debug/net9.0/Microsoft.Data.Sqlite.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user