Skip to content

Commit 2ac68f4

Browse files
feat: Adds currentValue endpoints and store
1 parent 4c51181 commit 2ac68f4

File tree

5 files changed

+150
-0
lines changed

5 files changed

+150
-0
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+
}

server.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func NewServer(serverConfig ServerConfig) (*http.ServeMux, error) {
5858
tokenAuth.HandleFunc("GET /recs/{pointId}/history", hisController.getHis)
5959
tokenAuth.HandleFunc("POST /recs/{pointId}/history", hisController.postHis)
6060
tokenAuth.HandleFunc("DELETE /recs/{pointId}/history", hisController.getHis)
61+
62+
currentController := currentController{store: newInMemoryCurrentStore()}
63+
tokenAuth.HandleFunc("GET /recs/{pointId}/current", currentController.getCurrent)
64+
tokenAuth.HandleFunc("POST /recs/{pointId}/current", currentController.postCurrent)
6165
server.Handle("/recs/", tokenAuthMiddleware(serverConfig.jwtSecret, tokenAuth))
6266

6367
// Catch all others with public files. Not found fallback is app index for browser router.

server_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,41 @@ func (suite *ServerTestSuite) TestDeleteRec() {
456456
assert.Equal(suite.T(), recCount, int64(0))
457457
}
458458

459+
func (suite *ServerTestSuite) TestCurrent() {
460+
id, _ := uuid.Parse("1b4e32c7-61b5-4b38-a1cd-023c25f9965c")
461+
recs := []rec{
462+
{
463+
ID: id,
464+
Dis: s("rec"),
465+
Tags: datatypes.JSON([]byte(`{"tag":"value"}`)),
466+
Unit: s("kW"),
467+
},
468+
}
469+
suite.db.Create(&recs)
470+
471+
value := 123.456
472+
currentInput := apiCurrentInput{
473+
Value: &value,
474+
}
475+
body, err := json.Marshal(currentInput)
476+
assert.Nil(suite.T(), err)
477+
setRequest, _ := http.NewRequest(http.MethodPost, fmt.Sprintf("/recs/%s/current", id), bytes.NewReader(body))
478+
setRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.getAuthToken()))
479+
setResponse := httptest.NewRecorder()
480+
suite.server.ServeHTTP(setResponse, setRequest)
481+
assert.Equal(suite.T(), setResponse.Code, http.StatusOK)
482+
483+
getRequest, _ := http.NewRequest(http.MethodGet, fmt.Sprintf("/recs/%s/current", id), nil)
484+
getRequest.Header.Add("Authorization", fmt.Sprintf("Bearer %s", suite.getAuthToken()))
485+
getResponse := httptest.NewRecorder()
486+
suite.server.ServeHTTP(getResponse, getRequest)
487+
assert.Equal(suite.T(), getResponse.Code, http.StatusOK)
488+
decoder := json.NewDecoder(getResponse.Result().Body)
489+
var apiCurrent apiCurrent
490+
assert.Nil(suite.T(), decoder.Decode(&apiCurrent))
491+
assert.Equal(suite.T(), 123.456, *apiCurrent.Value)
492+
}
493+
459494
// These functions just take literals and return a pointer to them. For easier DB/JSON construction
460495
func s(s string) *string { return &s }
461496
func f(f float64) *float64 { return &f }

0 commit comments

Comments
 (0)