Add subtask model and repository, implement webhook notification handling
fix Issue with Scheduler Support NotificationPlatformWebhook
This commit is contained in:
parent
41be361463
commit
8db572f1ec
13 changed files with 337 additions and 38 deletions
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
chModel "donetick.com/core/internal/chore/model"
|
||||
cRepo "donetick.com/core/internal/circle/repo"
|
||||
stRepo "donetick.com/core/internal/subtask/repo"
|
||||
uRepo "donetick.com/core/internal/user/repo"
|
||||
)
|
||||
|
||||
|
@ -25,15 +26,17 @@ type API struct {
|
|||
circleRepo *cRepo.CircleRepository
|
||||
nPlanner *nps.NotificationPlanner
|
||||
eventProducer *events.EventsProducer
|
||||
stRepo *stRepo.SubTasksRepository
|
||||
}
|
||||
|
||||
func NewAPI(cr *chRepo.ChoreRepository, userRepo *uRepo.UserRepository, circleRepo *cRepo.CircleRepository, nPlanner *nps.NotificationPlanner, eventProducer *events.EventsProducer) *API {
|
||||
func NewAPI(cr *chRepo.ChoreRepository, userRepo *uRepo.UserRepository, circleRepo *cRepo.CircleRepository, nPlanner *nps.NotificationPlanner, eventProducer *events.EventsProducer, stRepo *stRepo.SubTasksRepository) *API {
|
||||
return &API{
|
||||
choreRepo: cr,
|
||||
userRepo: userRepo,
|
||||
circleRepo: circleRepo,
|
||||
nPlanner: nPlanner,
|
||||
eventProducer: eventProducer,
|
||||
stRepo: stRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +200,10 @@ func (h *API) CompleteChore(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
if chore.SubTasks != nil && chore.FrequencyType != chModel.FrequencyTypeOnce {
|
||||
h.stRepo.ResetSubtasksCompletion(c, chore.ID)
|
||||
}
|
||||
|
||||
updatedChore, err := h.choreRepo.GetChore(c, choreID)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"donetick.com/core/internal/notifier"
|
||||
nRepo "donetick.com/core/internal/notifier/repo"
|
||||
nps "donetick.com/core/internal/notifier/service"
|
||||
stModel "donetick.com/core/internal/subtask/model"
|
||||
stRepo "donetick.com/core/internal/subtask/repo"
|
||||
tRepo "donetick.com/core/internal/thing/repo"
|
||||
uModel "donetick.com/core/internal/user/model"
|
||||
"donetick.com/core/logging"
|
||||
|
@ -36,11 +38,12 @@ type Handler struct {
|
|||
tRepo *tRepo.ThingRepository
|
||||
lRepo *lRepo.LabelRepository
|
||||
eventProducer *events.EventsProducer
|
||||
stRepo *stRepo.SubTasksRepository
|
||||
}
|
||||
|
||||
func NewHandler(cr *chRepo.ChoreRepository, circleRepo *cRepo.CircleRepository, nt *notifier.Notifier,
|
||||
np *nps.NotificationPlanner, nRepo *nRepo.NotificationRepository, tRepo *tRepo.ThingRepository, lRepo *lRepo.LabelRepository,
|
||||
ep *events.EventsProducer) *Handler {
|
||||
ep *events.EventsProducer, stRepo *stRepo.SubTasksRepository) *Handler {
|
||||
return &Handler{
|
||||
choreRepo: cr,
|
||||
circleRepo: circleRepo,
|
||||
|
@ -50,6 +53,7 @@ func NewHandler(cr *chRepo.ChoreRepository, circleRepo *cRepo.CircleRepository,
|
|||
tRepo: tRepo,
|
||||
lRepo: lRepo,
|
||||
eventProducer: ep,
|
||||
stRepo: stRepo,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,6 +263,7 @@ func (h *Handler) createChore(c *gin.Context) {
|
|||
Points: choreReq.Points,
|
||||
CompletionWindow: choreReq.CompletionWindow,
|
||||
Description: choreReq.Description,
|
||||
SubTasks: choreReq.SubTasks,
|
||||
}
|
||||
id, err := h.choreRepo.CreateChore(c, createdChore)
|
||||
createdChore.ID = id
|
||||
|
@ -270,6 +275,10 @@ func (h *Handler) createChore(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if choreReq.SubTasks != nil {
|
||||
h.stRepo.CreateSubtasks(c, nil, choreReq.SubTasks, createdChore.ID)
|
||||
}
|
||||
|
||||
var choreAssignees []*chModel.ChoreAssignees
|
||||
for _, assignee := range choreReq.Assignees {
|
||||
choreAssignees = append(choreAssignees, &chModel.ChoreAssignees{
|
||||
|
@ -521,6 +530,7 @@ func (h *Handler) editChore(c *gin.Context) {
|
|||
Points: choreReq.Points,
|
||||
CompletionWindow: choreReq.CompletionWindow,
|
||||
Description: choreReq.Description,
|
||||
Priority: choreReq.Priority,
|
||||
}
|
||||
if err := h.choreRepo.UpsertChore(c, updatedChore); err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
|
@ -528,6 +538,54 @@ func (h *Handler) editChore(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
if choreReq.SubTasks != nil {
|
||||
ToBeRemoved := []stModel.SubTask{}
|
||||
ToBeAdded := []stModel.SubTask{}
|
||||
if oldChore.SubTasks == nil {
|
||||
oldChore.SubTasks = &[]stModel.SubTask{}
|
||||
}
|
||||
if choreReq.SubTasks == nil {
|
||||
choreReq.SubTasks = &[]stModel.SubTask{}
|
||||
}
|
||||
for _, existedSubTask := range *oldChore.SubTasks {
|
||||
found := false
|
||||
for _, newSubTask := range *choreReq.SubTasks {
|
||||
if existedSubTask.ID == newSubTask.ID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
ToBeRemoved = append(ToBeRemoved, existedSubTask)
|
||||
}
|
||||
}
|
||||
|
||||
for _, newSubTask := range *choreReq.SubTasks {
|
||||
found := false
|
||||
newSubTask.ChoreID = oldChore.ID
|
||||
|
||||
for _, existedSubTask := range *oldChore.SubTasks {
|
||||
if existedSubTask.ID == newSubTask.ID {
|
||||
if existedSubTask.Name != newSubTask.Name || existedSubTask.OrderID != newSubTask.OrderID {
|
||||
// there is a change in the subtask, update it
|
||||
break
|
||||
}
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
ToBeAdded = append(ToBeAdded, newSubTask)
|
||||
}
|
||||
}
|
||||
if err := h.stRepo.UpdateSubtask(c, oldChore.ID, ToBeRemoved, ToBeAdded); err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error adding subtasks",
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(choreAssigneesToAdd) > 0 {
|
||||
err = h.choreRepo.UpdateChoreAssignees(c, choreAssigneesToAdd)
|
||||
|
||||
|
@ -1077,6 +1135,10 @@ func (h *Handler) completeChore(c *gin.Context) {
|
|||
})
|
||||
return
|
||||
}
|
||||
if updatedChore.SubTasks != nil && updatedChore.FrequencyType != chModel.FrequencyTypeOnce {
|
||||
h.stRepo.ResetSubtasksCompletion(c, updatedChore.ID)
|
||||
}
|
||||
|
||||
// go func() {
|
||||
|
||||
// h.notifier.SendChoreCompletion(c, chore, currentUser)
|
||||
|
@ -1355,6 +1417,76 @@ func (h *Handler) DeleteHistory(c *gin.Context) {
|
|||
"message": "History deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateSubtaskCompletedAt(c *gin.Context) {
|
||||
currentUser, ok := auth.CurrentUser(c)
|
||||
if !ok {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error getting current user",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rawID := c.Param("id")
|
||||
choreID, err := strconv.Atoi(rawID)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "Invalid Chore ID",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
type SubtaskReq struct {
|
||||
ID int `json:"id"`
|
||||
ChoreID int `json:"choreId"`
|
||||
CompletedAt *time.Time `json:"completedAt"`
|
||||
}
|
||||
|
||||
var req SubtaskReq
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
log.Print(err)
|
||||
c.JSON(400, gin.H{
|
||||
"error": "Invalid request",
|
||||
})
|
||||
return
|
||||
}
|
||||
chore, err := h.choreRepo.GetChore(c, choreID)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error getting chore",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !chore.CanComplete(currentUser.ID) {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "User is not assigned to chore",
|
||||
})
|
||||
return
|
||||
}
|
||||
var completedAt *time.Time
|
||||
if req.CompletedAt != nil {
|
||||
completedAt = req.CompletedAt
|
||||
}
|
||||
err = h.stRepo.UpdateSubTaskStatus(c, currentUser.ID, req.ID, completedAt)
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{
|
||||
"error": "Error getting subtask",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
h.eventProducer.SubtaskUpdated(c, currentUser.WebhookURL,
|
||||
&stModel.SubTask{
|
||||
ID: req.ID,
|
||||
ChoreID: req.ChoreID,
|
||||
CompletedAt: completedAt,
|
||||
CompletedBy: currentUser.ID,
|
||||
},
|
||||
)
|
||||
c.JSON(200, gin.H{})
|
||||
|
||||
}
|
||||
|
||||
func checkNextAssignee(chore *chModel.Chore, choresHistory []*chModel.ChoreHistory, performerID int) (int, error) {
|
||||
// copy the history to avoid modifying the original:
|
||||
history := make([]*chModel.ChoreHistory, len(choresHistory))
|
||||
|
@ -1470,6 +1602,7 @@ func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware) {
|
|||
choresRoutes.PUT("/:id/priority", h.updatePriority)
|
||||
choresRoutes.POST("/", h.createChore)
|
||||
choresRoutes.GET("/:id", h.getChore)
|
||||
choresRoutes.PUT("/:id/subtask", h.UpdateSubtaskCompletedAt)
|
||||
choresRoutes.GET("/:id/details", h.GetChoreDetail)
|
||||
choresRoutes.GET("/:id/history", h.GetChoreHistory)
|
||||
choresRoutes.PUT("/:id/history/:history_id", h.ModifyHistory)
|
||||
|
|
|
@ -5,8 +5,8 @@ import (
|
|||
|
||||
cModel "donetick.com/core/internal/circle/model"
|
||||
lModel "donetick.com/core/internal/label/model"
|
||||
stModel "donetick.com/core/internal/subtask/model"
|
||||
tModel "donetick.com/core/internal/thing/model"
|
||||
thingModel "donetick.com/core/internal/thing/model"
|
||||
)
|
||||
|
||||
type FrequencyType string
|
||||
|
@ -62,6 +62,7 @@ type Chore struct {
|
|||
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
|
||||
SubTasks *[]stModel.SubTask `json:"subTasks,omitempty" gorm:"foreignkey:ChoreID;references:ID"` // Subtasks for the chore
|
||||
}
|
||||
|
||||
type Status int8
|
||||
|
@ -119,19 +120,20 @@ type Tag struct {
|
|||
}
|
||||
|
||||
type ChoreDetail struct {
|
||||
ID int `json:"id" gorm:"column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Description *string `json:"description" gorm:"column:description"`
|
||||
FrequencyType string `json:"frequencyType" gorm:"column:frequency_type"`
|
||||
NextDueDate *time.Time `json:"nextDueDate" gorm:"column:next_due_date"`
|
||||
AssignedTo int `json:"assignedTo" gorm:"column:assigned_to"`
|
||||
LastCompletedDate *time.Time `json:"lastCompletedDate" gorm:"column:last_completed_date"`
|
||||
LastCompletedBy *int `json:"lastCompletedBy" gorm:"column:last_completed_by"`
|
||||
TotalCompletedCount int `json:"totalCompletedCount" gorm:"column:total_completed"`
|
||||
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"`
|
||||
ID int `json:"id" gorm:"column:id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
Description *string `json:"description" gorm:"column:description"`
|
||||
FrequencyType string `json:"frequencyType" gorm:"column:frequency_type"`
|
||||
NextDueDate *time.Time `json:"nextDueDate" gorm:"column:next_due_date"`
|
||||
AssignedTo int `json:"assignedTo" gorm:"column:assigned_to"`
|
||||
LastCompletedDate *time.Time `json:"lastCompletedDate" gorm:"column:last_completed_date"`
|
||||
LastCompletedBy *int `json:"lastCompletedBy" gorm:"column:last_completed_by"`
|
||||
TotalCompletedCount int `json:"totalCompletedCount" gorm:"column:total_completed"`
|
||||
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"`
|
||||
Subtasks *[]stModel.SubTask `json:"subTasks,omitempty" gorm:"foreignkey:ChoreID;references:ID"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
|
@ -150,25 +152,27 @@ type ChoreLabels struct {
|
|||
}
|
||||
|
||||
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"`
|
||||
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 *tModel.ThingTrigger `json:"thingTrigger"`
|
||||
Points *int `json:"points"`
|
||||
CompletionWindow *int `json:"completionWindow"`
|
||||
Description *string `json:"description"`
|
||||
Priority int `json:"priority"`
|
||||
SubTasks *[]stModel.SubTask `json:"subTasks"`
|
||||
}
|
||||
|
||||
func (c *Chore) CanEdit(userID int, circleUsers []*cModel.UserCircleDetail) bool {
|
||||
|
|
|
@ -44,7 +44,7 @@ func (r *ChoreRepository) CreateChore(c context.Context, chore *chModel.Chore) (
|
|||
|
||||
func (r *ChoreRepository) GetChore(c context.Context, choreID int) (*chModel.Chore, error) {
|
||||
var chore chModel.Chore
|
||||
if err := r.db.Debug().WithContext(c).Model(&chModel.Chore{}).Preload("Assignees").Preload("ThingChore").Preload("LabelsV2").First(&chore, choreID).Error; err != nil {
|
||||
if err := r.db.Debug().WithContext(c).Model(&chModel.Chore{}).Preload("SubTasks").Preload("Assignees").Preload("ThingChore").Preload("LabelsV2").First(&chore, choreID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &chore, nil
|
||||
|
@ -279,6 +279,7 @@ func (r *ChoreRepository) GetChoreDetailByID(c context.Context, choreID int, cir
|
|||
var choreDetail chModel.ChoreDetail
|
||||
if err := r.db.WithContext(c).
|
||||
Table("chores").
|
||||
Preload("Subtasks").
|
||||
Select(`
|
||||
chores.id,
|
||||
chores.name,
|
||||
|
|
|
@ -91,6 +91,16 @@ func scheduleNextDueDate(chore *chModel.Chore, completedDate time.Time) (*time.T
|
|||
}
|
||||
return nil, fmt.Errorf("no matching day of the week found")
|
||||
case "day_of_the_month":
|
||||
// for day of the month we need to pick the highest between completed date and next due date
|
||||
// when the chore is rolling. i keep forgetting so am writing a detail comment here:
|
||||
// if task due every 15 of jan, and you completed it on the 13 of jan( before the due date ) if we schedule from due date
|
||||
// we will go back to 15 of jan. so we need to pick the highest between the two dates specifically for day of the month
|
||||
if chore.IsRolling && chore.NextDueDate != nil {
|
||||
secondAfterDueDate := chore.NextDueDate.UTC().Add(time.Second)
|
||||
if completedDate.Before(secondAfterDueDate) {
|
||||
baseDate = secondAfterDueDate
|
||||
}
|
||||
}
|
||||
if len(frequencyMetadata.Months) == 0 {
|
||||
return nil, fmt.Errorf("day_of_the_month requires at least one month")
|
||||
}
|
||||
|
@ -101,7 +111,13 @@ func scheduleNextDueDate(chore *chModel.Chore, completedDate time.Time) (*time.T
|
|||
|
||||
// Find the next valid day of the month, considering the year
|
||||
currentMonth := int(baseDate.Month())
|
||||
for i := 0; i < 12; i++ { // Start from 0 to check the current month first
|
||||
|
||||
var startFrom int
|
||||
if chore.NextDueDate != nil && baseDate.Month() == chore.NextDueDate.Month() {
|
||||
startFrom = 1
|
||||
}
|
||||
|
||||
for i := startFrom; i < 12+startFrom; i++ { // Start from 0 to check the current month first
|
||||
nextDueDate := baseDate.AddDate(0, i, 0)
|
||||
nextMonth := (currentMonth + i) % 12 // Use modulo to cycle through months
|
||||
if nextMonth == 0 {
|
||||
|
|
|
@ -224,11 +224,25 @@ func TestScheduleNextDueDateDayOfMonth(t *testing.T) {
|
|||
chore: chModel.Chore{
|
||||
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
||||
Frequency: 15,
|
||||
IsRolling: true,
|
||||
FrequencyMetadata: jsonPtr(`{ "unit": "days", "time": "2025-01-20T18:00:00-05:00", "days": [], "months": [ "january" ] }`),
|
||||
},
|
||||
completedDate: now.AddDate(1, 1, 0),
|
||||
want: timePtr(time.Date(2027, 1, 15, 18, 0, 0, 0, location)),
|
||||
},
|
||||
// test if completed before the 15th of the month:
|
||||
{
|
||||
name: "Day of the month - 15th of January(isRolling)(Completed before due date)",
|
||||
chore: chModel.Chore{
|
||||
NextDueDate: timePtr(time.Date(2025, 1, 15, 18, 0, 0, 0, location)),
|
||||
FrequencyType: chModel.FrequencyTypeDayOfTheMonth,
|
||||
Frequency: 15,
|
||||
IsRolling: true,
|
||||
FrequencyMetadata: jsonPtr(`{ "unit": "days", "time": "2025-01-20T18:00:00-05:00", "days": [], "months": [ "january" ] }`),
|
||||
},
|
||||
completedDate: now.AddDate(0, 0, 2),
|
||||
want: timePtr(time.Date(2026, 1, 15, 18, 0, 0, 0, location)),
|
||||
},
|
||||
}
|
||||
executeTestTable(t, tests)
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
cModel "donetick.com/core/internal/circle/model"
|
||||
nModel "donetick.com/core/internal/notifier/model"
|
||||
pModel "donetick.com/core/internal/points"
|
||||
stModel "donetick.com/core/internal/subtask/model"
|
||||
tModel "donetick.com/core/internal/thing/model"
|
||||
uModel "donetick.com/core/internal/user/model" // Pure go SQLite driver, checkout https://github.com/glebarez/sqlite for details
|
||||
migrations "donetick.com/core/migrations"
|
||||
|
@ -37,6 +38,7 @@ func Migration(db *gorm.DB) error {
|
|||
chModel.ChoreLabels{},
|
||||
migrations.Migration{},
|
||||
pModel.PointsHistory{},
|
||||
stModel.SubTask{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -28,7 +28,8 @@ const (
|
|||
// EventTypeTaskCreated EventType = "task.created"
|
||||
EventTypeTaskReminder EventType = "task.reminder"
|
||||
// EventTypeTaskUpdated EventType = "task.updated"
|
||||
EventTypeTaskCompleted EventType = "task.completed"
|
||||
EventTypeTaskCompleted EventType = "task.completed"
|
||||
EventTypeSubTaskCompleted EventType = "subtask.completed"
|
||||
// EventTypeTaskReassigned EventType = "task.reassigned"
|
||||
EventTypeTaskSkipped EventType = "task.skipped"
|
||||
EventTypeThingChanged EventType = "thing.changed"
|
||||
|
@ -172,3 +173,16 @@ func (p *EventsProducer) ThingsUpdated(ctx context.Context, url string, data int
|
|||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *EventsProducer) SubtaskUpdated(ctx context.Context, url *string, data interface{}) {
|
||||
if url == nil {
|
||||
p.logger.Debug("No subscribers for circle, skipping webhook")
|
||||
return
|
||||
}
|
||||
p.publishEvent(Event{
|
||||
URL: *url,
|
||||
Type: EventTypeSubTaskCompleted,
|
||||
Timestamp: time.Now(),
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ const (
|
|||
NotificationPlatformNone NotificationPlatform = iota
|
||||
NotificationPlatformTelegram
|
||||
NotificationPlatformPushover
|
||||
NotificationPlatformWebhook
|
||||
)
|
||||
|
||||
type JSONB map[string]interface{}
|
||||
|
|
|
@ -41,6 +41,15 @@ func (n *Notifier) SendNotification(c context.Context, notification *nModel.Noti
|
|||
return nil
|
||||
}
|
||||
err = n.Pushover.SendNotification(c, notification)
|
||||
case nModel.NotificationPlatformWebhook:
|
||||
// TODO: Implement webhook notification
|
||||
// currently we have eventProducer to send events always as a webhook
|
||||
// if NotificationPlatform is selected. this a case to catch
|
||||
// when we only want to send a webhook
|
||||
|
||||
default:
|
||||
log.Error("Unknown notification type", "type", notification.TypeID)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("Failed to send notification", "err", err)
|
||||
|
|
12
internal/subtask/model/model.go
Normal file
12
internal/subtask/model/model.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type SubTask struct {
|
||||
ID int `json:"id" gorm:"primary_key"`
|
||||
ChoreID int `json:"-" gorm:"column:chore_id;index"`
|
||||
OrderID int8 `json:"orderId" gorm:"column:order_id"`
|
||||
Name string `json:"name" gorm:"column:name"`
|
||||
CompletedAt *time.Time `json:"completedAt" gorm:"column:completed_at"`
|
||||
CompletedBy int `json:"completedBy" gorm:"column:completed_by"`
|
||||
}
|
84
internal/subtask/repo/repository.go
Normal file
84
internal/subtask/repo/repository.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package repo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
stModel "donetick.com/core/internal/subtask/model"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type SubTasksRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewSubTasksRepository(db *gorm.DB) *SubTasksRepository {
|
||||
return &SubTasksRepository{db}
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) CreateSubtasks(c context.Context, tx *gorm.DB, subtasks *[]stModel.SubTask, choreID int) error {
|
||||
if tx != nil {
|
||||
return tx.Model(&stModel.SubTask{}).Save(subtasks).Error
|
||||
}
|
||||
return r.db.WithContext(c).Save(subtasks).Error
|
||||
}
|
||||
func (r *SubTasksRepository) UpdateSubtask(c context.Context, choreId int, toBeRemove []stModel.SubTask, toBeAdd []stModel.SubTask) error {
|
||||
return r.db.WithContext(c).Transaction(func(tx *gorm.DB) error {
|
||||
if len(toBeRemove) == 0 && len(toBeAdd) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(toBeRemove) > 0 {
|
||||
if err := tx.Delete(toBeRemove).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(toBeAdd) > 0 {
|
||||
var insertions []stModel.SubTask
|
||||
var updates []stModel.SubTask
|
||||
for _, subtask := range toBeAdd {
|
||||
if subtask.ID == 0 {
|
||||
insertions = append(insertions, subtask)
|
||||
} else {
|
||||
updates = append(updates, subtask)
|
||||
}
|
||||
}
|
||||
|
||||
if len(insertions) > 0 {
|
||||
if err := tx.Create(&insertions).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
for _, subtask := range updates {
|
||||
if err := tx.Model(&stModel.SubTask{}).Where("chore_id = ? AND id = ?", choreId, subtask.ID).Updates(subtask).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) DeleteSubtask(c context.Context, tx *gorm.DB, subtaskID int) error {
|
||||
if tx != nil {
|
||||
return tx.Delete(&stModel.SubTask{}, subtaskID).Error
|
||||
}
|
||||
return r.db.WithContext(c).Delete(&stModel.SubTask{}, subtaskID).Error
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) UpdateSubTaskStatus(c context.Context, userID int, subtaskID int, completedAt *time.Time) error {
|
||||
return r.db.Model(&stModel.SubTask{}).Where("id = ?", subtaskID).Updates(map[string]interface{}{
|
||||
"completed_at": completedAt,
|
||||
"completed_by": userID,
|
||||
}).Error
|
||||
}
|
||||
|
||||
func (r *SubTasksRepository) ResetSubtasksCompletion(c context.Context, choreID int) error {
|
||||
return r.db.Model(&stModel.SubTask{}).Where("chore_id = ?", choreID).Updates(map[string]interface{}{
|
||||
"completed_at": nil,
|
||||
"completed_by": nil,
|
||||
}).Error
|
||||
}
|
2
main.go
2
main.go
|
@ -26,6 +26,7 @@ import (
|
|||
label "donetick.com/core/internal/label"
|
||||
lRepo "donetick.com/core/internal/label/repo"
|
||||
"donetick.com/core/internal/resource"
|
||||
spRepo "donetick.com/core/internal/subtask/repo"
|
||||
|
||||
notifier "donetick.com/core/internal/notifier"
|
||||
nRepo "donetick.com/core/internal/notifier/repo"
|
||||
|
@ -89,6 +90,7 @@ func main() {
|
|||
|
||||
// points
|
||||
fx.Provide(pRepo.NewPointsRepository),
|
||||
fx.Provide(spRepo.NewSubTasksRepository),
|
||||
|
||||
// Labels:
|
||||
fx.Provide(lRepo.NewLabelRepository),
|
||||
|
|
Loading…
Add table
Reference in a new issue