using System.Security.Claims; using MiaoJiZhang.Api.Contracts; using MiaoJiZhang.Infrastructure.Persistence; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.EntityFrameworkCore; namespace MiaoJiZhang.Api.Services; public static class FeaturePermissionKeys { public const string Ai = "ai"; } public sealed class AiPermissionService(AppDbContext db) { public async Task IsEnabledAsync(long userId, CancellationToken ct = default) { var value = await db.UserFeaturePermissions .AsNoTracking() .Where(x => x.UserId == userId && x.PermissionKey == FeaturePermissionKeys.Ai) .Select(x => (bool?)x.IsEnabled) .FirstOrDefaultAsync(ct); // Existing installations remain compatible if a migration has not backfilled yet. return value ?? true; } } public sealed class RequireAiPermissionAttribute : TypeFilterAttribute { public RequireAiPermissionAttribute() : base(typeof(AiPermissionFilter)) { } } public sealed class AiPermissionFilter(AiPermissionService permissions) : IAsyncActionFilter { public async Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next) { var value = context.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? context.HttpContext.User.FindFirstValue("sub"); if (!long.TryParse(value, out var userId) || !await permissions.IsEnabledAsync(userId, context.HttpContext.RequestAborted)) { context.Result = new ObjectResult(new ApiError( "AI_PERMISSION_DENIED", "当前账号暂无 AI 功能权限")) { StatusCode = StatusCodes.Status403Forbidden, }; return; } await next(); } }