Merge branch 'dev'
This commit is contained in:
commit
bddb80b134
10 changed files with 321 additions and 19 deletions
2
.github/workflows/go-release.yml
vendored
2
.github/workflows/go-release.yml
vendored
|
@ -31,7 +31,7 @@ jobs:
|
||||||
- name: Build Frontend
|
- name: Build Frontend
|
||||||
run: |
|
run: |
|
||||||
cd frontend-code
|
cd frontend-code
|
||||||
npm run build --mode selfhosted
|
npm run build-selfhosted
|
||||||
|
|
||||||
- name: Copy Frontend
|
- name: Copy Frontend
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -33,7 +33,7 @@ type ThingTrigger struct {
|
||||||
|
|
||||||
type ChoreReq struct {
|
type ChoreReq struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
FrequencyType string `json:"frequencyType"`
|
FrequencyType chModel.FrequencyType `json:"frequencyType"`
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
DueDate string `json:"dueDate"`
|
DueDate string `json:"dueDate"`
|
||||||
Assignees []chModel.ChoreAssignees `json:"assignees"`
|
Assignees []chModel.ChoreAssignees `json:"assignees"`
|
||||||
|
|
|
@ -6,10 +6,26 @@ import (
|
||||||
tModel "donetick.com/core/internal/thing/model"
|
tModel "donetick.com/core/internal/thing/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type FrequencyType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FrequancyTypeOnce FrequencyType = "once"
|
||||||
|
FrequancyTypeDaily FrequencyType = "daily"
|
||||||
|
FrequancyTypeWeekly FrequencyType = "weekly"
|
||||||
|
FrequancyTypeMonthly FrequencyType = "monthly"
|
||||||
|
FrequancyTypeYearly FrequencyType = "yearly"
|
||||||
|
FrequancyTypeAdaptive FrequencyType = "adaptive"
|
||||||
|
FrequancyTypeIntervel FrequencyType = "interval"
|
||||||
|
FrequancyTypeDayOfTheWeek FrequencyType = "days_of_the_week"
|
||||||
|
FrequancyTypeDayOfTheMonth FrequencyType = "day_of_the_month"
|
||||||
|
FrequancyTypeTrigger FrequencyType = "trigger"
|
||||||
|
FrequancyTypeNoRepeat FrequencyType = "no_repeat"
|
||||||
|
)
|
||||||
|
|
||||||
type Chore struct {
|
type Chore struct {
|
||||||
ID int `json:"id" gorm:"primary_key"`
|
ID int `json:"id" gorm:"primary_key"`
|
||||||
Name string `json:"name" gorm:"column:name"` // Chore description
|
Name string `json:"name" gorm:"column:name"` // Chore description
|
||||||
FrequencyType string `json:"frequencyType" gorm:"column:frequency_type"` // "daily", "weekly", "monthly", "yearly", "adaptive",or "custom"
|
FrequencyType FrequencyType `json:"frequencyType" gorm:"column:frequency_type"` // "daily", "weekly", "monthly", "yearly", "adaptive",or "custom"
|
||||||
Frequency int `json:"frequency" gorm:"column:frequency"` // Number of days, weeks, months, or years between chores
|
Frequency int `json:"frequency" gorm:"column:frequency"` // Number of days, weeks, months, or years between chores
|
||||||
FrequencyMetadata *string `json:"frequencyMetadata" gorm:"column:frequency_meta"` // Additional frequency information
|
FrequencyMetadata *string `json:"frequencyMetadata" gorm:"column:frequency_meta"` // Additional frequency information
|
||||||
NextDueDate *time.Time `json:"nextDueDate" gorm:"column:next_due_date;index"` // When the chore is due
|
NextDueDate *time.Time `json:"nextDueDate" gorm:"column:next_due_date;index"` // When the chore is due
|
||||||
|
|
|
@ -3,6 +3,7 @@ package chore
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -141,11 +142,15 @@ func scheduleNextDueDate(chore *chModel.Chore, completedDate time.Time) (*time.T
|
||||||
return &nextDueDate, nil
|
return &nextDueDate, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func scheduleAdaptiveNextDueDate(chore *chModel.Chore, completedDate time.Time, history []*chModel.ChoreHistory) (*time.Time, error) {
|
func scheduleAdaptiveNextDueDate(chore *chModel.Chore, completedDate time.Time, history []*chModel.ChoreHistory) (*time.Time, error) {
|
||||||
// will generate due date base on history and the different between the completed date and the due date
|
|
||||||
// the more recent the higher weight
|
history = append([]*chModel.ChoreHistory{
|
||||||
if len(history) <= 1 {
|
{
|
||||||
|
CompletedAt: &completedDate,
|
||||||
|
},
|
||||||
|
}, history...)
|
||||||
|
|
||||||
|
if len(history) < 2 {
|
||||||
if chore.NextDueDate != nil {
|
if chore.NextDueDate != nil {
|
||||||
diff := completedDate.UTC().Sub(chore.NextDueDate.UTC())
|
diff := completedDate.UTC().Sub(chore.NextDueDate.UTC())
|
||||||
nextDueDate := completedDate.UTC().Add(diff)
|
nextDueDate := completedDate.UTC().Add(diff)
|
||||||
|
@ -153,22 +158,23 @@ func scheduleAdaptiveNextDueDate(chore *chModel.Chore, completedDate time.Time,
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
var weight float64
|
|
||||||
|
var totalDelay float64
|
||||||
var totalWeight float64
|
var totalWeight float64
|
||||||
var nextDueDate time.Time
|
decayFactor := 0.5 // Adjust this value to control the decay rate
|
||||||
|
|
||||||
for i := 0; i < len(history)-1; i++ {
|
for i := 0; i < len(history)-1; i++ {
|
||||||
delay := history[i].CompletedAt.UTC().Sub(history[i+1].CompletedAt.UTC()).Seconds()
|
delay := history[i].CompletedAt.UTC().Sub(history[i+1].CompletedAt.UTC()).Seconds()
|
||||||
weight = delay * float64(len(history)-i)
|
weight := math.Pow(decayFactor, float64(i))
|
||||||
|
totalDelay += delay * weight
|
||||||
totalWeight += weight
|
totalWeight += weight
|
||||||
}
|
}
|
||||||
// calculate the average delay
|
|
||||||
averageDelay := totalWeight / float64(len(history)-1)
|
averageDelay := totalDelay / totalWeight
|
||||||
// calculate the difference between the completed date and the due date
|
nextDueDate := completedDate.UTC().Add(time.Duration(averageDelay) * time.Second)
|
||||||
nextDueDate = completedDate.UTC().Add(time.Duration(averageDelay) * time.Second)
|
|
||||||
|
|
||||||
return &nextDueDate, nil
|
return &nextDueDate, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func RemoveAssigneeAndReassign(chore *chModel.Chore, userID int) {
|
func RemoveAssigneeAndReassign(chore *chModel.Chore, userID int) {
|
||||||
for i, assignee := range chore.Assignees {
|
for i, assignee := range chore.Assignees {
|
||||||
if assignee.UserID == userID {
|
if assignee.UserID == userID {
|
||||||
|
|
229
internal/chore/scheduler_test.go
Normal file
229
internal/chore/scheduler_test.go
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
package chore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
chModel "donetick.com/core/internal/chore/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestScheduleNextDueDateBasic(t *testing.T) {
|
||||||
|
choreTime := time.Now()
|
||||||
|
freqencyMetadataBytes := `{"time":"2024-07-07T14:30:00-04:00"}`
|
||||||
|
|
||||||
|
testTable := []struct {
|
||||||
|
chore *chModel.Chore
|
||||||
|
expected time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
FrequencyType: chModel.FrequancyTypeDaily,
|
||||||
|
NextDueDate: &choreTime,
|
||||||
|
FrequencyMetadata: &freqencyMetadataBytes,
|
||||||
|
},
|
||||||
|
expected: choreTime.AddDate(0, 0, 1),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
FrequencyType: chModel.FrequancyTypeWeekly,
|
||||||
|
NextDueDate: &choreTime,
|
||||||
|
FrequencyMetadata: &freqencyMetadataBytes,
|
||||||
|
},
|
||||||
|
expected: choreTime.AddDate(0, 0, 7),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
FrequencyType: chModel.FrequancyTypeMonthly,
|
||||||
|
NextDueDate: &choreTime,
|
||||||
|
FrequencyMetadata: &freqencyMetadataBytes,
|
||||||
|
},
|
||||||
|
expected: choreTime.AddDate(0, 1, 0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
FrequencyType: chModel.FrequancyTypeYearly,
|
||||||
|
NextDueDate: &choreTime,
|
||||||
|
FrequencyMetadata: &freqencyMetadataBytes,
|
||||||
|
},
|
||||||
|
expected: choreTime.AddDate(1, 0, 0),
|
||||||
|
},
|
||||||
|
|
||||||
|
//
|
||||||
|
}
|
||||||
|
for _, tt := range testTable {
|
||||||
|
t.Run(string(tt.chore.FrequencyType), func(t *testing.T) {
|
||||||
|
|
||||||
|
actual, err := scheduleNextDueDate(tt.chore, choreTime)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error: %v", err)
|
||||||
|
}
|
||||||
|
if actual != nil && actual.UTC().Format(time.RFC3339) != tt.expected.UTC().Format(time.RFC3339) {
|
||||||
|
t.Errorf("Expected: %v, Actual: %v", tt.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestScheduleNextDueDateDayOfTheWeek(t *testing.T) {
|
||||||
|
choreTime := time.Now()
|
||||||
|
|
||||||
|
Monday := "monday"
|
||||||
|
Wednesday := "wednesday"
|
||||||
|
|
||||||
|
timeOfChore := "2024-07-07T16:30:00-04:00"
|
||||||
|
getExpectedTime := func(choreTime time.Time, timeOfChore string) time.Time {
|
||||||
|
t, err := time.Parse(time.RFC3339, timeOfChore)
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
return time.Date(choreTime.Year(), choreTime.Month(), choreTime.Day(), t.Hour(), t.Minute(), 0, 0, t.Location())
|
||||||
|
}
|
||||||
|
nextSaturday := choreTime.AddDate(0, 0, 1)
|
||||||
|
for nextSaturday.Weekday() != time.Saturday {
|
||||||
|
nextSaturday = nextSaturday.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextMonday := choreTime.AddDate(0, 0, 1)
|
||||||
|
for nextMonday.Weekday() != time.Monday {
|
||||||
|
nextMonday = nextMonday.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextTuesday := choreTime.AddDate(0, 0, 1)
|
||||||
|
for nextTuesday.Weekday() != time.Tuesday {
|
||||||
|
nextTuesday = nextTuesday.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextWednesday := choreTime.AddDate(0, 0, 1)
|
||||||
|
for nextWednesday.Weekday() != time.Wednesday {
|
||||||
|
nextWednesday = nextWednesday.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextThursday := choreTime.AddDate(0, 0, 1)
|
||||||
|
for nextThursday.Weekday() != time.Thursday {
|
||||||
|
nextThursday = nextThursday.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
testTable := []struct {
|
||||||
|
chore *chModel.Chore
|
||||||
|
frequencyMetadata *chModel.FrequencyMetadata
|
||||||
|
expected time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
FrequencyType: chModel.FrequancyTypeDayOfTheWeek,
|
||||||
|
NextDueDate: &nextSaturday,
|
||||||
|
},
|
||||||
|
frequencyMetadata: &chModel.FrequencyMetadata{
|
||||||
|
Time: timeOfChore,
|
||||||
|
Days: []*string{&Monday, &Wednesday},
|
||||||
|
},
|
||||||
|
|
||||||
|
expected: getExpectedTime(nextMonday, timeOfChore),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
FrequencyType: chModel.FrequancyTypeDayOfTheWeek,
|
||||||
|
NextDueDate: &nextMonday,
|
||||||
|
},
|
||||||
|
frequencyMetadata: &chModel.FrequencyMetadata{
|
||||||
|
Time: timeOfChore,
|
||||||
|
Days: []*string{&Monday, &Wednesday},
|
||||||
|
},
|
||||||
|
expected: getExpectedTime(nextWednesday, timeOfChore),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testTable {
|
||||||
|
t.Run(string(tt.chore.FrequencyType), func(t *testing.T) {
|
||||||
|
bytesFrequencyMetadata, err := json.Marshal(tt.frequencyMetadata)
|
||||||
|
stringFrequencyMetadata := string(bytesFrequencyMetadata)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error: %v", err)
|
||||||
|
}
|
||||||
|
tt.chore.FrequencyMetadata = &stringFrequencyMetadata
|
||||||
|
actual, err := scheduleNextDueDate(tt.chore, choreTime)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error: %v", err)
|
||||||
|
}
|
||||||
|
if actual != nil && actual.UTC().Format(time.RFC3339) != tt.expected.UTC().Format(time.RFC3339) {
|
||||||
|
t.Errorf("Expected: %v, Actual: %v", tt.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestScheduleAdaptiveNextDueDate(t *testing.T) {
|
||||||
|
getTimeFromDate := func(timeOfChore string) *time.Time {
|
||||||
|
t, err := time.Parse(time.RFC3339, timeOfChore)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
testTable := []struct {
|
||||||
|
description string
|
||||||
|
history []*chModel.ChoreHistory
|
||||||
|
chore *chModel.Chore
|
||||||
|
expected *time.Time
|
||||||
|
completeDate *time.Time
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "Every Two days",
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
NextDueDate: getTimeFromDate("2024-07-13T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
history: []*chModel.ChoreHistory{
|
||||||
|
{
|
||||||
|
CompletedAt: getTimeFromDate("2024-07-11T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// CompletedAt: getTimeFromDate("2024-07-09T01:30:00-00:00"),
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// CompletedAt: getTimeFromDate("2024-07-07T01:30:00-00:00"),
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
expected: getTimeFromDate("2024-07-15T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Every 8 days",
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
NextDueDate: getTimeFromDate("2024-07-13T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
history: []*chModel.ChoreHistory{
|
||||||
|
{
|
||||||
|
CompletedAt: getTimeFromDate("2024-07-05T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
CompletedAt: getTimeFromDate("2024-06-27T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: getTimeFromDate("2024-07-21T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "40 days with limit Data",
|
||||||
|
chore: &chModel.Chore{
|
||||||
|
NextDueDate: getTimeFromDate("2024-07-13T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
history: []*chModel.ChoreHistory{
|
||||||
|
{CompletedAt: getTimeFromDate("2024-06-03T01:30:00-00:00")},
|
||||||
|
},
|
||||||
|
expected: getTimeFromDate("2024-08-22T01:30:00-00:00"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range testTable {
|
||||||
|
t.Run(tt.description, func(t *testing.T) {
|
||||||
|
expectedNextDueDate := tt.expected
|
||||||
|
|
||||||
|
actualNextDueDate, err := scheduleAdaptiveNextDueDate(tt.chore, *tt.chore.NextDueDate, tt.history)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if actualNextDueDate == nil || !actualNextDueDate.Equal(*expectedNextDueDate) {
|
||||||
|
t.Errorf("Expected: %v, Actual: %v", expectedNextDueDate, actualNextDueDate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -486,6 +486,32 @@ func (h *Handler) DeleteUserToken(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{})
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *Handler) UpdateNotificationTarget(c *gin.Context) {
|
||||||
|
currentUser, ok := auth.CurrentUser(c)
|
||||||
|
if !ok {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get current user"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type Request struct {
|
||||||
|
Type uModel.UserNotificationType `json:"type" binding:"required"`
|
||||||
|
Token string `json:"token" binding:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var req Request
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.userRepo.UpdateNotificationTarget(c, currentUser.ID, req.Token, req.Type)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update notification target"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{})
|
||||||
|
}
|
||||||
func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware, limiter *limiter.Limiter) {
|
func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware, limiter *limiter.Limiter) {
|
||||||
|
|
||||||
userRoutes := router.Group("users")
|
userRoutes := router.Group("users")
|
||||||
|
@ -497,6 +523,7 @@ func Routes(router *gin.Engine, h *Handler, auth *jwt.GinJWTMiddleware, limiter
|
||||||
userRoutes.POST("/tokens", h.CreateLongLivedToken)
|
userRoutes.POST("/tokens", h.CreateLongLivedToken)
|
||||||
userRoutes.GET("/tokens", h.GetAllUserToken)
|
userRoutes.GET("/tokens", h.GetAllUserToken)
|
||||||
userRoutes.DELETE("/tokens/:id", h.DeleteUserToken)
|
userRoutes.DELETE("/tokens/:id", h.DeleteUserToken)
|
||||||
|
userRoutes.PUT("/targets", h.UpdateNotificationTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
authRoutes := router.Group("auth")
|
authRoutes := router.Group("auth")
|
||||||
|
|
|
@ -16,9 +16,10 @@ type User struct {
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` // Updated at
|
UpdatedAt time.Time `json:"updated_at" gorm:"column:updated_at"` // Updated at
|
||||||
Disabled bool `json:"disabled" gorm:"column:disabled"` // Disabled
|
Disabled bool `json:"disabled" gorm:"column:disabled"` // Disabled
|
||||||
// Email string `json:"email" gorm:"column:email"` // Email
|
// Email string `json:"email" gorm:"column:email"` // Email
|
||||||
CustomerID *string `gorm:"column:customer_id;<-:false"` // read one column
|
CustomerID *string `gorm:"column:customer_id;<-:false"` // read only column
|
||||||
Subscription *string `json:"subscription" gorm:"column:subscription;<-:false"` // read one column
|
Subscription *string `json:"subscription" gorm:"column:subscription;<-:false"` // read only column
|
||||||
Expiration *string `json:"expiration" gorm:"column:expiration;<-:false"` // read one column
|
Expiration *string `json:"expiration" gorm:"column:expiration;<-:false"` // read only column
|
||||||
|
UserNotificationTargets []UserNotificationTarget `json:"-" gorm:"foreignKey:UserID;references:ID"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserPasswordReset struct {
|
type UserPasswordReset struct {
|
||||||
|
@ -36,3 +37,20 @@ type APIToken struct {
|
||||||
Token string `json:"token" gorm:"column:token;index"` // Index on token
|
Token string `json:"token" gorm:"column:token;index"` // Index on token
|
||||||
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"`
|
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserNotificationTarget struct {
|
||||||
|
ID int `json:"id" gorm:"primary_key"` // Unique identifier
|
||||||
|
UserID int `json:"userId" gorm:"column:user_id;index"` // Index on userID
|
||||||
|
Type UserNotificationType `json:"type" gorm:"column:type"` // Type
|
||||||
|
TargetID string `json:"targetId" gorm:"column:target_id"` // Target ID
|
||||||
|
CreatedAt time.Time `json:"createdAt" gorm:"column:created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserNotificationType int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
_ UserNotificationType = iota
|
||||||
|
Android
|
||||||
|
IOS
|
||||||
|
Telegram
|
||||||
|
)
|
||||||
|
|
|
@ -158,3 +158,7 @@ func (r *UserRepository) GetAllUserTokens(c context.Context, userID int) ([]*uMo
|
||||||
func (r *UserRepository) DeleteAPIToken(c context.Context, userID int, tokenID string) error {
|
func (r *UserRepository) DeleteAPIToken(c context.Context, userID int, tokenID string) error {
|
||||||
return r.db.WithContext(c).Where("id = ? AND user_id = ?", tokenID, userID).Delete(&uModel.APIToken{}).Error
|
return r.db.WithContext(c).Where("id = ? AND user_id = ?", tokenID, userID).Delete(&uModel.APIToken{}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *UserRepository) UpdateNotificationTarget(c context.Context, userID int, targetID string, targetType uModel.UserNotificationType) error {
|
||||||
|
return r.db.WithContext(c).Model(&uModel.UserNotificationTarget{}).Where("user_id = ? AND type = ?", userID, targetType).Update("target_id", targetID).Error
|
||||||
|
}
|
||||||
|
|
3
main.go
3
main.go
|
@ -114,7 +114,8 @@ func newServer(lc fx.Lifecycle, cfg *config.Config, db *gorm.DB, notifier *notif
|
||||||
}
|
}
|
||||||
config := cors.DefaultConfig()
|
config := cors.DefaultConfig()
|
||||||
if cfg.IsDoneTickDotCom {
|
if cfg.IsDoneTickDotCom {
|
||||||
config.AllowOrigins = cfg.Server.CorsAllowOrigins
|
// config.AllowOrigins = cfg.Server.CorsAllowOrigins
|
||||||
|
config.AllowAllOrigins = true
|
||||||
} else {
|
} else {
|
||||||
config.AllowAllOrigins = true
|
config.AllowAllOrigins = true
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ func Migration(db *gorm.DB) error {
|
||||||
tModel.ThingChore{},
|
tModel.ThingChore{},
|
||||||
tModel.ThingHistory{},
|
tModel.ThingHistory{},
|
||||||
uModel.APIToken{},
|
uModel.APIToken{},
|
||||||
|
uModel.UserNotificationTarget{},
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue