donetick/internal/chore/api.go

225 lines
5.5 KiB
Go
Raw Normal View History

package chore
import (
"log"
"strconv"
"time"
"donetick.com/core/config"
chRepo "donetick.com/core/internal/chore/repo"
"donetick.com/core/internal/events"
nps "donetick.com/core/internal/notifier/service"
jwt "github.com/appleboy/gin-jwt/v2"
"github.com/gin-gonic/gin"
limiter "github.com/ulule/limiter/v3"
chModel "donetick.com/core/internal/chore/model"
cRepo "donetick.com/core/internal/circle/repo"
uRepo "donetick.com/core/internal/user/repo"
)
type API struct {
choreRepo *chRepo.ChoreRepository
userRepo *uRepo.UserRepository
circleRepo *cRepo.CircleRepository
nPlanner *nps.NotificationPlanner
eventProducer *events.EventsProducer
}
func NewAPI(cr *chRepo.ChoreRepository, userRepo *uRepo.UserRepository, circleRepo *cRepo.CircleRepository, nPlanner *nps.NotificationPlanner, eventProducer *events.EventsProducer) *API {
return &API{
choreRepo: cr,
userRepo: userRepo,
circleRepo: circleRepo,
nPlanner: nPlanner,
eventProducer: eventProducer,
}
}
func (h *API) GetAllChores(c *gin.Context) {
apiToken := c.GetHeader("secretkey")
if apiToken == "" {
c.JSON(401, gin.H{"error": "Unauthorized"})
return
}
user, err := h.userRepo.GetUserByToken(c, apiToken)
if err != nil {
c.JSON(401, gin.H{"error": "Unauthorized"})
return
}
chores, err := h.choreRepo.GetChores(c, user.CircleID, user.ID, false)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, chores)
}
func (h *API) CreateChore(c *gin.Context) {
var choreRequest chModel.ChoreReq
apiToken := c.GetHeader("secretkey")
if apiToken == "" {
c.JSON(401, gin.H{"error": "Unauthorized"})
return
}
user, err := h.userRepo.GetUserByToken(c, apiToken)
if err != nil {
c.JSON(401, gin.H{"error": "Unauthorized"})
return
}
if err := c.BindJSON(&choreRequest); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
chore := &chModel.Chore{
CreatedBy: user.ID,
CircleID: user.CircleID,
Name: choreRequest.Name,
IsRolling: choreRequest.IsRolling,
FrequencyType: choreRequest.FrequencyType,
Frequency: choreRequest.Frequency,
AssignStrategy: choreRequest.AssignStrategy,
AssignedTo: user.ID,
Assignees: []chModel.ChoreAssignees{{UserID: user.ID}},
Description: choreRequest.Description,
}
_, err = h.choreRepo.CreateChore(c, chore)
if err != nil {
c.JSON(500, gin.H{"error": err.Error()})
return
}
c.JSON(200, chore)
}
func (h *API) CompleteChore(c *gin.Context) {
completedDate := time.Now().UTC()
choreIDRaw := c.Param("id")
choreID, err := strconv.Atoi(choreIDRaw)
if err != nil {
c.JSON(400, gin.H{
"error": "Invalid ID",
})
return
}
apiToken := c.GetHeader("secretkey")
if apiToken == "" {
c.JSON(401, gin.H{"error": "No secret key provided"})
return
}
currentUser, err := h.userRepo.GetUserByToken(c, apiToken)
if err != nil {
c.JSON(401, gin.H{"error": "Unauthorized"})
return
}
chore, err := h.choreRepo.GetChore(c, choreID)
if err != nil {
c.JSON(500, gin.H{
"error": "Error getting chore",
})
return
}
// user need to be assigned to the chore to complete it
if !chore.CanComplete(currentUser.ID) {
c.JSON(400, gin.H{
"error": "User is not assigned to chore",
})
return
}
// confirm that the chore in completion window:
if chore.CompletionWindow != nil {
if completedDate.Before(chore.NextDueDate.Add(time.Hour * time.Duration(*chore.CompletionWindow))) {
c.JSON(400, gin.H{
"error": "Chore is out of completion window",
})
return
}
}
var nextDueDate *time.Time
if chore.FrequencyType == "adaptive" {
history, err := h.choreRepo.GetChoreHistoryWithLimit(c, chore.ID, 5)
if err != nil {
c.JSON(500, gin.H{
"error": "Error getting chore history",
})
return
}
nextDueDate, err = scheduleAdaptiveNextDueDate(chore, completedDate, history)
if err != nil {
log.Printf("Error scheduling next due date: %s", err)
c.JSON(500, gin.H{
"error": "Error scheduling next due date",
})
return
}
} else {
nextDueDate, err = scheduleNextDueDate(chore, completedDate.UTC())
if err != nil {
log.Printf("Error scheduling next due date: %s", err)
c.JSON(500, gin.H{
"error": "Error scheduling next due date",
})
return
}
}
choreHistory, err := h.choreRepo.GetChoreHistory(c, chore.ID)
if err != nil {
c.JSON(500, gin.H{
"error": "Error getting chore history",
})
return
}
nextAssignedTo, err := checkNextAssignee(chore, choreHistory, currentUser.ID)
if err != nil {
log.Printf("Error checking next assignee: %s", err)
c.JSON(500, gin.H{
"error": "Error checking next assignee",
})
return
}
if err := h.choreRepo.CompleteChore(c, chore, nil, currentUser.ID, nextDueDate, &completedDate, nextAssignedTo, true); err != nil {
c.JSON(500, gin.H{
"error": "Error completing chore",
})
return
}
updatedChore, err := h.choreRepo.GetChore(c, choreID)
if err != nil {
c.JSON(500, gin.H{
"error": "Error getting chore",
})
return
}
h.nPlanner.GenerateNotifications(c, updatedChore)
h.eventProducer.ChoreCompleted(c, currentUser.WebhookURL, chore, &currentUser.User)
c.JSON(200,
updatedChore,
)
}
func APIs(cfg *config.Config, api *API, r *gin.Engine, auth *jwt.GinJWTMiddleware, limiter *limiter.Limiter) {
thingsAPI := r.Group("eapi/v1/chore")
// thingsAPI.Use(utils.TimeoutMiddleware(cfg.Server.WriteTimeout), utils.RateLimitMiddleware(limiter))
{
thingsAPI.GET("", api.GetAllChores)
thingsAPI.POST("/:id/complete", api.CompleteChore)
}
}