| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
- using Application.Abstractions.Data;
- using Application.Abstractions.Messaging;
- using Application.Helpers;
- using Domain.Entities.Developers;
- using Microsoft.EntityFrameworkCore;
- using SharedKernel.Results;
- namespace Application.Features.Api.Developers.Tokens.IssuePat;
- internal sealed class Handler(IAppDbContext db) : ICommandHandler<Command, Result<Response>>
- {
- private const int MaxActivePatPerMember = 10;
- public async Task<Result<Response>> Handle(Command request, CancellationToken ct)
- {
- if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 100)
- {
- return Result.Failure<Response>(Error.Problem("Pat.NameInvalid", "Name is required (1-100 chars)."));
- }
- var activeCount = await db.ApiPersonalAccessToken
- .CountAsync(c => c.OwnerMemberID == request.OwnerMemberID && c.RevokedAt == null, ct);
- if (activeCount >= MaxActivePatPerMember)
- {
- return Result.Failure<Response>(Error.Problem("Pat.LimitExceeded", $"Max {MaxActivePatPerMember} active PAT per member."));
- }
- var token = ApiTokenGenerator.GeneratePat();
- var scopesCsv = string.Join(",", (request.Scopes ?? []).Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim()));
- var pat = ApiPersonalAccessToken.Create(
- request.OwnerMemberID,
- request.Name,
- ApiTokenGenerator.Hash(token),
- ApiTokenGenerator.Hint(token),
- scopesCsv,
- request.ExpiresAt
- );
- await db.ApiPersonalAccessToken.AddAsync(pat, ct);
- await db.SaveChangesAsync(ct);
- return Result.Success(new Response(
- pat.ID,
- pat.Name,
- token,
- pat.TokenHint,
- scopesCsv.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries),
- pat.ExpiresAt,
- pat.CreatedAt
- ));
- }
- }
|