package thing
import (
"strconv"
"time"
"donetick.com/core/config"
chRepo "donetick.com/core/internal/chore/repo"
cRepo "donetick.com/core/internal/circle/repo"
tModel "donetick.com/core/internal/thing/model"
tRepo "donetick.com/core/internal/thing/repo"
uRepo "donetick.com/core/internal/user/repo"
"donetick.com/core/internal/utils"
"donetick.com/core/logging"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
)
type Webhook struct {
choreRepo *chRepo.ChoreRepository
circleRepo *cRepo.CircleRepository
thingRepo *tRepo.ThingRepository
userRepo *uRepo.UserRepository
tRepo *tRepo.ThingRepository
}
func NewWebhook(cr *chRepo.ChoreRepository, circleRepo *cRepo.CircleRepository,
thingRepo *tRepo.ThingRepository, userRepo *uRepo.UserRepository, tRepo *tRepo.ThingRepository) *Webhook {
return &Webhook{
choreRepo: cr,
circleRepo: circleRepo,
thingRepo: thingRepo,
userRepo: userRepo,
tRepo: tRepo,
}
}
func (h *Webhook) UpdateThingState(c *gin.Context) {
thing, shouldReturn := validateUserAndThing(c, h)
if shouldReturn {
return
}
state := c.Query("state")
if state == "" {
c.JSON(400, gin.H{"error": "Invalid state value"})
return
}
thing.State = state
if !isValidThingState(thing) {
c.JSON(400, gin.H{"error": "Invalid state for thing"})
return
}
if err := h.thingRepo.UpdateThingState(c, thing); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, gin.H{})
}
func (h *Webhook) ChangeThingState(c *gin.Context) {
thing, shouldReturn := validateUserAndThing(c, h)
if shouldReturn {
return
}
addRemoveRaw := c.Query("op")
setRaw := c.Query("set")
if addRemoveRaw == "" && setRaw == "" {
c.JSON(400, gin.H{"error": "Invalid increment value"})
return
}
var xValue int
var err error
if addRemoveRaw != "" {
xValue, err = strconv.Atoi(addRemoveRaw)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid increment value"})
return
}
currentState, err := strconv.Atoi(thing.State)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid state for thing"})
return
}
newState := currentState + xValue
thing.State = strconv.Itoa(newState)
}
if setRaw != "" {
thing.State = setRaw
}
if !isValidThingState(thing) {
c.JSON(400, gin.H{"error": "Invalid state for thing"})
return
}
if err := h.thingRepo.UpdateThingState(c, thing); err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
shouldReturn1 := WebhookEvaluateTriggerAndScheduleDueDate(h, c, thing)
if shouldReturn1 {
return
}
c.JSON(200, gin.H{"state": thing.State})
}
func WebhookEvaluateTriggerAndScheduleDueDate(h *Webhook, c *gin.Context, thing *tModel.Thing) bool {
// handler should be interface to not duplicate both WebhookEvaluateTriggerAndScheduleDueDate and EvaluateTriggerAndScheduleDueDate
// this is bad code written Saturday at 2:25 AM
log := logging.FromContext(c)
thingChores, err := h.tRepo.GetThingChoresByThingId(c, thing.ID)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return true
}
for _, tc := range thingChores {
triggered := EvaluateThingChore(tc, thing.State)
if triggered {
errSave := h.choreRepo.SetDueDate(c, tc.ChoreID, time.Now().UTC())
if errSave != nil {
log.Error("Error setting due date for chore ", errSave)
log.Error("Chore ID ", tc.ChoreID, " Thing ID ", thing.ID, " State ", thing.State)
}
}
}
return false
}
func validateUserAndThing(c *gin.Context, h *Webhook) (*tModel.Thing, bool) {
apiToken := c.GetHeader("secretkey")
if apiToken == "" {
c.JSON(401, gin.H{"error": "Unauthorized"})
return nil, true
}
thingID, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return nil, true
}
user, err := h.userRepo.GetUserByToken(c, apiToken)
if err != nil {
c.JSON(401, gin.H{"error": "Unauthorized"})
return nil, true
}
thing, err := h.thingRepo.GetThingByID(c, thingID)
if err != nil {
c.JSON(400, gin.H{"error": "Invalid thing id"})
return nil, true
}
if thing.UserID != user.ID {
c.JSON(401, gin.H{"error": "Unauthorized"})
return nil, true
}
return thing, false
}
func Webhooks(cfg *config.Config, w *Webhook, r *gin.Engine, auth *jwt.GinJWTMiddleware) {
thingsAPI := r.Group("webhooks/things")
thingsAPI.Use(utils.TimeoutMiddleware(cfg.Server.WriteTimeout))
{
thingsAPI.GET("/:id/state/change", w.ChangeThingState)
thingsAPI.GET("/:id/state", w.UpdateThingState)
}
}