Files
galaxy-game/mail/internal/api/internalhttp/operator_handler.go
T
2026-04-17 18:39:16 +02:00

196 lines
7.0 KiB
Go

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)
}