package files import ( "time" "git.apinb.com/bsm-sdk/core/infra" fileerrors "git.apinb.com/ops/files/internal/errors" "git.apinb.com/ops/files/internal/impl" "git.apinb.com/ops/files/internal/models" "github.com/gin-gonic/gin" ) func CompleteUpload(ctx *gin.Context) { fileObject, ok := loadOwnedFile(ctx) if !ok { return } if fileObject.Status != models.FileStatusPending { infra.Response.Error(ctx, fileerrors.ErrUploadStatusConflict) return } if fileObject.ExpiresAt == nil || !fileObject.ExpiresAt.After(time.Now()) { infra.Response.Error(ctx, fileerrors.ErrUploadExpired) return } objectInfo, err := impl.StorageService.Stat(ctx.Request.Context(), fileObject.ObjectKey) if err != nil { infra.Response.Error(ctx, fileerrors.ErrObjectStorageOperation) return } if objectInfo.Size != fileObject.ExpectedSize { infra.Response.Error(ctx, fileerrors.ErrObjectInfoMismatch) return } completedAt := time.Now() result := impl.DBService.Model(&models.FileObject{}). Where("id = ? AND status = ? AND expires_at > ?", fileObject.ID, models.FileStatusPending, completedAt). Updates(map[string]any{ "status": models.FileStatusReady, "actual_size": objectInfo.Size, "e_tag": objectInfo.ETag, "completed_at": completedAt, }) if result.Error != nil { infra.Response.Error(ctx, fileerrors.ErrDatabaseOperation) return } if result.RowsAffected != 1 { infra.Response.Error(ctx, fileerrors.ErrUploadStatusConflict) return } fileObject.Status = models.FileStatusReady fileObject.ActualSize = objectInfo.Size fileObject.ETag = objectInfo.ETag fileObject.CompletedAt = &completedAt infra.Response.Success(ctx, newFileResponse(fileObject)) }