diff --git a/internal/chore/api.go b/internal/chore/api.go index 70b269c..f532def 100644 --- a/internal/chore/api.go +++ b/internal/chore/api.go @@ -9,6 +9,7 @@ import ( limiter "github.com/ulule/limiter/v3" + chModel "donetick.com/core/internal/chore/model" uRepo "donetick.com/core/internal/user/repo" ) @@ -44,6 +45,46 @@ func (h *API) GetAllChores(c *gin.Context) { 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 APIs(cfg *config.Config, api *API, r *gin.Engine, auth *jwt.GinJWTMiddleware, limiter *limiter.Limiter) { thingsAPI := r.Group("eapi/v1/chore") diff --git a/internal/chore/handler.go b/internal/chore/handler.go index 238a813..c557d16 100644 --- a/internal/chore/handler.go +++ b/internal/chore/handler.go @@ -26,36 +26,6 @@ import ( "github.com/gin-gonic/gin" ) -type ThingTrigger struct { - ID int `json:"thingID" binding:"required"` - TriggerState string `json:"triggerState" binding:"required"` - Condition string `json:"condition"` -} - -type LabelReq struct { - LabelID int `json:"id" binding:"required"` -} - -type ChoreReq struct { - Name string `json:"name" binding:"required"` - FrequencyType chModel.FrequencyType `json:"frequencyType"` - ID int `json:"id"` - DueDate string `json:"dueDate"` - Assignees []chModel.ChoreAssignees `json:"assignees"` - AssignStrategy string `json:"assignStrategy" binding:"required"` - AssignedTo int `json:"assignedTo"` - IsRolling bool `json:"isRolling"` - IsActive bool `json:"isActive"` - Frequency int `json:"frequency"` - FrequencyMetadata *chModel.FrequencyMetadata `json:"frequencyMetadata"` - Notification bool `json:"notification"` - NotificationMetadata *chModel.NotificationMetadata `json:"notificationMetadata"` - Labels []string `json:"labels"` - LabelsV2 *[]LabelReq `json:"labelsV2"` - ThingTrigger *ThingTrigger `json:"thingTrigger"` - Points *int `json:"points"` - CompletionWindow *int `json:"completionWindow"` -} type Handler struct { choreRepo *chRepo.ChoreRepository circleRepo *cRepo.CircleRepository @@ -185,7 +155,7 @@ func (h *Handler) createChore(c *gin.Context) { return } // Validate chore: - var choreReq ChoreReq + var choreReq chModel.ChoreReq if err := c.ShouldBindJSON(&choreReq); err != nil { log.Print(err) c.JSON(400, gin.H{ @@ -284,6 +254,7 @@ func (h *Handler) createChore(c *gin.Context) { CircleID: currentUser.CircleID, Points: choreReq.Points, CompletionWindow: choreReq.CompletionWindow, + Description: choreReq.Description, } id, err := h.choreRepo.CreateChore(c, createdChore) createdChore.ID = id @@ -302,18 +273,18 @@ func (h *Handler) createChore(c *gin.Context) { UserID: assignee.UserID, }) } - - labelsV2 := make([]int, len(*choreReq.LabelsV2)) - for i, label := range *choreReq.LabelsV2 { - labelsV2[i] = int(label.LabelID) + if choreReq.LabelsV2 != nil { + labelsV2 := make([]int, len(*choreReq.LabelsV2)) + for i, label := range *choreReq.LabelsV2 { + labelsV2[i] = int(label.LabelID) + } + if err := h.lRepo.AssignLabelsToChore(c, createdChore.ID, currentUser.ID, currentUser.CircleID, labelsV2, []int{}); err != nil { + c.JSON(500, gin.H{ + "error": "Error adding labels", + }) + return + } } - if err := h.lRepo.AssignLabelsToChore(c, createdChore.ID, currentUser.ID, currentUser.CircleID, labelsV2, []int{}); err != nil { - c.JSON(500, gin.H{ - "error": "Error adding labels", - }) - return - } - if err := h.choreRepo.UpdateChoreAssignees(c, choreAssignees); err != nil { c.JSON(500, gin.H{ "error": "Error adding chore assignees", @@ -342,7 +313,7 @@ func (h *Handler) editChore(c *gin.Context) { return } - var choreReq ChoreReq + var choreReq chModel.ChoreReq if err := c.ShouldBindJSON(&choreReq); err != nil { log.Print(err) c.JSON(400, gin.H{ @@ -545,6 +516,7 @@ func (h *Handler) editChore(c *gin.Context) { CreatedAt: oldChore.CreatedAt, Points: choreReq.Points, CompletionWindow: choreReq.CompletionWindow, + Description: choreReq.Description, } if err := h.choreRepo.UpsertChore(c, updatedChore); err != nil { c.JSON(500, gin.H{ @@ -589,7 +561,7 @@ func (h *Handler) editChore(c *gin.Context) { }) } -func HandleThingAssociation(choreReq ChoreReq, h *Handler, c *gin.Context, currentUser *uModel.User) bool { +func HandleThingAssociation(choreReq chModel.ChoreReq, h *Handler, c *gin.Context, currentUser *uModel.User) bool { if choreReq.ThingTrigger != nil { thing, err := h.tRepo.GetThingByID(c, choreReq.ThingTrigger.ID) if err != nil { @@ -744,6 +716,61 @@ func (h *Handler) updateAssignee(c *gin.Context) { }) } +func (h *Handler) updateChoreStatus(c *gin.Context) { + type StatusReq struct { + Status *chModel.Status `json:"status" binding:"required"` + } + + var statusReq StatusReq + + if err := c.ShouldBindJSON(&statusReq); err != nil { + c.JSON(400, gin.H{ + "error": "Invalid request", + }) + } + + rawID := c.Param("id") + id, err := strconv.Atoi(rawID) + if err != nil { + c.JSON(400, gin.H{ + "error": "Invalid ID", + }) + return + } + + currentUser, ok := auth.CurrentUser(c) + if !ok { + c.JSON(500, gin.H{ + "error": "Error getting current user", + }) + return + } + + chore, err := h.choreRepo.GetChore(c, id) + if err != nil { + c.JSON(500, gin.H{ + "error": "Error getting chore", + }) + return + } + if chore.CircleID != currentUser.CircleID { + + c.JSON(403, gin.H{ + "error": "You are not allowed to start this chore", + }) + return + } + if err := h.choreRepo.UpdateChoreStatus(c, chore.ID, currentUser.ID, *statusReq.Status); err != nil { + + c.JSON(500, gin.H{ + "error": "Error starting chore", + }) + return + } + c.JSON(200, gin.H{}) + +} + func (h *Handler) skipChore(c *gin.Context) { rawID := c.Param("id") id, err := strconv.Atoi(rawID) @@ -778,7 +805,7 @@ func (h *Handler) skipChore(c *gin.Context) { } nextAssigedTo := chore.AssignedTo - if err := h.choreRepo.CompleteChore(c, chore, nil, currentUser.ID, nextDueDate, nil, nextAssigedTo); err != nil { + if err := h.choreRepo.CompleteChore(c, chore, nil, currentUser.ID, nextDueDate, nil, nextAssigedTo, false); err != nil { c.JSON(500, gin.H{ "error": "Error completing chore", }) @@ -1024,7 +1051,7 @@ func (h *Handler) completeChore(c *gin.Context) { return } - if err := h.choreRepo.CompleteChore(c, chore, additionalNotes, currentUser.ID, nextDueDate, &completedDate, nextAssignedTo); err != nil { + if err := h.choreRepo.CompleteChore(c, chore, additionalNotes, currentUser.ID, nextDueDate, &completedDate, nextAssignedTo, true); err != nil { c.JSON(500, gin.H{ "error": "Error completing chore", }) @@ -1436,6 +1463,7 @@ func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware) { choresRoutes.DELETE("/:id/history/:history_id", h.DeleteHistory) choresRoutes.POST("/:id/do", h.completeChore) choresRoutes.POST("/:id/skip", h.skipChore) + choresRoutes.PUT("/:id/status", h.updateChoreStatus) choresRoutes.PUT("/:id/assignee", h.updateAssignee) choresRoutes.PUT("/:id/dueDate", h.updateDueDate) choresRoutes.PUT("/:id/archive", h.archiveChore) diff --git a/internal/chore/model/model.go b/internal/chore/model/model.go index 4f15219..9e1abe8 100644 --- a/internal/chore/model/model.go +++ b/internal/chore/model/model.go @@ -3,7 +3,9 @@ package model import ( "time" + lModel "donetick.com/core/internal/label/model" tModel "donetick.com/core/internal/thing/model" + thingModel "donetick.com/core/internal/thing/model" ) type FrequencyType string @@ -22,6 +24,16 @@ const ( FrequancyTypeNoRepeat FrequencyType = "no_repeat" ) +type AssignmentStrategy string + +const ( + AssignmentStrategyRandom AssignmentStrategy = "random" + AssignmentStrategyLeastAssigned AssignmentStrategy = "least_assigned" + AssignmentStrategyLeastCompleted AssignmentStrategy = "least_completed" + AssignmentStrategyKeepLastAssigned AssignmentStrategy = "keep_last_assigned" + AssignmentStrategyRandomExceptLastAssigned AssignmentStrategy = "random_except_last_assigned" +) + type Chore struct { ID int `json:"id" gorm:"primary_key"` Name string `json:"name" gorm:"column:name"` // Chore description @@ -32,7 +44,7 @@ type Chore struct { IsRolling bool `json:"isRolling" gorm:"column:is_rolling"` // Whether the chore is rolling AssignedTo int `json:"assignedTo" gorm:"column:assigned_to"` // Who the chore is assigned to Assignees []ChoreAssignees `json:"assignees" gorm:"foreignkey:ChoreID;references:ID"` // Assignees of the chore - AssignStrategy string `json:"assignStrategy" gorm:"column:assign_strategy"` // How the chore is assigned + AssignStrategy AssignmentStrategy `json:"assignStrategy" gorm:"column:assign_strategy"` // How the chore is assigned IsActive bool `json:"isActive" gorm:"column:is_active"` // Whether the chore is active Notification bool `json:"notification" gorm:"column:notification"` // Whether the chore has notification NotificationMetadata *string `json:"notificationMetadata" gorm:"column:notification_meta"` // Additional notification information @@ -44,11 +56,21 @@ type Chore struct { CreatedBy int `json:"createdBy" gorm:"column:created_by"` // Who created the chore UpdatedBy int `json:"updatedBy" gorm:"column:updated_by"` // Who last updated the chore ThingChore *tModel.ThingChore `json:"thingChore" gorm:"foreignkey:chore_id;references:id;<-:false"` // ThingChore relationship - Status int `json:"status" gorm:"column:status"` + Status Status `json:"status" gorm:"column:status"` Priority int `json:"priority" gorm:"column:priority"` CompletionWindow *int `json:"completionWindow,omitempty" gorm:"column:completion_window"` // Number seconds before the chore is due that it can be completed Points *int `json:"points,omitempty" gorm:"column:points"` // Points for completing the chore + Description *string `json:"description,omitempty" gorm:"type:text;column:description"` // Description of the chore } + +type Status int8 + +const ( + ChoreStatusNoStatus Status = 0 + ChoreStatusInProgress Status = 1 + ChoreStatusPaused Status = 2 +) + type ChoreAssignees struct { ID int `json:"-" gorm:"primary_key"` ChoreID int `json:"-" gorm:"column:chore_id;uniqueIndex:idx_chore_user"` // The chore this assignee is for @@ -69,11 +91,9 @@ type ChoreHistory struct { type ChoreHistoryStatus int8 const ( - ChoreHistoryStatusPending ChoreHistoryStatus = 0 - ChoreHistoryStatusCompleted ChoreHistoryStatus = 1 - ChoreHistoryStatusCompletedLate ChoreHistoryStatus = 2 - ChoreHistoryStatusMissed ChoreHistoryStatus = 3 - ChoreHistoryStatusSkipped ChoreHistoryStatus = 4 + ChoreHistoryStatusPending ChoreHistoryStatus = 0 + ChoreHistoryStatusCompleted ChoreHistoryStatus = 1 + ChoreHistoryStatusSkipped ChoreHistoryStatus = 2 ) type FrequencyMetadata struct { @@ -126,3 +146,25 @@ type ChoreLabels struct { UserID int `json:"userId" gorm:"primaryKey;autoIncrement:false;not null"` Label Label } + +type ChoreReq struct { + Name string `json:"name" binding:"required"` + FrequencyType FrequencyType `json:"frequencyType"` + ID int `json:"id"` + DueDate string `json:"dueDate"` + Assignees []ChoreAssignees `json:"assignees"` + AssignStrategy AssignmentStrategy `json:"assignStrategy" binding:"required"` + AssignedTo int `json:"assignedTo"` + IsRolling bool `json:"isRolling"` + IsActive bool `json:"isActive"` + Frequency int `json:"frequency"` + FrequencyMetadata *FrequencyMetadata `json:"frequencyMetadata"` + Notification bool `json:"notification"` + NotificationMetadata *NotificationMetadata `json:"notificationMetadata"` + Labels []string `json:"labels"` + LabelsV2 *[]lModel.LabelReq `json:"labelsV2"` + ThingTrigger *thingModel.ThingTrigger `json:"thingTrigger"` + Points *int `json:"points"` + CompletionWindow *int `json:"completionWindow"` + Description *string `json:"description"` +} diff --git a/internal/chore/repo/repository.go b/internal/chore/repo/repository.go index 1643f84..3b6d124 100644 --- a/internal/chore/repo/repository.go +++ b/internal/chore/repo/repository.go @@ -85,15 +85,7 @@ func (r *ChoreRepository) IsChoreOwner(c context.Context, choreID int, userID in return err } -// func (r *ChoreRepository) ListChores(circleID int) ([]*chModel.Chore, error) { -// var chores []*Chore -// if err := r.db.WithContext(c).Find(&chores).Where("is_active = ?", true).Order("next_due_date").Error; err != nil { -// return nil, err -// } -// return chores, nil -// } - -func (r *ChoreRepository) CompleteChore(c context.Context, chore *chModel.Chore, note *string, userID int, dueDate *time.Time, completedDate *time.Time, nextAssignedTo int) error { +func (r *ChoreRepository) CompleteChore(c context.Context, chore *chModel.Chore, note *string, userID int, dueDate *time.Time, completedDate *time.Time, nextAssignedTo int, applyPoints bool) error { err := r.db.WithContext(c).Transaction(func(tx *gorm.DB) error { ch := &chModel.ChoreHistory{ ChoreID: chore.ID, @@ -102,17 +94,18 @@ func (r *ChoreRepository) CompleteChore(c context.Context, chore *chModel.Chore, AssignedTo: chore.AssignedTo, DueDate: chore.NextDueDate, Note: note, - Points: chore.Points, } if err := tx.Create(ch).Error; err != nil { return err } updates := map[string]interface{}{} updates["next_due_date"] = dueDate + updates["status"] = chModel.ChoreStatusNoStatus if dueDate != nil { updates["assigned_to"] = nextAssignedTo } else { + // one time task updates["is_active"] = false } // Perform the update operation once, using the prepared updates map. @@ -120,8 +113,8 @@ func (r *ChoreRepository) CompleteChore(c context.Context, chore *chModel.Chore, return err } // Update UserCirclee Points : - if chore.Points != nil && *chore.Points > 0 { - if err := tx.Debug().Model(&cModel.UserCircle{}).Where("user_id = ? AND circle_id = ?", userID, chore.CircleID).Update("points", gorm.Expr("points + ?", chore.Points)).Error; err != nil { + if applyPoints && chore.Points != nil && *chore.Points > 0 { + if err := tx.Model(&cModel.UserCircle{}).Where("user_id = ? AND circle_id = ?", userID, chore.CircleID).Update("points", gorm.Expr("points + ?", chore.Points)).Error; err != nil { return err } } @@ -323,3 +316,7 @@ func (r *ChoreRepository) GetChoresHistoryByUserID(c context.Context, userID int } return chores, nil } + +func (r *ChoreRepository) UpdateChoreStatus(c context.Context, choreID int, userId int, status chModel.Status) error { + return r.db.WithContext(c).Model(&chModel.Chore{}).Where("id = ?", choreID).Where("created_by = ? ", userId).Update("status", status).Error +} diff --git a/internal/circle/handler.go b/internal/circle/handler.go index 03fb9cb..c878971 100644 --- a/internal/circle/handler.go +++ b/internal/circle/handler.go @@ -11,6 +11,7 @@ import ( chRepo "donetick.com/core/internal/chore/repo" cModel "donetick.com/core/internal/circle/model" cRepo "donetick.com/core/internal/circle/repo" + pRepo "donetick.com/core/internal/points/repo" uModel "donetick.com/core/internal/user/model" uRepo "donetick.com/core/internal/user/repo" "donetick.com/core/logging" @@ -22,13 +23,15 @@ type Handler struct { circleRepo *cRepo.CircleRepository userRepo *uRepo.UserRepository choreRepo *chRepo.ChoreRepository + pointRepo *pRepo.PointsRepository } -func NewHandler(cr *cRepo.CircleRepository, ur *uRepo.UserRepository, c *chRepo.ChoreRepository) *Handler { +func NewHandler(cr *cRepo.CircleRepository, ur *uRepo.UserRepository, c *chRepo.ChoreRepository, pr *pRepo.PointsRepository) *Handler { return &Handler{ circleRepo: cr, userRepo: ur, choreRepo: c, + pointRepo: pr, } } @@ -424,6 +427,112 @@ func (h *Handler) AcceptJoinRequest(c *gin.Context) { }) } +func (h *Handler) RedeemPoints(c *gin.Context) { + type RedeemPointsRequest struct { + Points int `json:"points"` + UserID int `json:"userId"` + } + + log := logging.FromContext(c) + currentUser, ok := auth.CurrentUser(c) + if !ok { + c.JSON(500, gin.H{ + "error": "Error getting current user", + }) + return + } + // parse body: + var redeemReq RedeemPointsRequest + + if err := c.ShouldBind(&redeemReq); err != nil { + c.JSON(400, gin.H{ + "error": "Invalid request", + }) + return + + } + + if redeemReq.Points <= 0 { + c.JSON(400, gin.H{ + "error": "Invalid request", + }) + return + } + circleIdRaw := c.Param("id") + + circleID, err := strconv.Atoi(circleIdRaw) + if err != nil { + log.Error("Error redeeming points: invalid circle id") + c.JSON(400, gin.H{ + "error": "Invalid request: invalid circle id", + }) + return + } + if circleID != currentUser.CircleID { + log.Error("You are not a member of this circle") + c.JSON(400, gin.H{ + "error": "You are not a member of this circle", + }) + return + } + members, err := h.circleRepo.GetCircleUsers(c, currentUser.CircleID) + if err != nil { + log.Error("Error getting circle admins:", err) + c.JSON(500, gin.H{ + "error": "Error getting circle admins", + }) + return + } + isAdmin := false + isValidMember := false + var member *cModel.UserCircleDetail + for _, user := range members { + if user.UserID == currentUser.ID && user.Role == "admin" { + isAdmin = true + } + if user.UserID == redeemReq.UserID { + isValidMember = true + member = user + } + + } + + if !isAdmin { + log.Error("Error redeeming points: user is not an admin of this circle") + c.JSON(403, gin.H{ + "error": "You are not an admin of this circle", + }) + return + } + if !isValidMember { + log.Error("Error redeeming points: user is not a member of this circle") + c.JSON(400, gin.H{ + "error": "User is not a member of this circle", + }) + return + } + if member.Points-redeemReq.Points < 0 { + log.Error("Error redeeming points: user does not have enough points") + c.JSON(400, gin.H{ + "error": "User does not have enough points", + }) + return + } + + err = h.circleRepo.RedeemPoints(c, currentUser.CircleID, currentUser.ID, redeemReq.Points, currentUser.ID) + if err != nil { + log.Error("Error redeeming points:", err) + c.JSON(500, gin.H{ + "error": "Error redeeming points", + }) + return + } + + c.JSON(200, gin.H{ + "res": "Points redeemed successfully", + }) +} + func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware) { log.Println("Registering routes") @@ -437,6 +546,7 @@ func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware) { circleRoutes.POST("/join", h.JoinCircle) circleRoutes.DELETE("/leave", h.LeaveCircle) circleRoutes.DELETE("/:id/members/delete", h.DeleteCircleMember) + circleRoutes.POST("/:id/members/points/redeem", h.RedeemPoints) } diff --git a/internal/circle/repo/repository.go b/internal/circle/repo/repository.go index 05a1654..7b1af47 100644 --- a/internal/circle/repo/repository.go +++ b/internal/circle/repo/repository.go @@ -2,8 +2,10 @@ package repo import ( "context" + "time" cModel "donetick.com/core/internal/circle/model" + pModel "donetick.com/core/internal/points" uModel "donetick.com/core/internal/user/model" "gorm.io/gorm" ) @@ -138,3 +140,26 @@ func (r *CircleRepository) AssignDefaultCircle(c context.Context, userID int) er return r.db.WithContext(c).Model(&uModel.User{}).Where("id = ?", userID).Update("circle_id", defaultCircle.ID).Error } + +func (r *CircleRepository) RedeemPoints(c context.Context, circleID int, userID int, points int, createdBy int) error { + err := r.db.Transaction(func(tx *gorm.DB) error { + if err := tx.Model(&cModel.UserCircle{}).Where("user_id = ? AND circle_id = ?", userID, circleID).Update("points_redeemed", gorm.Expr("points_redeemed + ?", points)).Error; err != nil { + return err + } + if err := tx.Create(&pModel.PointsHistory{ + Action: pModel.PointsHistoryActionRedeem, + CircleID: circleID, + UserID: userID, + Points: points, + CreatedAt: time.Now().UTC(), + CreatedBy: createdBy, + }).Error; err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + return nil +} diff --git a/internal/label/model/model.go b/internal/label/model/model.go index 45084ec..dd81979 100644 --- a/internal/label/model/model.go +++ b/internal/label/model/model.go @@ -7,3 +7,7 @@ type Label struct { CircleID *int `json:"-" gorm:"column:circle_id"` CreatedBy int `json:"created_by" gorm:"column:created_by"` } + +type LabelReq struct { + LabelID int `json:"id" binding:"required"` +} diff --git a/internal/points/model.go b/internal/points/model.go new file mode 100644 index 0000000..da9abd4 --- /dev/null +++ b/internal/points/model.go @@ -0,0 +1,21 @@ +package model + +import "time" + +type PointsHistory struct { + ID int `json:"id" gorm:"primary_key"` // Unique identifier + Action PointsHistoryAction `json:"action" gorm:"column:action"` // Action + Points int `json:"points" gorm:"column:points"` // Points + CreatedAt time.Time `json:"created_at" gorm:"column:created_at"` // Created at + CreatedBy int `json:"created_by" gorm:"column:created_by"` // Created by + UserID int `json:"user_id" gorm:"column:user_id;index"` // User ID + CircleID int `json:"circle_id" gorm:"column:circle_id;index"` // Circle ID with index +} + +type PointsHistoryAction int8 + +const ( + PointsHistoryActionAdd PointsHistoryAction = iota + PointsHistoryActionRemove + PointsHistoryActionRedeem +) diff --git a/internal/points/repo/repository.go b/internal/points/repo/repository.go new file mode 100644 index 0000000..3ed654e --- /dev/null +++ b/internal/points/repo/repository.go @@ -0,0 +1,24 @@ +package points + +import ( + "context" + + pModel "donetick.com/core/internal/points" + "gorm.io/gorm" +) + +type PointsRepository struct { + db *gorm.DB +} + +func NewPointsRepository(db *gorm.DB) *PointsRepository { + return &PointsRepository{db} +} + +func (r *PointsRepository) CreatePointsHistory(c context.Context, tx *gorm.DB, pointsHistory *pModel.PointsHistory) error { + if tx != nil { + return tx.Model(&pModel.PointsHistory{}).Save(pointsHistory).Error + } + + return r.db.WithContext(c).Save(pointsHistory).Error +} diff --git a/internal/thing/model/model.go b/internal/thing/model/model.go index 1780c64..6f5a40d 100644 --- a/internal/thing/model/model.go +++ b/internal/thing/model/model.go @@ -28,3 +28,9 @@ type ThingChore struct { TriggerState string `json:"triggerState" gorm:"column:trigger_state"` Condition string `json:"condition" gorm:"column:condition"` } + +type ThingTrigger struct { + ID int `json:"thingID" binding:"required"` + TriggerState string `json:"triggerState" binding:"required"` + Condition string `json:"condition"` +} diff --git a/main.go b/main.go index afa98fd..3772ebd 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,7 @@ import ( nps "donetick.com/core/internal/notifier/service" "donetick.com/core/internal/notifier/service/pushover" telegram "donetick.com/core/internal/notifier/service/telegram" + pRepo "donetick.com/core/internal/points/repo" "donetick.com/core/internal/thing" tRepo "donetick.com/core/internal/thing/repo" "donetick.com/core/internal/user" @@ -81,6 +82,9 @@ func main() { // things fx.Provide(tRepo.NewThingRepository), + // points + fx.Provide(pRepo.NewPointsRepository), + // Labels: fx.Provide(lRepo.NewLabelRepository), fx.Provide(label.NewHandler),