282 lines
6.3 KiB
Go
282 lines
6.3 KiB
Go
|
package thing
|
||
|
|
||
|
import (
|
||
|
"strconv"
|
||
|
"time"
|
||
|
|
||
|
auth "donetick.com/core/internal/authorization"
|
||
|
chRepo "donetick.com/core/internal/chore/repo"
|
||
|
cRepo "donetick.com/core/internal/circle/repo"
|
||
|
nRepo "donetick.com/core/internal/notifier/repo"
|
||
|
nps "donetick.com/core/internal/notifier/service"
|
||
|
tModel "donetick.com/core/internal/thing/model"
|
||
|
tRepo "donetick.com/core/internal/thing/repo"
|
||
|
"donetick.com/core/logging"
|
||
|
jwt "github.com/appleboy/gin-jwt/v2"
|
||
|
"github.com/gin-gonic/gin"
|
||
|
)
|
||
|
|
||
|
type Handler struct {
|
||
|
choreRepo *chRepo.ChoreRepository
|
||
|
circleRepo *cRepo.CircleRepository
|
||
|
nPlanner *nps.NotificationPlanner
|
||
|
nRepo *nRepo.NotificationRepository
|
||
|
tRepo *tRepo.ThingRepository
|
||
|
}
|
||
|
|
||
|
type ThingRequest struct {
|
||
|
ID int `json:"id"`
|
||
|
Name string `json:"name" binding:"required"`
|
||
|
Type string `json:"type" binding:"required"`
|
||
|
State string `json:"state"`
|
||
|
}
|
||
|
|
||
|
func NewHandler(cr *chRepo.ChoreRepository, circleRepo *cRepo.CircleRepository,
|
||
|
np *nps.NotificationPlanner, nRepo *nRepo.NotificationRepository, tRepo *tRepo.ThingRepository) *Handler {
|
||
|
return &Handler{
|
||
|
choreRepo: cr,
|
||
|
circleRepo: circleRepo,
|
||
|
nPlanner: np,
|
||
|
nRepo: nRepo,
|
||
|
tRepo: tRepo,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (h *Handler) CreateThing(c *gin.Context) {
|
||
|
log := logging.FromContext(c)
|
||
|
currentUser, ok := auth.CurrentUser(c)
|
||
|
if !ok {
|
||
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var req ThingRequest
|
||
|
if err := c.BindJSON(&req); err != nil {
|
||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
thing := &tModel.Thing{
|
||
|
Name: req.Name,
|
||
|
UserID: currentUser.ID,
|
||
|
Type: req.Type,
|
||
|
State: req.State,
|
||
|
}
|
||
|
if !isValidThingState(thing) {
|
||
|
c.JSON(400, gin.H{"error": "Invalid state"})
|
||
|
return
|
||
|
}
|
||
|
log.Debug("Creating thing", thing)
|
||
|
if err := h.tRepo.UpsertThing(c, thing); err != nil {
|
||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
c.JSON(201, gin.H{
|
||
|
"res": thing,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (h *Handler) UpdateThingState(c *gin.Context) {
|
||
|
currentUser, ok := auth.CurrentUser(c)
|
||
|
if !ok {
|
||
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thingIDRaw := c.Param("id")
|
||
|
thingID, err := strconv.Atoi(thingIDRaw)
|
||
|
if err != nil {
|
||
|
c.JSON(400, gin.H{"error": "Invalid thing id"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
val := c.Query("value")
|
||
|
if val == "" {
|
||
|
c.JSON(400, gin.H{"error": "state or increment query param is required"})
|
||
|
return
|
||
|
}
|
||
|
thing, err := h.tRepo.GetThingByID(c, thingID)
|
||
|
if thing.UserID != currentUser.ID {
|
||
|
c.JSON(403, gin.H{"error": "Forbidden"})
|
||
|
return
|
||
|
}
|
||
|
if err != nil {
|
||
|
c.JSON(500, gin.H{"error": "Unable to find thing"})
|
||
|
return
|
||
|
}
|
||
|
thing.State = val
|
||
|
if !isValidThingState(thing) {
|
||
|
c.JSON(400, gin.H{"error": "Invalid state"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if err := h.tRepo.UpdateThingState(c, thing); err != nil {
|
||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
shouldReturn := EvaluateTriggerAndScheduleDueDate(h, c, thing)
|
||
|
if shouldReturn {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
c.JSON(200, gin.H{
|
||
|
"res": thing,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func EvaluateTriggerAndScheduleDueDate(h *Handler, c *gin.Context, thing *tModel.Thing) bool {
|
||
|
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 {
|
||
|
h.choreRepo.SetDueDateIfNotExisted(c, tc.ChoreID, time.Now().UTC())
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func (h *Handler) UpdateThing(c *gin.Context) {
|
||
|
currentUser, ok := auth.CurrentUser(c)
|
||
|
if !ok {
|
||
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var req ThingRequest
|
||
|
if err := c.BindJSON(&req); err != nil {
|
||
|
c.JSON(400, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thing, err := h.tRepo.GetThingByID(c, req.ID)
|
||
|
|
||
|
if err != nil {
|
||
|
c.JSON(500, gin.H{"error": "Unable to find thing"})
|
||
|
return
|
||
|
}
|
||
|
if thing.UserID != currentUser.ID {
|
||
|
c.JSON(403, gin.H{"error": "Forbidden"})
|
||
|
return
|
||
|
}
|
||
|
thing.Name = req.Name
|
||
|
thing.Type = req.Type
|
||
|
if req.State != "" {
|
||
|
thing.State = req.State
|
||
|
if !isValidThingState(thing) {
|
||
|
c.JSON(400, gin.H{"error": "Invalid state"})
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err := h.tRepo.UpsertThing(c, thing); err != nil {
|
||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
c.JSON(200, gin.H{
|
||
|
"res": thing,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (h *Handler) GetAllThings(c *gin.Context) {
|
||
|
currentUser, ok := auth.CurrentUser(c)
|
||
|
if !ok {
|
||
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
things, err := h.tRepo.GetUserThings(c, currentUser.ID)
|
||
|
if err != nil {
|
||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
c.JSON(200, gin.H{
|
||
|
"res": things,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (h *Handler) GetThingHistory(c *gin.Context) {
|
||
|
currentUser, ok := auth.CurrentUser(c)
|
||
|
if !ok {
|
||
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thingIDRaw := c.Param("id")
|
||
|
thingID, err := strconv.Atoi(thingIDRaw)
|
||
|
if err != nil {
|
||
|
c.JSON(400, gin.H{"error": "Invalid thing id"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thing, err := h.tRepo.GetThingByID(c, thingID)
|
||
|
if err != nil {
|
||
|
c.JSON(500, gin.H{"error": "Unable to find thing"})
|
||
|
return
|
||
|
}
|
||
|
if thing.UserID != currentUser.ID {
|
||
|
c.JSON(403, gin.H{"error": "Forbidden"})
|
||
|
return
|
||
|
}
|
||
|
offsetRaw := c.Query("offset")
|
||
|
offset, err := strconv.Atoi(offsetRaw)
|
||
|
if err != nil {
|
||
|
c.JSON(400, gin.H{"error": "Invalid offset"})
|
||
|
return
|
||
|
}
|
||
|
history, err := h.tRepo.GetThingHistoryWithOffset(c, thingID, offset, 10)
|
||
|
if err != nil {
|
||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
c.JSON(200, gin.H{
|
||
|
"res": history,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
func (h *Handler) DeleteThing(c *gin.Context) {
|
||
|
currentUser, ok := auth.CurrentUser(c)
|
||
|
if !ok {
|
||
|
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thingIDRaw := c.Param("id")
|
||
|
thingID, err := strconv.Atoi(thingIDRaw)
|
||
|
if err != nil {
|
||
|
c.JSON(400, gin.H{"error": "Invalid thing id"})
|
||
|
return
|
||
|
}
|
||
|
|
||
|
thing, err := h.tRepo.GetThingByID(c, thingID)
|
||
|
if err != nil {
|
||
|
c.JSON(500, gin.H{"error": "Unable to find thing"})
|
||
|
return
|
||
|
}
|
||
|
if thing.UserID != currentUser.ID {
|
||
|
c.JSON(403, gin.H{"error": "Forbidden"})
|
||
|
return
|
||
|
}
|
||
|
if err := h.tRepo.DeleteThing(c, thingID); err != nil {
|
||
|
c.JSON(500, gin.H{"error": err.Error()})
|
||
|
return
|
||
|
}
|
||
|
c.JSON(200, gin.H{})
|
||
|
}
|
||
|
func Routes(r *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware) {
|
||
|
|
||
|
thingRoutes := r.Group("things")
|
||
|
thingRoutes.Use(auth.MiddlewareFunc())
|
||
|
{
|
||
|
thingRoutes.POST("", h.CreateThing)
|
||
|
thingRoutes.PUT("/:id/state", h.UpdateThingState)
|
||
|
thingRoutes.PUT("", h.UpdateThing)
|
||
|
thingRoutes.GET("", h.GetAllThings)
|
||
|
thingRoutes.GET("/:id/history", h.GetThingHistory)
|
||
|
thingRoutes.DELETE("/:id", h.DeleteThing)
|
||
|
}
|
||
|
}
|