Skip to content

Commit 423ce84

Browse files
Merge branch 'feat/currentValue'
2 parents 29985c5 + 03cca4e commit 423ce84

File tree

9 files changed

+369
-96
lines changed

9 files changed

+369
-96
lines changed

apiModels.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ type apiRec struct {
2525
Unit *string `json:"unit"`
2626
}
2727

28+
type apiCurrentInput struct {
29+
Value *float64 `json:"value"`
30+
}
31+
32+
type apiCurrent struct {
33+
Ts *time.Time `json:"ts"`
34+
Value *float64 `json:"value"`
35+
}
36+
2837
type clientToken struct {
2938
Token string `json:"token"`
3039
}

currentController.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"log"
6+
"net/http"
7+
8+
"github.com/google/uuid"
9+
)
10+
11+
type currentController struct {
12+
store currentStore
13+
}
14+
15+
// GET /recs/:pointId/current
16+
// Note that start and end are in seconds since epoch (1970-01-01T00:00:00Z)
17+
func (h currentController) getCurrent(w http.ResponseWriter, request *http.Request) {
18+
pointIdString := request.PathValue("pointId")
19+
pointId, err := uuid.Parse(pointIdString)
20+
if err != nil {
21+
log.Printf("Invalid UUID: %s", pointIdString)
22+
w.WriteHeader(http.StatusNotFound)
23+
return
24+
}
25+
err = request.ParseForm()
26+
if err != nil {
27+
log.Printf("Cannot parse form: %s", err)
28+
w.WriteHeader(http.StatusBadRequest)
29+
return
30+
}
31+
32+
httpResult := h.store.getCurrent(pointId)
33+
34+
httpJson, err := json.Marshal(httpResult)
35+
if err != nil {
36+
log.Printf("Cannot encode response JSON")
37+
w.WriteHeader(http.StatusBadRequest)
38+
return
39+
}
40+
41+
w.WriteHeader(http.StatusOK)
42+
w.Write(httpJson)
43+
}
44+
45+
// POST /recs/:pointId/current
46+
// Note that start and end are in seconds since epoch (1970-01-01T00:00:00Z)
47+
func (h currentController) postCurrent(writer http.ResponseWriter, request *http.Request) {
48+
pointIdString := request.PathValue("pointId")
49+
pointId, err := uuid.Parse(pointIdString)
50+
if err != nil {
51+
log.Printf("Invalid UUID: %s", pointIdString)
52+
writer.WriteHeader(http.StatusNotFound)
53+
return
54+
}
55+
decoder := json.NewDecoder(request.Body)
56+
var currentItem apiCurrentInput
57+
err = decoder.Decode(&currentItem)
58+
if err != nil {
59+
log.Printf("Cannot decode request JSON: %s", err)
60+
writer.WriteHeader(http.StatusBadRequest)
61+
return
62+
}
63+
64+
h.store.setCurrent(pointId, currentItem)
65+
66+
writer.WriteHeader(http.StatusOK)
67+
}

currentStore.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package main
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
// currentStore is able to store point current values
10+
type currentStore interface {
11+
getCurrent(uuid.UUID) apiCurrent
12+
setCurrent(uuid.UUID, apiCurrentInput)
13+
}
14+
15+
// inMemoryCurrentStore stores point current values in a local in-memory cache.
16+
// These are not shared between instances.
17+
type inMemoryCurrentStore struct {
18+
cache map[uuid.UUID]apiCurrent
19+
}
20+
21+
func newInMemoryCurrentStore() inMemoryCurrentStore {
22+
return inMemoryCurrentStore{cache: map[uuid.UUID]apiCurrent{}}
23+
}
24+
25+
func (s inMemoryCurrentStore) getCurrent(id uuid.UUID) apiCurrent {
26+
return s.cache[id]
27+
}
28+
29+
func (s inMemoryCurrentStore) setCurrent(id uuid.UUID, input apiCurrentInput) {
30+
timestamp := time.Now()
31+
s.cache[id] = apiCurrent{
32+
Ts: &timestamp,
33+
Value: input.Value,
34+
}
35+
}

hisController.go

Lines changed: 25 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,13 @@ import (
88
"time"
99

1010
"github.com/google/uuid"
11-
"gorm.io/gorm"
12-
"gorm.io/gorm/clause"
1311
)
1412

1513
type hisController struct {
16-
db *gorm.DB
14+
store historyStore
1715
}
1816

19-
// GET /his/:pointId?start=...&end=...
17+
// GET /recs/:pointId/history?start=...&end=...
2018
// Note that start and end are in seconds since epoch (1970-01-01T00:00:00Z)
2119
func (h hisController) getHis(w http.ResponseWriter, request *http.Request) {
2220
pointIdString := request.PathValue("pointId")
@@ -34,41 +32,38 @@ func (h hisController) getHis(w http.ResponseWriter, request *http.Request) {
3432
}
3533
params := request.Form
3634

37-
var sqlResult []his
38-
query := h.db.Where(&his{PointId: pointId})
3935
// TODO: Change start/end to ISO8601
36+
var start *time.Time
4037
if params["start"] != nil {
4138
startStr := params["start"][0]
42-
start, err := strconv.ParseInt(startStr, 0, 64)
39+
startUnix, err := strconv.ParseInt(startStr, 0, 64)
4340
if err != nil {
4441
log.Printf("Cannot parse time: %s", startStr)
4542
w.WriteHeader(http.StatusBadRequest)
4643
return
4744
}
48-
query.Where("ts >= ?", time.Unix(start, 0))
45+
startTime := time.Unix(startUnix, 0)
46+
start = &startTime
4947
}
48+
var end *time.Time
5049
if params["end"] != nil {
5150
endStr := params["end"][0]
52-
end, err := strconv.ParseInt(endStr, 0, 64)
51+
endUnix, err := strconv.ParseInt(endStr, 0, 64)
5352
if err != nil {
5453
log.Printf("Cannot parse time: %s", endStr)
5554
w.WriteHeader(http.StatusBadRequest)
5655
return
5756
}
58-
query.Where("ts < ?", time.Unix(end, 0))
57+
endTime := time.Unix(endUnix, 0)
58+
end = &endTime
5959
}
60-
err = query.Order("ts asc").Find(&sqlResult).Error
60+
httpResult, err := h.store.readHistory(pointId, start, end)
6161
if err != nil {
62-
log.Printf("SQL Error: %s", err)
62+
log.Printf("Storage Error: %s", err)
6363
w.WriteHeader(http.StatusInternalServerError)
6464
return
6565
}
6666

67-
httpResult := []apiHis{}
68-
for _, sqlRow := range sqlResult {
69-
httpResult = append(httpResult, apiHis(sqlRow))
70-
}
71-
7267
httpJson, err := json.Marshal(httpResult)
7368
if err != nil {
7469
log.Printf("Cannot encode response JSON")
@@ -80,7 +75,7 @@ func (h hisController) getHis(w http.ResponseWriter, request *http.Request) {
8075
w.Write(httpJson)
8176
}
8277

83-
// POST /his/:pointId
78+
// POST /recs/:pointId/history
8479
// Note that start and end are in seconds since epoch (1970-01-01T00:00:00Z)
8580
func (h hisController) postHis(writer http.ResponseWriter, request *http.Request) {
8681
pointIdString := request.PathValue("pointId")
@@ -98,27 +93,16 @@ func (h hisController) postHis(writer http.ResponseWriter, request *http.Request
9893
writer.WriteHeader(http.StatusBadRequest)
9994
return
10095
}
101-
102-
his := his{
103-
PointId: pointId,
104-
Ts: hisItem.Ts,
105-
Value: hisItem.Value,
106-
}
107-
108-
err = h.db.Clauses(clause.OnConflict{
109-
Columns: []clause.Column{{Name: "pointId"}, {Name: "ts"}},
110-
DoUpdates: clause.AssignmentColumns([]string{"value"}),
111-
}).Create(&his).Error
96+
err = h.store.writeHistory(pointId, hisItem)
11297
if err != nil {
113-
log.Printf("SQL Error: %s", err)
98+
log.Printf("Storage Error: %s", err)
11499
writer.WriteHeader(http.StatusInternalServerError)
115100
return
116101
}
117-
118102
writer.WriteHeader(http.StatusOK)
119103
}
120104

121-
// DELETE /his/:pointId?start=...&end=...
105+
// DELETE /recs/:pointId/history?start=...&end=...
122106
// Note that start and end are in seconds since epoch (1970-01-01T00:00:00Z)
123107
func (h hisController) deleteHis(writer http.ResponseWriter, request *http.Request) {
124108
pointIdString := request.PathValue("pointId")
@@ -136,30 +120,32 @@ func (h hisController) deleteHis(writer http.ResponseWriter, request *http.Reque
136120
}
137121
params := request.Form
138122

139-
var sqlResult []his
140-
query := h.db.Where(&his{PointId: pointId})
141123
// TODO: Change start/end to ISO8601
124+
var start *time.Time
142125
if params["start"] != nil {
143126
startStr := params["start"][0]
144-
start, err := strconv.ParseInt(startStr, 0, 64)
127+
startUnix, err := strconv.ParseInt(startStr, 0, 64)
145128
if err != nil {
146129
log.Printf("Cannot parse time: %s", startStr)
147130
writer.WriteHeader(http.StatusBadRequest)
148131
return
149132
}
150-
query.Where("ts >= ?", time.Unix(start, 0))
133+
startTime := time.Unix(startUnix, 0)
134+
start = &startTime
151135
}
136+
var end *time.Time
152137
if params["end"] != nil {
153138
endStr := params["end"][0]
154-
end, err := strconv.ParseInt(endStr, 0, 64)
139+
endUnix, err := strconv.ParseInt(endStr, 0, 64)
155140
if err != nil {
156141
log.Printf("Cannot parse time: %s", endStr)
157142
writer.WriteHeader(http.StatusBadRequest)
158143
return
159144
}
160-
query.Where("ts < ?", time.Unix(end, 0))
145+
endTime := time.Unix(endUnix, 0)
146+
end = &endTime
161147
}
162-
err = query.Delete(&sqlResult).Error
148+
err = h.store.deleteHistory(pointId, start, end)
163149
if err != nil {
164150
log.Printf("SQL Error: %s", err)
165151
writer.WriteHeader(http.StatusInternalServerError)

historyStore.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"time"
5+
6+
"github.com/google/uuid"
7+
"gorm.io/gorm"
8+
"gorm.io/gorm/clause"
9+
)
10+
11+
// historyStore is able to store point historical values
12+
type historyStore interface {
13+
readHistory(uuid.UUID, *time.Time, *time.Time) ([]apiHisItem, error)
14+
writeHistory(uuid.UUID, apiHisItem) error
15+
deleteHistory(uuid.UUID, *time.Time, *time.Time) error
16+
}
17+
18+
// gormHistoryStore stores point historical values in a GORM database.
19+
type gormHistoryStore struct {
20+
db *gorm.DB
21+
}
22+
23+
func newGormHistoryStore(db *gorm.DB) gormHistoryStore {
24+
return gormHistoryStore{db: db}
25+
}
26+
27+
func (s gormHistoryStore) readHistory(
28+
pointId uuid.UUID,
29+
start *time.Time,
30+
end *time.Time,
31+
) ([]apiHisItem, error) {
32+
result := []apiHisItem{}
33+
34+
var sqlResult []his
35+
query := s.db.Where(&his{PointId: pointId})
36+
if start != nil {
37+
query.Where("ts >= ?", start)
38+
}
39+
if end != nil {
40+
query.Where("ts < ?", end)
41+
}
42+
err := query.Order("ts asc").Find(&sqlResult).Error
43+
if err != nil {
44+
return result, err
45+
}
46+
for _, sqlRow := range sqlResult {
47+
result = append(result, apiHisItem{Ts: sqlRow.Ts, Value: sqlRow.Value})
48+
}
49+
return result, nil
50+
}
51+
52+
func (s gormHistoryStore) writeHistory(
53+
pointId uuid.UUID,
54+
hisItem apiHisItem,
55+
) error {
56+
his := his{
57+
PointId: pointId,
58+
Ts: hisItem.Ts,
59+
Value: hisItem.Value,
60+
}
61+
62+
return s.db.Clauses(clause.OnConflict{
63+
Columns: []clause.Column{{Name: "pointId"}, {Name: "ts"}},
64+
DoUpdates: clause.AssignmentColumns([]string{"value"}),
65+
}).Create(&his).Error
66+
}
67+
68+
func (s gormHistoryStore) deleteHistory(
69+
pointId uuid.UUID,
70+
start *time.Time,
71+
end *time.Time,
72+
) error {
73+
var sqlResult []his
74+
query := s.db.Where(&his{PointId: pointId})
75+
if start != nil {
76+
query.Where("ts >= ?", start)
77+
}
78+
if end != nil {
79+
query.Where("ts < ?", end)
80+
}
81+
return query.Delete(&sqlResult).Error
82+
}

0 commit comments

Comments
 (0)