196 lines
7.0 KiB
Go
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)
|
|
}
|