Initial project import
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
using MiaoJiZhang.Api.Contracts;
|
||||
using MiaoJiZhang.Api.Services;
|
||||
using MiaoJiZhang.Domain.Entities;
|
||||
using MiaoJiZhang.Domain.Enums;
|
||||
using MiaoJiZhang.Infrastructure.Persistence;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace MiaoJiZhang.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[EnableRateLimiting("auth")]
|
||||
[Route("api/auth")]
|
||||
public class AuthController(AppDbContext db, JwtService jwt) : ControllerBase
|
||||
{
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<AuthResponse>> Register(RegisterRequest req)
|
||||
{
|
||||
if (!req.AgreedToTerms)
|
||||
return BadRequest(new ApiError("TERMS_NOT_ACCEPTED", "请先阅读并同意用户协议与隐私政策"));
|
||||
if (req.Username.Length is < 3 or > 32) return BadRequest(new ApiError("USERNAME_INVALID", "用户名长度需在 3-32 之间"));
|
||||
if (req.Password.Length < 6) return BadRequest(new ApiError("PASSWORD_TOO_SHORT", "密码至少 6 位"));
|
||||
if (await db.Users.AnyAsync(u => u.Username == req.Username)) return Conflict(new ApiError("USERNAME_TAKEN", "用户名已被占用"));
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var defaultAiEnabled = !string.Equals(
|
||||
(await db.AppConfigs.FirstOrDefaultAsync(c => c.Key == "permission.default.ai_enabled"))?.Value,
|
||||
"false",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
var quotaConfig = await db.AppConfigs
|
||||
.Where(c => c.Key == "quota.default_ai_chat_limit" ||
|
||||
c.Key == "quota.default_ai_chat_period")
|
||||
.ToDictionaryAsync(c => c.Key, c => c.Value);
|
||||
var defaultAiChatLimit =
|
||||
quotaConfig.TryGetValue("quota.default_ai_chat_limit", out var limitValue) &&
|
||||
int.TryParse(limitValue, out var parsedLimit)
|
||||
? Math.Clamp(parsedLimit, 0, 1_000_000)
|
||||
: 50;
|
||||
var periodValue = quotaConfig.GetValueOrDefault(
|
||||
"quota.default_ai_chat_period",
|
||||
"day");
|
||||
if (!AiChatQuotaService.TryParsePeriod(periodValue, out var defaultAiChatPeriod))
|
||||
defaultAiChatPeriod = AiQuotaPeriod.Day;
|
||||
var (quotaWindowStart, _) =
|
||||
AiChatQuotaService.CurrentWindow(defaultAiChatPeriod);
|
||||
var user = new User
|
||||
{
|
||||
Username = req.Username,
|
||||
PasswordHash = BCrypt.Net.BCrypt.HashPassword(req.Password),
|
||||
AiChatLimit = defaultAiChatLimit,
|
||||
AiChatPeriod = defaultAiChatPeriod,
|
||||
AiChatWindowStartedAt = quotaWindowStart,
|
||||
CreatedAt = now,
|
||||
};
|
||||
user.FeaturePermissions.Add(new UserFeaturePermission
|
||||
{
|
||||
PermissionKey = FeaturePermissionKeys.Ai,
|
||||
IsEnabled = defaultAiEnabled,
|
||||
CreatedAt = now,
|
||||
UpdatedAt = now,
|
||||
});
|
||||
var defaultLedgerName = (await db.AppConfigs.FirstOrDefaultAsync(c => c.Key == "system.default_ledger_name"))?.Value ?? "日常账本";
|
||||
user.Ledgers.Add(new Ledger { Name = defaultLedgerName, IsDefault = true, CreatedAt = DateTime.UtcNow });
|
||||
db.Users.Add(user);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
var (token, expires) = jwt.Issue(user);
|
||||
return Ok(new AuthResponse(user.Id, user.Username, token, expires));
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<ActionResult<AuthResponse>> Login(LoginRequest req)
|
||||
{
|
||||
var user = await db.Users.FirstOrDefaultAsync(u => u.Username == req.Username);
|
||||
if (user is null || !BCrypt.Net.BCrypt.Verify(req.Password, user.PasswordHash)) return Unauthorized(new ApiError("BAD_CREDENTIALS", "用户名或密码错误"));
|
||||
if (user.IsBanned)
|
||||
return Unauthorized(new ApiError("USER_BANNED", "账号已被封禁"));
|
||||
|
||||
var closureCancelled = false;
|
||||
if (user.AccountClosureScheduledAt.HasValue)
|
||||
{
|
||||
if (user.AccountClosureScheduledAt <= DateTime.UtcNow)
|
||||
return Unauthorized(new ApiError(
|
||||
"ACCOUNT_CLOSED",
|
||||
"账号注销等待期已结束,无法恢复"));
|
||||
user.AccountClosureRequestedAt = null;
|
||||
user.AccountClosureScheduledAt = null;
|
||||
user.AuthVersion++;
|
||||
closureCancelled = true;
|
||||
}
|
||||
|
||||
user.LastLoginAt = DateTime.UtcNow;
|
||||
await db.SaveChangesAsync();
|
||||
var (token, expires) = jwt.Issue(user);
|
||||
return Ok(new AuthResponse(
|
||||
user.Id,
|
||||
user.Username,
|
||||
token,
|
||||
expires,
|
||||
closureCancelled));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user