-
-
Notifications
You must be signed in to change notification settings - Fork 272
feat: send schedules based on review feedback #841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f80acce
df1642a
a988dea
6cfa208
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package entities | ||
|
|
||
| import ( | ||
| "time" | ||
|
|
||
| "github.com/google/uuid" | ||
| ) | ||
|
|
||
| // SendScheduleWindow represents a single availability window for a day of the week. | ||
| type SendScheduleWindow struct { | ||
| DayOfWeek int `json:"day_of_week" example:"1"` | ||
| StartMinute int `json:"start_minute" example:"540"` | ||
| EndMinute int `json:"end_minute" example:"1020"` | ||
| } | ||
|
|
||
| // SendSchedule controls when a phone is allowed to send outgoing SMS messages. | ||
| type SendSchedule struct { | ||
| ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;" example:"32343a19-da5e-4b1b-a767-3298a73703cb"` | ||
| UserID UserID `json:"user_id" example:"WB7DRDWrJZRGbYrv2CKGkqbzvqdC"` | ||
| Name string `json:"name" example:"Business Hours"` | ||
| Timezone string `json:"timezone" example:"Europe/Tallinn"` | ||
| IsActive bool `json:"is_active" gorm:"default:true" example:"true"` | ||
| Windows []SendScheduleWindow `json:"windows" gorm:"type:jsonb;serializer:json"` | ||
| CreatedAt time.Time `json:"created_at" example:"2022-06-05T14:26:02.302718+03:00"` | ||
| UpdatedAt time.Time `json:"updated_at" example:"2022-06-05T14:26:10.303278+03:00"` | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| package handlers | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/NdoleStudio/httpsms/pkg/requests" | ||
| "github.com/NdoleStudio/httpsms/pkg/services" | ||
| "github.com/NdoleStudio/httpsms/pkg/telemetry" | ||
| "github.com/NdoleStudio/httpsms/pkg/validators" | ||
| "github.com/davecgh/go-spew/spew" | ||
| "github.com/gofiber/fiber/v2" | ||
| "github.com/google/uuid" | ||
| "github.com/palantir/stacktrace" | ||
| ) | ||
|
|
||
| type SendScheduleHandler struct { | ||
| handler | ||
| logger telemetry.Logger | ||
| tracer telemetry.Tracer | ||
| validator *validators.SendScheduleHandlerValidator | ||
| service *services.SendScheduleService | ||
| } | ||
|
|
||
| func NewSendScheduleHandler(logger telemetry.Logger, tracer telemetry.Tracer, validator *validators.SendScheduleHandlerValidator, service *services.SendScheduleService) *SendScheduleHandler { | ||
| return &SendScheduleHandler{logger: logger.WithService(fmt.Sprintf("%T", &SendScheduleHandler{})), tracer: tracer, validator: validator, service: service} | ||
| } | ||
|
|
||
| func (h *SendScheduleHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) { | ||
| router.Get("/v1/send-schedules", h.computeRoute(middlewares, h.Index)...) | ||
| router.Post("/v1/send-schedules", h.computeRoute(middlewares, h.Store)...) | ||
| router.Get("/v1/send-schedules/:scheduleID", h.computeRoute(middlewares, h.Show)...) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we use this route anywhere |
||
| router.Put("/v1/send-schedules/:scheduleID", h.computeRoute(middlewares, h.Update)...) | ||
| router.Delete("/v1/send-schedules/:scheduleID", h.computeRoute(middlewares, h.Delete)...) | ||
| } | ||
|
|
||
| // Index godoc | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instruct the AI to actually type the description it should say something like. SImilar comment goes to all the methods in this class.
|
||
| // @Summary List send schedules | ||
| // @Description Lists the send schedules owned by the authenticated user. | ||
| // @Security ApiKeyAuth | ||
| // @Tags Send Schedules | ||
| // @Produce json | ||
| // @Success 200 {object} responses.SendSchedulesResponse | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The response is not |
||
| // @Failure 401 {object} responses.Unauthorized | ||
| // @Failure 500 {object} responses.InternalServerError | ||
| // @Router /send-schedules [get] | ||
| func (h *SendScheduleHandler) Index(c *fiber.Ctx) error { | ||
| ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) | ||
| defer span.End() | ||
| schedules, err := h.service.Index(ctx, h.userIDFomContext(c)) | ||
| if err != nil { | ||
| ctxLogger.Error(stacktrace.Propagate(err, "cannot list send schedules")) | ||
| return h.responseInternalServerError(c) | ||
| } | ||
| return h.responseOK(c, "send schedules fetched successfully", schedules) | ||
| } | ||
|
|
||
| // Show godoc | ||
| // @Summary Show send schedule | ||
| // @Description Loads a single send schedule owned by the authenticated user. | ||
| // @Security ApiKeyAuth | ||
| // @Tags Send Schedules | ||
| // @Produce json | ||
| // @Param scheduleID path string true "Schedule ID" | ||
| // @Success 200 {object} responses.SendScheduleResponse | ||
| // @Failure 401 {object} responses.Unauthorized | ||
| // @Failure 404 {object} responses.NotFound | ||
| // @Failure 500 {object} responses.InternalServerError | ||
| // @Router /send-schedules/{scheduleID} [get] | ||
| func (h *SendScheduleHandler) Show(c *fiber.Ctx) error { | ||
| ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) | ||
| defer span.End() | ||
| scheduleID, err := uuid.Parse(c.Params("scheduleID")) | ||
| if err != nil { | ||
| return h.responseBadRequest(c, err) | ||
| } | ||
| schedule, err := h.service.Load(ctx, h.userIDFomContext(c), scheduleID) | ||
| if err != nil { | ||
| ctxLogger.Error(stacktrace.Propagate(err, "cannot load send schedule")) | ||
| if stacktrace.GetCode(err) == 404 { | ||
| return h.responseNotFound(c, err.Error()) | ||
| } | ||
| return h.responseInternalServerError(c) | ||
| } | ||
| return h.responseOK(c, "send schedule fetched successfully", schedule) | ||
| } | ||
|
|
||
| // Store godoc | ||
| // @Summary Create send schedule | ||
| // @Description Creates a send schedule for the authenticated user. | ||
| // @Security ApiKeyAuth | ||
| // @Tags Send Schedules | ||
| // @Accept json | ||
| // @Produce json | ||
| // @Param payload body requests.SendScheduleStore true "Payload of new send schedule." | ||
| // @Success 201 {object} responses.SendScheduleResponse | ||
| // @Failure 400 {object} responses.BadRequest | ||
| // @Failure 401 {object} responses.Unauthorized | ||
| // @Failure 422 {object} responses.UnprocessableEntity | ||
| // @Failure 500 {object} responses.InternalServerError | ||
| // @Router /send-schedules [post] | ||
| func (h *SendScheduleHandler) Store(c *fiber.Ctx) error { | ||
| ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) | ||
| defer span.End() | ||
| var request requests.SendScheduleStore | ||
| if err := c.BodyParser(&request); err != nil { | ||
| return h.responseBadRequest(c, err) | ||
| } | ||
| request = request.Sanitize() | ||
| if errors := h.validator.ValidateStore(ctx, request); len(errors) != 0 { | ||
| ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("validation errors [%s], while storing send schedule [%+#v]", spew.Sdump(errors), request))) | ||
| return h.responseUnprocessableEntity(c, errors, "validation errors while saving send schedule") | ||
| } | ||
| schedule, err := h.service.Store(ctx, request.ToParams(h.userFromContext(c))) | ||
| if err != nil { | ||
| ctxLogger.Error(stacktrace.Propagate(err, "cannot create send schedule")) | ||
| return h.responseInternalServerError(c) | ||
| } | ||
| return h.responseCreated(c, "send schedule created successfully", schedule) | ||
| } | ||
|
|
||
| // Update godoc | ||
| // @Summary Update send schedule | ||
| // @Description Updates a send schedule owned by the authenticated user. | ||
| // @Security ApiKeyAuth | ||
| // @Tags Send Schedules | ||
| // @Accept json | ||
| // @Produce json | ||
| // @Param scheduleID path string true "Schedule ID" | ||
| // @Param payload body requests.SendScheduleStore true "Payload of updated send schedule." | ||
| // @Success 200 {object} responses.SendScheduleResponse | ||
| // @Failure 400 {object} responses.BadRequest | ||
| // @Failure 401 {object} responses.Unauthorized | ||
| // @Failure 404 {object} responses.NotFound | ||
| // @Failure 422 {object} responses.UnprocessableEntity | ||
| // @Failure 500 {object} responses.InternalServerError | ||
| // @Router /send-schedules/{scheduleID} [put] | ||
| func (h *SendScheduleHandler) Update(c *fiber.Ctx) error { | ||
| ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) | ||
| defer span.End() | ||
| scheduleID, err := uuid.Parse(c.Params("scheduleID")) | ||
| if err != nil { | ||
| return h.responseBadRequest(c, err) | ||
| } | ||
| var request requests.SendScheduleStore | ||
| if err = c.BodyParser(&request); err != nil { | ||
| return h.responseBadRequest(c, err) | ||
| } | ||
| request = request.Sanitize() | ||
| if errors := h.validator.ValidateStore(ctx, request); len(errors) != 0 { | ||
| return h.responseUnprocessableEntity(c, errors, "validation errors while updating send schedule") | ||
| } | ||
| schedule, err := h.service.Update(ctx, h.userIDFomContext(c), scheduleID, request.ToParams(h.userFromContext(c))) | ||
| if err != nil { | ||
| ctxLogger.Error(stacktrace.Propagate(err, "cannot update send schedule")) | ||
| if stacktrace.GetCode(err) == 404 { | ||
| return h.responseNotFound(c, err.Error()) | ||
| } | ||
| return h.responseInternalServerError(c) | ||
| } | ||
| return h.responseOK(c, "send schedule updated successfully", schedule) | ||
| } | ||
|
|
||
| // Delete godoc | ||
| // @Summary Delete send schedule | ||
| // @Description Deletes a send schedule owned by the authenticated user. | ||
| // @Security ApiKeyAuth | ||
| // @Tags Send Schedules | ||
| // @Produce json | ||
| // @Param scheduleID path string true "Schedule ID" | ||
| // @Success 204 {object} responses.NoContent | ||
| // @Failure 400 {object} responses.BadRequest | ||
| // @Failure 401 {object} responses.Unauthorized | ||
| // @Failure 500 {object} responses.InternalServerError | ||
| // @Router /send-schedules/{scheduleID} [delete] | ||
| func (h *SendScheduleHandler) Delete(c *fiber.Ctx) error { | ||
| ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger) | ||
| defer span.End() | ||
| scheduleID, err := uuid.Parse(c.Params("scheduleID")) | ||
| if err != nil { | ||
| return h.responseBadRequest(c, err) | ||
| } | ||
| if err = h.service.Delete(ctx, h.userIDFomContext(c), scheduleID); err != nil { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Respond with 404 if the schedule doesn't exist. |
||
| ctxLogger.Error(stacktrace.Propagate(err, "cannot delete send schedule")) | ||
| return h.responseInternalServerError(c) | ||
| } | ||
| return h.responseNoContent(c, "send schedule deleted successfully") | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call the entity
MessageSendSchedule