feat: mail service
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user