This repository has been archived by the owner on Jun 4, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
automation.go
332 lines (321 loc) · 13.4 KB
/
automation.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
package api
import (
"encoding/json"
"fmt"
"net/http"
"github.com/MikMuellerDev/smarthome/core/database"
"github.com/MikMuellerDev/smarthome/core/scheduler/automation"
"github.com/MikMuellerDev/smarthome/server/middleware"
)
type NewAutomationRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Hour uint `json:"hour"` // 24 >= h >= 0 | Can only be used with minute, specifies the exact hour in which the automation will run, 0 is midnight, 15 is 3PM, 3 is 3AM -> 24h format
Minute uint `json:"minute"` // 60 >= m >= 0 | Can only be used with hour, specifies the exact minute on which the automation will run
Days []uint8 `json:"days"` // 6 >= d >= 0 | Can contain 7 elements at maximum, value `0` represents Sunday, value `6` represents Saturday
HomescriptId string `json:"homescriptId"`
Enabled bool `json:"enabled"`
TimingMode database.TimingMode `json:"timingMode"`
}
type ModifyAutomationRequest struct {
Id uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Hour uint `json:"hour"`
Minute uint `json:"minute"`
Days []uint8 `json:"days"`
HomescriptId string `json:"homescriptId"`
Enabled bool `json:"enabled"`
TimingMode database.TimingMode `json:"timingMode"`
}
type DeleteAutomationRequest struct {
Id uint `json:"id"`
}
type AutomationActivationRequest struct {
Enabled bool `json:"enabled"`
}
// Returns a list of all automations set up by the current user
func GetUserAutomations(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
username, err := middleware.GetUserFromCurrentSession(w, r)
if err != nil {
return
}
automations, err := automation.GetUserAutomations(username)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to list personal automations", Error: "internal server error"})
return
}
if err := json.NewEncoder(w).Encode(automations); err != nil {
Res(w, Response{Success: false, Message: "failed to list personal automations", Error: "could not encode content"})
}
}
// Creates a new automation
func CreateNewAutomation(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
username, err := middleware.GetUserFromCurrentSession(w, r)
if err != nil {
return
}
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
var request NewAutomationRequest
if err := decoder.Decode(&request); err != nil {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "bad request", Error: "invalid request body"})
return
}
// Check if the provided HomescriptId is valid
_, homescriptValid, err := database.GetUserHomescriptById(request.HomescriptId, username)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "database failure"})
return
}
if !homescriptValid {
w.WriteHeader(http.StatusUnprocessableEntity)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "homescript id is invalid or not found"})
return
}
// Check if the provided hour, minute and days are valid
if len(request.Days) > 7 || len(request.Days) == 0 { // Check if there are more than 7 days or 0
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "length of `days` cannot be greater than 7 or none (0)"})
return
}
// Check for duplicates and if each provided day is valid
containsDays := make([]uint8, 0) // Contains the days, is used to check if there are duplicates in the days
for _, day := range request.Days {
if day > 6 {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "invalid day in `days`: day must be >= 0 and <= 6"})
return
}
dayIsAlreadyPresend := false
for _, dayTemp := range containsDays {
if dayTemp == day {
dayIsAlreadyPresend = true
}
}
if dayIsAlreadyPresend {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "duplicate entries in `days`"})
return
}
containsDays = append(containsDays, day) // If the day is not already present, add it
}
if request.TimingMode != database.TimingNormal && request.TimingMode != database.TimingSunrise && request.TimingMode != database.TimingSunset {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "invalid timing mode"})
return
}
if request.Hour > 24 || request.Minute > 60 { // Checks the minute and hour, values below 0 are checked implicitly through `uint`
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "invalid hour and / or minute"})
return
}
id, err := automation.CreateNewAutomation(
request.Name,
request.Description,
uint8(request.Hour),
uint8(request.Minute),
request.Days,
request.HomescriptId,
username,
request.Enabled,
request.TimingMode,
)
if err != nil {
log.Error(err.Error())
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to create new automation", Error: "backend failure"})
return
}
Res(w, Response{Success: true, Message: fmt.Sprintf("successfully added new automation %d", id)})
}
// Stops, then removes the given automation from the system
func RemoveAutomation(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
username, err := middleware.GetUserFromCurrentSession(w, r)
if err != nil {
return
}
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
var request DeleteAutomationRequest
if err := decoder.Decode(&request); err != nil {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "bad request", Error: "invalid request body"})
return
}
_, doesExists, err := automation.GetUserAutomationById(username, request.Id) // Checks if the automation exists and if the user is allowed to delete it
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to delete automation", Error: "backend failure"})
return
}
if !doesExists {
w.WriteHeader(http.StatusUnprocessableEntity)
Res(w, Response{Success: false, Message: "failed to delete automation", Error: "invalid id / not found"})
return
}
if err := automation.RemoveAutomation(request.Id); err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to delete automation", Error: "backend failure"})
return
}
Res(w, Response{Success: true, Message: "successfully deleted automation"})
}
// Modifies a existing automation, also restarts the schedule
func ModifyAutomation(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
username, err := middleware.GetUserFromCurrentSession(w, r)
if err != nil {
return
}
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
var request ModifyAutomationRequest
if err := decoder.Decode(&request); err != nil {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "bad request", Error: "invalid request body"})
return
}
// Check if the requested automation is valid
_, automationValid, err := automation.GetUserAutomationById(username, request.Id)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "database failure"})
return
}
if !automationValid {
w.WriteHeader(http.StatusUnprocessableEntity)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "automation id is invalid or not found"})
return
}
// Check if the provided HomescriptId is valid
_, homescriptValid, err := database.GetUserHomescriptById(request.HomescriptId, username)
if err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "database failure"})
return
}
if !homescriptValid {
w.WriteHeader(http.StatusUnprocessableEntity)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "homescript id is invalid or not found"})
return
}
// Check if the provided hour, minute and days are valid
if len(request.Days) > 7 || len(request.Days) == 0 { // Check if there are more than 7 days or 0
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "length of `days` cannot be greater than 7 or none (0)"})
return
}
// Check for duplicates and if each provided day is valid
containsDays := make([]uint8, 0) // Contains the days, is used to check if there are duplicates in the days
for _, day := range request.Days {
if day > 6 {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "invalid day in `days`: day must be >= 0 and <= 6"})
return
}
dayIsAlreadyPresend := false
for _, dayTemp := range containsDays {
if dayTemp == day {
dayIsAlreadyPresend = true
}
}
if dayIsAlreadyPresend {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "duplicate entries in `days`"})
return
}
containsDays = append(containsDays, day) // If the day is not already present, add it
}
if request.Hour > 24 || request.Minute > 60 { // Checks the minute and hour, values below 0 are checked implicitly through `uint`
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "invalid hour and / or minute"})
return
}
cronExpr, err := automation.GenerateCronExpression(
uint8(request.Hour),
uint8(request.Minute),
request.Days,
)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "could not create cron expression"})
return
}
newAutomation := database.AutomationWithoutIdAndUsername{
Name: request.Name,
Description: request.Description,
CronExpression: cronExpr,
HomescriptId: request.HomescriptId,
Enabled: request.Enabled,
TimingMode: request.TimingMode,
}
if err := automation.ModifyAutomationById(request.Id, newAutomation); err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to modify automation", Error: "internal server error"})
return
}
Res(w, Response{Success: true, Message: "successfully modified automation"})
}
// Activate or deactivate the entire automation system
func ChangeActivationAutomation(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
var request AutomationActivationRequest
if err := decoder.Decode(&request); err != nil {
w.WriteHeader(http.StatusBadRequest)
Res(w, Response{Success: false, Message: "bad request", Error: "invalid request body"})
return
}
serverConfig, found, err := database.GetServerConfiguration()
if err != nil || !found {
w.WriteHeader(http.StatusServiceUnavailable)
Res(w, Response{Success: false, Message: "failed to change activation state of automations", Error: "database failure"})
return
}
if serverConfig.AutomationEnabled == request.Enabled {
w.WriteHeader(http.StatusUnprocessableEntity)
Res(w, Response{Success: false, Message: "failed to change activation state of automations", Error: fmt.Sprintf("current activation mode of automation is already set to %t", serverConfig.AutomationEnabled)})
return
}
if request.Enabled {
if err := automation.ActivateAutomationSystem(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to activate automations", Error: "internal server error"})
return
}
} else {
if err := automation.DeactivateAutomationSystem(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to deactivate automations", Error: "internal server error"})
return
}
}
if err := database.SetAutomationSystemActivation(request.Enabled); err != nil {
// If this fails, rollback the change
if !request.Enabled {
if err := automation.ActivateAutomationSystem(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to activate automations", Error: "internal server error"})
return
}
} else {
if err := automation.DeactivateAutomationSystem(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to deactivate automations", Error: "internal server error"})
return
}
}
w.WriteHeader(http.StatusInternalServerError)
Res(w, Response{Success: false, Message: "failed to (de) activate automations but managed to rollback", Error: "database failure"})
return
}
Res(w, Response{Success: true, Message: fmt.Sprintf("successfully set activation mode of automations to %t", request.Enabled)})
}