using System.Linq; using FastEndpoints; using GB5Shared.DTO.Framework.Login; using GB5Shared.DTO.Framework.ResponseStandard; using GB5Shared.FastEndPoint; using RecruitmentBLL.CandidatePortal; using RecruitmentSL.Common; using ErrorResponse = GB5Shared.Resource.Response.ErrorResponse; using static GB5Shared.GB5Constant.Constant; namespace RecruitmentSL.EndPoints.CandidatePortal { // CandidatePortal-scheme-protected. ObjectId (CandidateId) is always resolved from the // verified JWT claims server-side (CandidatePortalCallerContext) — never client-supplied — // so a candidate can never tag an attachment against another candidate's record. Tagged with // EntityConstant.OBJECTRECRUITMENTCANDIDATEDOC via ICandidatePortalBLL.UploadDocumentAsync // (GB5Shared.Attachment.IAttachmentUploadService). public class UploadDocument : BaseEndpoint> { private const string CandidatePortalScheme = "CandidatePortal"; private readonly ICandidatePortalBLL _BLL; public UploadDocument(ICandidatePortalBLL bll) => _BLL = bll; public record Params( [property: FromHeader] string Login, [property: QueryParam] string? Remarks ); public override void Configure() { Post("/CandidatePortal/UploadDocument"); AllowFileUploads(); AuthSchemes(CandidatePortalScheme); } protected override string? GetCacheKey(Params req, LoginDTO login) => null; protected override async Task> ExecuteAsync( Params req, LoginDTO login, CancellationToken ct) { var caller = CandidatePortalCallerContext.FromClaims(HttpContext.User.Claims); if (caller.CandidateId <= 0) return await GB5Shared.ResponseStandard.Response.CreateErrorResponse( ErrorResponse.AccessDeniedMessage, CacheKeyLevel.NOT_REQUIRED, login, statusCode: 403); try { var file = HttpContext.Request.Form.Files.FirstOrDefault(); if (file is null || file.Length == 0) throw new ArgumentException("No file provided or file is empty."); await using var stream = file.OpenReadStream(); var result = await _BLL.UploadDocumentAsync( caller.CandidateId, stream, file.FileName, file.ContentType, req.Remarks, login, ct) .ConfigureAwait(false); return await GB5Shared.ResponseStandard.Response.CreateSuccessResponse( result, CacheKeyLevel.NOT_REQUIRED, login); } catch (Exception ex) { return await GB5Shared.ResponseStandard.Response.CreateExceptionError( ex, CacheKeyLevel.NOT_REQUIRED, login, ex.Message, 400); } } } }