package internalhttp import ( "context" "encoding/json" "errors" "net/http" "time" "galaxy/mail/internal/service/getdelivery" "galaxy/mail/internal/service/listattempts" "galaxy/mail/internal/service/listdeliveries" "galaxy/mail/internal/service/resenddelivery" ) const defaultOperatorRequestTimeout = 5 * time.Second // ListDeliveriesUseCase lists accepted deliveries for trusted operators. type ListDeliveriesUseCase interface { // Execute returns one filtered deterministic ordered page of deliveries. Execute(context.Context, listdeliveries.Input) (listdeliveries.Result, error) } // GetDeliveryUseCase resolves one accepted delivery for trusted operators. type GetDeliveryUseCase interface { // Execute returns one exact delivery view and its optional dead-letter // entry. Execute(context.Context, getdelivery.Input) (getdelivery.Result, error) } // ListAttemptsUseCase resolves one delivery-attempt history for trusted // operators. type ListAttemptsUseCase interface { // Execute returns the full attempt history of one accepted delivery. Execute(context.Context, listattempts.Input) (listattempts.Result, error) } // ResendDeliveryUseCase clones one accepted terminal delivery for trusted // operator resend. type ResendDeliveryUseCase interface { // Execute creates one new clone delivery and returns its identifier. Execute(context.Context, resenddelivery.Input) (resenddelivery.Result, error) } func newListDeliveriesHandler(useCase ListDeliveriesUseCase, timeout time.Duration) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { input, err := DecodeDeliveryListInput(request) if err != nil { writeErrorResponse(writer, http.StatusBadRequest, ErrorCodeInvalidRequest, err.Error()) return } callCtx, cancel := context.WithTimeout(request.Context(), effectiveOperatorTimeout(timeout)) defer cancel() result, err := useCase.Execute(callCtx, input) if err != nil { switch { case errors.Is(err, listdeliveries.ErrInvalidCursor): writeErrorResponse(writer, http.StatusBadRequest, ErrorCodeInvalidRequest, "cursor is invalid") case errors.Is(err, listdeliveries.ErrServiceUnavailable): writeErrorResponse(writer, http.StatusServiceUnavailable, ErrorCodeServiceUnavailable, "service is unavailable") default: writeErrorResponse(writer, http.StatusInternalServerError, ErrorCodeInternalError, "internal server error") } return } response := DeliveryListResponse{ Items: make([]DeliverySummaryResponse, len(result.Items)), } for index, record := range result.Items { response.Items[index] = summaryResponseFromDelivery(record) } if result.NextCursor != nil { encodedCursor, err := EncodeDeliveryListCursor(*result.NextCursor) if err != nil { writeErrorResponse(writer, http.StatusInternalServerError, ErrorCodeInternalError, "internal server error") return } response.NextCursor = encodedCursor } writeJSONResponse(writer, http.StatusOK, response) } } func newGetDeliveryHandler(useCase GetDeliveryUseCase, timeout time.Duration) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { deliveryID, err := DecodeDeliveryIDFromPath(request) if err != nil { writeErrorResponse(writer, http.StatusBadRequest, ErrorCodeInvalidRequest, err.Error()) return } callCtx, cancel := context.WithTimeout(request.Context(), effectiveOperatorTimeout(timeout)) defer cancel() result, err := useCase.Execute(callCtx, getdelivery.Input{DeliveryID: deliveryID}) if err != nil { switch { case errors.Is(err, getdelivery.ErrNotFound): writeErrorResponse(writer, http.StatusNotFound, ErrorCodeDeliveryNotFound, "delivery not found") case errors.Is(err, getdelivery.ErrServiceUnavailable): writeErrorResponse(writer, http.StatusServiceUnavailable, ErrorCodeServiceUnavailable, "service is unavailable") default: writeErrorResponse(writer, http.StatusInternalServerError, ErrorCodeInternalError, "internal server error") } return } writeJSONResponse(writer, http.StatusOK, detailResponseFromDelivery(result.Delivery, result.DeadLetter)) } } func newListAttemptsHandler(useCase ListAttemptsUseCase, timeout time.Duration) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { deliveryID, err := DecodeDeliveryIDFromPath(request) if err != nil { writeErrorResponse(writer, http.StatusBadRequest, ErrorCodeInvalidRequest, err.Error()) return } callCtx, cancel := context.WithTimeout(request.Context(), effectiveOperatorTimeout(timeout)) defer cancel() result, err := useCase.Execute(callCtx, listattempts.Input{DeliveryID: deliveryID}) if err != nil { switch { case errors.Is(err, listattempts.ErrNotFound): writeErrorResponse(writer, http.StatusNotFound, ErrorCodeDeliveryNotFound, "delivery not found") case errors.Is(err, listattempts.ErrServiceUnavailable): writeErrorResponse(writer, http.StatusServiceUnavailable, ErrorCodeServiceUnavailable, "service is unavailable") default: writeErrorResponse(writer, http.StatusInternalServerError, ErrorCodeInternalError, "internal server error") } return } response := DeliveryAttemptsResponse{ Items: make([]AttemptResponse, len(result.Attempts)), } for index, record := range result.Attempts { response.Items[index] = attemptResponseFromRecord(record) } writeJSONResponse(writer, http.StatusOK, response) } } func newResendDeliveryHandler(useCase ResendDeliveryUseCase, timeout time.Duration) http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { deliveryID, err := DecodeDeliveryIDFromPath(request) if err != nil { writeErrorResponse(writer, http.StatusBadRequest, ErrorCodeInvalidRequest, err.Error()) return } callCtx, cancel := context.WithTimeout(request.Context(), effectiveOperatorTimeout(timeout)) defer cancel() result, err := useCase.Execute(callCtx, resenddelivery.Input{DeliveryID: deliveryID}) if err != nil { switch { case errors.Is(err, resenddelivery.ErrNotFound): writeErrorResponse(writer, http.StatusNotFound, ErrorCodeDeliveryNotFound, "delivery not found") case errors.Is(err, resenddelivery.ErrNotAllowed): writeErrorResponse(writer, http.StatusConflict, ErrorCodeResendNotAllowed, "delivery status does not allow resend") case errors.Is(err, resenddelivery.ErrServiceUnavailable): writeErrorResponse(writer, http.StatusServiceUnavailable, ErrorCodeServiceUnavailable, "service is unavailable") default: writeErrorResponse(writer, http.StatusInternalServerError, ErrorCodeInternalError, "internal server error") } return } writeJSONResponse(writer, http.StatusOK, DeliveryResendResponse{ DeliveryID: result.DeliveryID.String(), }) } } func effectiveOperatorTimeout(timeout time.Duration) time.Duration { if timeout <= 0 { return defaultOperatorRequestTimeout } return timeout } func writeJSONResponse(writer http.ResponseWriter, statusCode int, payload any) { writer.Header().Set("Content-Type", "application/json") writer.WriteHeader(statusCode) _ = json.NewEncoder(writer).Encode(payload) }