Skip to content

Commit 93ac204

Browse files
authored
feat: add recurrence to goals (#1163)
1 parent de86f5d commit 93ac204

File tree

7 files changed

+116
-0
lines changed

7 files changed

+116
-0
lines changed

api/docs.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1862,6 +1862,24 @@ const docTemplate = `{
18621862
"description": "Maximum number of goal to return. Defaults to 50.",
18631863
"name": "limit",
18641864
"in": "query"
1865+
},
1866+
{
1867+
"type": "integer",
1868+
"description": "Period is exactly this",
1869+
"name": "period",
1870+
"in": "query"
1871+
},
1872+
{
1873+
"type": "integer",
1874+
"description": "Period is less or equal to this. Non-recurring goals are not returned.",
1875+
"name": "periodLessOrEqual",
1876+
"in": "query"
1877+
},
1878+
{
1879+
"type": "integer",
1880+
"description": "Period is more or equal to this. Non-recurring goals are not returned.",
1881+
"name": "periodMoreOrEqual",
1882+
"in": "query"
18651883
}
18661884
],
18671885
"responses": {
@@ -4220,6 +4238,12 @@ const docTemplate = `{
42204238
"type": "string",
42214239
"example": "We want to replace the old CRT TV soon-ish"
42224240
},
4241+
"period": {
4242+
"description": "The period in months for when this goal should repeat. \"0\" means no repetition",
4243+
"type": "integer",
4244+
"default": 0,
4245+
"example": 6
4246+
},
42234247
"updatedAt": {
42244248
"description": "Last time the resource was updated",
42254249
"type": "string",
@@ -4281,6 +4305,12 @@ const docTemplate = `{
42814305
"description": "Note about the goal",
42824306
"type": "string",
42834307
"example": "We want to replace the old CRT TV soon-ish"
4308+
},
4309+
"period": {
4310+
"description": "The period in months for when this goal should repeat. \"0\" means no repetition",
4311+
"type": "integer",
4312+
"default": 0,
4313+
"example": 6
42844314
}
42854315
}
42864316
},

api/swagger.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,24 @@
18511851
"description": "Maximum number of goal to return. Defaults to 50.",
18521852
"name": "limit",
18531853
"in": "query"
1854+
},
1855+
{
1856+
"type": "integer",
1857+
"description": "Period is exactly this",
1858+
"name": "period",
1859+
"in": "query"
1860+
},
1861+
{
1862+
"type": "integer",
1863+
"description": "Period is less or equal to this. Non-recurring goals are not returned.",
1864+
"name": "periodLessOrEqual",
1865+
"in": "query"
1866+
},
1867+
{
1868+
"type": "integer",
1869+
"description": "Period is more or equal to this. Non-recurring goals are not returned.",
1870+
"name": "periodMoreOrEqual",
1871+
"in": "query"
18541872
}
18551873
],
18561874
"responses": {
@@ -4209,6 +4227,12 @@
42094227
"type": "string",
42104228
"example": "We want to replace the old CRT TV soon-ish"
42114229
},
4230+
"period": {
4231+
"description": "The period in months for when this goal should repeat. \"0\" means no repetition",
4232+
"type": "integer",
4233+
"default": 0,
4234+
"example": 6
4235+
},
42124236
"updatedAt": {
42134237
"description": "Last time the resource was updated",
42144238
"type": "string",
@@ -4270,6 +4294,12 @@
42704294
"description": "Note about the goal",
42714295
"type": "string",
42724296
"example": "We want to replace the old CRT TV soon-ish"
4297+
},
4298+
"period": {
4299+
"description": "The period in months for when this goal should repeat. \"0\" means no repetition",
4300+
"type": "integer",
4301+
"default": 0,
4302+
"example": 6
42734303
}
42744304
}
42754305
},

api/swagger.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,12 @@ definitions:
740740
description: Note about the goal
741741
example: We want to replace the old CRT TV soon-ish
742742
type: string
743+
period:
744+
default: 0
745+
description: The period in months for when this goal should repeat. "0" means
746+
no repetition
747+
example: 6
748+
type: integer
743749
updatedAt:
744750
description: Last time the resource was updated
745751
example: "2022-04-17T20:14:01.048145Z"
@@ -788,6 +794,12 @@ definitions:
788794
description: Note about the goal
789795
example: We want to replace the old CRT TV soon-ish
790796
type: string
797+
period:
798+
default: 0
799+
description: The period in months for when this goal should repeat. "0" means
800+
no repetition
801+
example: 6
802+
type: integer
791803
type: object
792804
v4.GoalLinks:
793805
properties:
@@ -2623,6 +2635,20 @@ paths:
26232635
in: query
26242636
name: limit
26252637
type: integer
2638+
- description: Period is exactly this
2639+
in: query
2640+
name: period
2641+
type: integer
2642+
- description: Period is less or equal to this. Non-recurring goals are not
2643+
returned.
2644+
in: query
2645+
name: periodLessOrEqual
2646+
type: integer
2647+
- description: Period is more or equal to this. Non-recurring goals are not
2648+
returned.
2649+
in: query
2650+
name: periodMoreOrEqual
2651+
type: integer
26262652
produces:
26272653
- application/json
26282654
responses:

internal/controllers/v4/goal.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ func CreateGoals(c *gin.Context) {
111111
// @Param amountMoreOrEqual query string false "Amount more than or equal to this"
112112
// @Param offset query uint false "The offset of the first goal returned. Defaults to 0."
113113
// @Param limit query int false "Maximum number of goal to return. Defaults to 50."
114+
// @Param period query uint false "Period is exactly this"
115+
// @Param periodLessOrEqual query uint false "Period is less or equal to this. Non-recurring goals are not returned."
116+
// @Param periodMoreOrEqual query uint false "Period is more or equal to this. Non-recurring goals are not returned."
114117
func GetGoals(c *gin.Context) {
115118
var filter GoalQueryFilter
116119

@@ -198,6 +201,20 @@ func GetGoals(c *gin.Context) {
198201
Where("budgets.id = ?", filter.BudgetID.UUID)
199202
}
200203

204+
if filter.PeriodLessOrEqual != 0 {
205+
q = q.
206+
Where("goals.period <= ?", filter.PeriodLessOrEqual).
207+
// Explicitly filter non-recurring goals
208+
Where("goals.period != 0")
209+
}
210+
211+
if filter.PeriodMoreOrEqual != 0 {
212+
q = q.
213+
Where("goals.period >= ?", filter.PeriodMoreOrEqual).
214+
// Explicitly filter non-recurring goals
215+
Where("goals.period != 0")
216+
}
217+
201218
var goals []models.Goal
202219
err = q.Find(&goals).Error
203220
if err != nil {

internal/controllers/v4/goal_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ func (suite *TestSuiteStandard) TestGoalsGetFilter() {
171171
Amount: decimal.NewFromFloat(100),
172172
Month: types.NewMonth(2024, 1),
173173
Archived: false,
174+
Period: 3,
174175
})
175176

176177
_ = createTestGoal(suite.T(), v4.GoalEditable{
@@ -179,6 +180,7 @@ func (suite *TestSuiteStandard) TestGoalsGetFilter() {
179180
Amount: decimal.NewFromFloat(200),
180181
Month: types.NewMonth(2024, 2),
181182
Archived: true,
183+
Period: 12,
182184
})
183185

184186
_ = createTestGoal(suite.T(), v4.GoalEditable{
@@ -228,6 +230,9 @@ func (suite *TestSuiteStandard) TestGoalsGetFilter() {
228230
{"Offset positive", "offset=2", 1},
229231
{"Offset zero", "offset=0", 3},
230232
{"Same month", fmt.Sprintf("month=%s", types.NewMonth(2024, 1)), 2},
233+
{"Period Exact", "period=12", 1},
234+
{"Period more or equal", "periodMoreOrEqual=3", 2},
235+
{"Period less or equal", "periodLessOrEqual=3", 1},
231236
}
232237

233238
for _, tt := range tests {

internal/controllers/v4/goal_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type GoalEditable struct {
1818
Amount decimal.Decimal `json:"amount" example:"750" minimum:"0.00000001" maximum:"999999999999.99999999" multipleOf:"0.00000001" default:"0"` // How much money should be saved for this goal?
1919
Month types.Month `json:"month" example:"2024-07-01T00:00:00.000000Z"` // The month the goal should be reached
2020
Archived bool `json:"archived" example:"true" default:"false"` // If this goal is still in use or not
21+
Period uint `json:"period" example:"6" default:"0"` // The period in months for when this goal should repeat. "0" means no repetition
2122
}
2223

2324
// model returns the database resource for the API representation of the editable fields
@@ -29,6 +30,7 @@ func (editable GoalEditable) model() models.Goal {
2930
Amount: editable.Amount,
3031
Month: editable.Month,
3132
Archived: editable.Archived,
33+
Period: editable.Period,
3234
}
3335
}
3436

@@ -56,6 +58,7 @@ func newGoal(c *gin.Context, model models.Goal) Goal {
5658
Amount: model.Amount,
5759
Month: model.Month,
5860
Archived: model.Archived,
61+
Period: model.Period,
5962
},
6063
Links: GoalLinks{
6164
Self: fmt.Sprintf("%s/v4/goals/%s", url, model.ID),
@@ -107,6 +110,9 @@ type GoalQueryFilter struct {
107110
Amount decimal.Decimal `form:"amount"` // Exact amount
108111
AmountLessOrEqual decimal.Decimal `form:"amountLessOrEqual" filterField:"false"` // Amount less than or equal to this
109112
AmountMoreOrEqual decimal.Decimal `form:"amountMoreOrEqual" filterField:"false"` // Amount more than or equal to this
113+
Period uint `form:"period"` // Period is exactly this
114+
PeriodLessOrEqual uint `form:"periodLessOrEqual" filterField:"false"` // Period is less or equal to this. Non-recurring goals are not returned.
115+
PeriodMoreOrEqual uint `form:"periodMoreOrEqual" filterField:"false"` // Period is more or equal to this. Non-recurring goals are not returned.
110116
Offset uint `form:"offset" filterField:"false"` // The offset of the first goal returned. Defaults to 0.
111117
Limit int `form:"limit" filterField:"false"` // Maximum number of goals to return. Defaults to 50.
112118
}
@@ -129,5 +135,6 @@ func (f GoalQueryFilter) model() (models.Goal, error) {
129135
Amount: f.Amount,
130136
Month: month,
131137
Archived: f.Archived,
138+
Period: f.Period,
132139
}.model(), nil
133140
}

internal/models/goal.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Goal struct {
2020
Amount decimal.Decimal `gorm:"type:DECIMAL(20,8)"` // The target for the goal
2121
Month types.Month
2222
Archived bool
23+
Period uint
2324
}
2425

2526
var ErrGoalAmountNotPositive = errors.New("goal amounts must be larger than zero")

0 commit comments

Comments
 (0)