Add Points to user's circle, GetChoresHistoryByUserID
make sure task completed only if it's within completion window
This commit is contained in:
commit
aceeb74e75
6 changed files with 121 additions and 22 deletions
|
@ -36,7 +36,7 @@ func (h *API) GetAllChores(c *gin.Context) {
|
|||
c.JSON(401, gin.H{"error": "Unauthorized"})
|
||||
return
|
||||
}
|
||||
chores, err := h.choreRepo.GetChores(c, user.CircleID, user.ID)
|
||||
chores, err := h.choreRepo.GetChores(c, user.CircleID, user.ID, false)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": err.Error()})
|
||||
return
|
||||
|
|
|
@ -53,6 +53,8 @@ type ChoreReq struct {
|
|||
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
|
||||
|
@ -85,7 +87,13 @@ func (h *Handler) getChores(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
chores, err := h.choreRepo.GetChores(c, u.CircleID, u.ID)
|
||||
includeArchived := false
|
||||
|
||||
if c.Query("includeArchived") == "true" {
|
||||
includeArchived = true
|
||||
}
|
||||
|
||||
chores, err := h.choreRepo.GetChores(c, u.CircleID, u.ID, includeArchived)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error getting chores",
|
||||
|
@ -271,6 +279,8 @@ func (h *Handler) createChore(c *gin.Context) {
|
|||
CreatedBy: currentUser.ID,
|
||||
CreatedAt: time.Now().UTC(),
|
||||
CircleID: currentUser.CircleID,
|
||||
Points: choreReq.Points,
|
||||
CompletionWindow: choreReq.CompletionWindow,
|
||||
}
|
||||
id, err := h.choreRepo.CreateChore(c, createdChore)
|
||||
createdChore.ID = id
|
||||
|
@ -530,6 +540,8 @@ func (h *Handler) editChore(c *gin.Context) {
|
|||
UpdatedBy: currentUser.ID,
|
||||
CreatedBy: oldChore.CreatedBy,
|
||||
CreatedAt: oldChore.CreatedAt,
|
||||
Points: choreReq.Points,
|
||||
CompletionWindow: choreReq.CompletionWindow,
|
||||
}
|
||||
if err := h.choreRepo.UpsertChore(c, updatedChore); err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
|
@ -954,6 +966,16 @@ func (h *Handler) completeChore(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
// confirm that the chore in completion window:
|
||||
if chore.CompletionWindow != nil {
|
||||
if completedDate.After(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)
|
||||
|
@ -1197,6 +1219,45 @@ func (h *Handler) updatePriority(c *gin.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
func (h *Handler) getChoresHistory(c *gin.Context) {
|
||||
|
||||
currentUser, ok := auth.CurrentUser(c)
|
||||
if !ok {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error getting current user",
|
||||
})
|
||||
return
|
||||
}
|
||||
durationRaw := c.Query("limit")
|
||||
if durationRaw == "" {
|
||||
durationRaw = "7"
|
||||
}
|
||||
|
||||
duration, err := strconv.Atoi(durationRaw)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "Invalid duration",
|
||||
})
|
||||
return
|
||||
}
|
||||
includeCircleRaw := c.Query("members")
|
||||
includeCircle := false
|
||||
if includeCircleRaw == "true" {
|
||||
includeCircle = true
|
||||
}
|
||||
|
||||
choreHistories, err := h.choreRepo.GetChoresHistoryByUserID(c, currentUser.ID, currentUser.CircleID, duration, includeCircle)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error getting chore history",
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(200, gin.H{
|
||||
"res": choreHistories,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteHistory(c *gin.Context) {
|
||||
|
||||
currentUser, ok := auth.CurrentUser(c)
|
||||
|
@ -1361,7 +1422,7 @@ func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware) {
|
|||
{
|
||||
choresRoutes.GET("/", h.getChores)
|
||||
choresRoutes.GET("/archived", h.getArchivedChores)
|
||||
|
||||
choresRoutes.GET("/history", h.getChoresHistory)
|
||||
choresRoutes.PUT("/", h.editChore)
|
||||
choresRoutes.PUT("/:id/priority", h.updatePriority)
|
||||
choresRoutes.POST("/", h.createChore)
|
||||
|
|
|
@ -46,6 +46,8 @@ type Chore struct {
|
|||
ThingChore *tModel.ThingChore `json:"thingChore" gorm:"foreignkey:chore_id;references:id;<-:false"` // ThingChore relationship
|
||||
Status int `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
|
||||
}
|
||||
type ChoreAssignees struct {
|
||||
ID int `json:"-" gorm:"primary_key"`
|
||||
|
@ -53,15 +55,26 @@ type ChoreAssignees struct {
|
|||
UserID int `json:"userId" gorm:"column:user_id;uniqueIndex:idx_chore_user"` // The user this assignee is for
|
||||
}
|
||||
type ChoreHistory struct {
|
||||
ID int `json:"id" gorm:"primary_key"` // Unique identifier
|
||||
ChoreID int `json:"choreId" gorm:"column:chore_id"` // The chore this history is for
|
||||
CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"` // When the chore was completed
|
||||
CompletedBy int `json:"completedBy" gorm:"column:completed_by"` // Who completed the chore
|
||||
AssignedTo int `json:"assignedTo" gorm:"column:assigned_to"` // Who the chore was assigned to
|
||||
Note *string `json:"notes" gorm:"column:notes"` // Notes about the chore
|
||||
DueDate *time.Time `json:"dueDate" gorm:"column:due_date"` // When the chore was due
|
||||
UpdatedAt *time.Time `json:"updatedAt" gorm:"column:updated_at"` // When the record was last updated
|
||||
ID int `json:"id" gorm:"primary_key"` // Unique identifier
|
||||
ChoreID int `json:"choreId" gorm:"column:chore_id"` // The chore this history is for
|
||||
CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"` // When the chore was completed
|
||||
CompletedBy int `json:"completedBy" gorm:"column:completed_by"` // Who completed the chore
|
||||
AssignedTo int `json:"assignedTo" gorm:"column:assigned_to"` // Who the chore was assigned to
|
||||
Note *string `json:"notes" gorm:"column:notes"` // Notes about the chore
|
||||
DueDate *time.Time `json:"dueDate" gorm:"column:due_date"` // When the chore was due
|
||||
UpdatedAt *time.Time `json:"updatedAt" gorm:"column:updated_at"` // When the record was last updated
|
||||
Status ChoreHistoryStatus `json:"status" gorm:"column:status"` // Status of the chore
|
||||
Points *int `json:"points,omitempty" gorm:"column:points"` // Points for completing the chore
|
||||
}
|
||||
type ChoreHistoryStatus int8
|
||||
|
||||
const (
|
||||
ChoreHistoryStatusPending ChoreHistoryStatus = 0
|
||||
ChoreHistoryStatusCompleted ChoreHistoryStatus = 1
|
||||
ChoreHistoryStatusCompletedLate ChoreHistoryStatus = 2
|
||||
ChoreHistoryStatusMissed ChoreHistoryStatus = 3
|
||||
ChoreHistoryStatusSkipped ChoreHistoryStatus = 4
|
||||
)
|
||||
|
||||
type FrequencyMetadata struct {
|
||||
Days []*string `json:"days,omitempty"`
|
||||
|
@ -96,6 +109,7 @@ type ChoreDetail struct {
|
|||
Priority int `json:"priority" gorm:"column:priority"`
|
||||
Notes *string `json:"notes" gorm:"column:notes"`
|
||||
CreatedBy int `json:"createdBy" gorm:"column:created_by"`
|
||||
CompletionWindow *int `json:"completionWindow,omitempty" gorm:"column:completion_window"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
config "donetick.com/core/config"
|
||||
chModel "donetick.com/core/internal/chore/model"
|
||||
cModel "donetick.com/core/internal/circle/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
|
@ -49,10 +50,13 @@ func (r *ChoreRepository) GetChore(c context.Context, choreID int) (*chModel.Cho
|
|||
return &chore, nil
|
||||
}
|
||||
|
||||
func (r *ChoreRepository) GetChores(c context.Context, circleID int, userID int) ([]*chModel.Chore, error) {
|
||||
func (r *ChoreRepository) GetChores(c context.Context, circleID int, userID int, includeArchived bool) ([]*chModel.Chore, error) {
|
||||
var chores []*chModel.Chore
|
||||
// if err := r.db.WithContext(c).Preload("Assignees").Where("is_active = ?", true).Order("next_due_date asc").Find(&chores, "circle_id = ?", circleID).Error; err != nil {
|
||||
if err := r.db.WithContext(c).Preload("Assignees").Preload("LabelsV2").Joins("left join chore_assignees on chores.id = chore_assignees.chore_id").Where("chores.circle_id = ? AND (chores.created_by = ? OR chore_assignees.user_id = ?)", circleID, userID, userID).Group("chores.id").Order("next_due_date asc").Find(&chores, "circle_id = ? AND is_active = ?", circleID, true).Error; err != nil {
|
||||
query := r.db.WithContext(c).Preload("Assignees").Preload("LabelsV2").Joins("left join chore_assignees on chores.id = chore_assignees.chore_id").Where("chores.circle_id = ? AND (chores.created_by = ? OR chore_assignees.user_id = ?)", circleID, userID, userID).Group("chores.id").Order("next_due_date asc")
|
||||
if !includeArchived {
|
||||
query = query.Where("chores.is_active = ?", true)
|
||||
}
|
||||
if err := query.Find(&chores, "circle_id = ?", circleID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chores, nil
|
||||
|
@ -98,6 +102,7 @@ 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
|
||||
|
@ -114,6 +119,12 @@ func (r *ChoreRepository) CompleteChore(c context.Context, chore *chModel.Chore,
|
|||
if err := tx.Model(&chModel.Chore{}).Where("id = ?", chore.ID).Updates(updates).Error; err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
@ -266,6 +277,7 @@ func (r *ChoreRepository) GetChoreDetailByID(c context.Context, choreID int, cir
|
|||
chores.assigned_to,
|
||||
chores.created_by,
|
||||
chores.priority,
|
||||
chores.completion_window,
|
||||
recent_history.last_completed_date,
|
||||
recent_history.notes,
|
||||
recent_history.last_assigned_to as last_completed_by,
|
||||
|
@ -301,3 +313,13 @@ func (r *ChoreRepository) ArchiveChore(c context.Context, choreID int, userID in
|
|||
func (r *ChoreRepository) UnarchiveChore(c context.Context, choreID int, userID int) error {
|
||||
return r.db.WithContext(c).Model(&chModel.Chore{}).Where("id = ? and created_by = ?", choreID, userID).Update("is_active", true).Error
|
||||
}
|
||||
|
||||
func (r *ChoreRepository) GetChoresHistoryByUserID(c context.Context, userID int, circleID int, days int, includeCircle bool) ([]*chModel.ChoreHistory, error) {
|
||||
|
||||
var chores []*chModel.ChoreHistory
|
||||
since := time.Now().AddDate(0, 0, days*-1)
|
||||
if err := r.db.WithContext(c).Where("completed_by = ? AND completed_at > ?", userID, since).Order("completed_at desc").Find(&chores).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return chores, nil
|
||||
}
|
||||
|
|
|
@ -172,7 +172,7 @@ func (h *Handler) LeaveCircle(c *gin.Context) {
|
|||
}
|
||||
|
||||
func handleUserLeavingCircle(h *Handler, c *gin.Context, leavingUser *uModel.User, orginalCircleID int) error {
|
||||
userAssignedCircleChores, err := h.choreRepo.GetChores(c, leavingUser.CircleID, leavingUser.ID)
|
||||
userAssignedCircleChores, err := h.choreRepo.GetChores(c, leavingUser.CircleID, leavingUser.ID, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -22,13 +22,15 @@ type CircleDetail struct {
|
|||
}
|
||||
|
||||
type UserCircle struct {
|
||||
ID int `json:"id" gorm:"primary_key"` // Unique identifier
|
||||
UserID int `json:"userId" gorm:"column:user_id"` // User ID
|
||||
CircleID int `json:"circleId" gorm:"column:circle_id"` // Circle ID
|
||||
Role string `json:"role" gorm:"column:role"` // Role
|
||||
IsActive bool `json:"isActive" gorm:"column:is_active;default:false"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"` // Created at
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"column:updated_at"` // Updated at
|
||||
ID int `json:"id" gorm:"primary_key"` // Unique identifier
|
||||
UserID int `json:"userId" gorm:"column:user_id"` // User ID
|
||||
CircleID int `json:"circleId" gorm:"column:circle_id"` // Circle ID
|
||||
Role string `json:"role" gorm:"column:role"` // Role
|
||||
IsActive bool `json:"isActive" gorm:"column:is_active;default:false"`
|
||||
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"` // Created at
|
||||
UpdatedAt time.Time `json:"updatedAt" gorm:"column:updated_at"` // Updated at
|
||||
Points int `json:"points" gorm:"column:points;default:0;not null"` // Points
|
||||
PointsRedeemed int `json:"pointsRedeemed" gorm:"column:points_redeemed;default:0;not null"` // Points Redeemed
|
||||
}
|
||||
|
||||
type UserCircleDetail struct {
|
||||
|
|
Loading…
Add table
Reference in a new issue