From c56a6789253cb2741db93e01f556a46eb58fcc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Z=C3=ADpek?= Date: Sun, 28 Aug 2022 11:56:03 +0200 Subject: [PATCH] Working on sensors page --- client/package-lock.json | 17 +++- client/package.json | 3 +- client/src/api/sensors.ts | 19 ++++- client/src/assets/components/_button.scss | 31 +++++++ .../src/assets/components/_sensors-page.scss | 71 ++++++++++++++++ client/src/assets/icons/edit.svg | 1 + client/src/assets/icons/eye-off.svg | 1 + client/src/assets/icons/eye.svg | 1 + client/src/assets/style.scss | 30 +++---- client/src/icons.ts | 3 + .../UserLayout/components/UserMenu.tsx | 3 +- client/src/pages/Router.tsx | 14 +++- .../components/BoxSettings/BoxSettings.tsx | 4 +- .../DashboardGrid/components/EditableBox.tsx | 2 +- .../dashboard/components/SensorSettings.tsx | 4 +- client/src/pages/sensors/SensorsPage.tsx | 37 +++++++++ .../pages/sensors/components/SensorItem.tsx | 40 +++++++++ client/src/utils/hooks/useHashLocation.ts | 25 ++++++ .../1661668805489_sensors_table.sql | 7 ++ .../1661668953214_migrate_sensors.sql | 31 +++++++ server/main.go | 6 +- server/middleware/auth.go | 20 ++++- server/routes/sensor_values.go | 35 ++++++-- server/routes/sensors.go | 57 ++++++++++++- server/services/sensor_values_service.go | 12 +-- server/services/sensors_service.go | 82 +++++++++++++++---- 26 files changed, 492 insertions(+), 64 deletions(-) create mode 100644 client/src/assets/components/_button.scss create mode 100644 client/src/assets/components/_sensors-page.scss create mode 100644 client/src/assets/icons/edit.svg create mode 100644 client/src/assets/icons/eye-off.svg create mode 100644 client/src/assets/icons/eye.svg create mode 100644 client/src/pages/sensors/SensorsPage.tsx create mode 100644 client/src/pages/sensors/components/SensorItem.tsx create mode 100644 client/src/utils/hooks/useHashLocation.ts create mode 100644 server/database/migrations/1661668805489_sensors_table.sql create mode 100644 server/database/migrations/1661668953214_migrate_sensors.sql diff --git a/client/package-lock.json b/client/package-lock.json index 322ff61..d59734e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "plotly.js": "^2.14.0", "preact": "^10.10.6", - "react-query": "^3.39.2" + "react-query": "^3.39.2", + "wouter": "^2.8.0-alpha.2" }, "devDependencies": { "@preact/preset-vite": "^2.3.0", @@ -6423,6 +6424,14 @@ "object-assign": "^4.1.0" } }, + "node_modules/wouter": { + "version": "2.8.0-alpha.2", + "resolved": "https://registry.npmjs.org/wouter/-/wouter-2.8.0-alpha.2.tgz", + "integrity": "sha512-aPsL5m5rW9RiceClOmGj6t5gn9Ut2TJVr98UDi1u9MIRNYiYVflg6vFIjdDYJ4IAyH0JdnkSgGwfo0LQS3k2zg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -11300,6 +11309,12 @@ "object-assign": "^4.1.0" } }, + "wouter": { + "version": "2.8.0-alpha.2", + "resolved": "https://registry.npmjs.org/wouter/-/wouter-2.8.0-alpha.2.tgz", + "integrity": "sha512-aPsL5m5rW9RiceClOmGj6t5gn9Ut2TJVr98UDi1u9MIRNYiYVflg6vFIjdDYJ4IAyH0JdnkSgGwfo0LQS3k2zg==", + "requires": {} + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/client/package.json b/client/package.json index f14447b..439a4d5 100644 --- a/client/package.json +++ b/client/package.json @@ -28,6 +28,7 @@ "dependencies": { "plotly.js": "^2.14.0", "preact": "^10.10.6", - "react-query": "^3.39.2" + "react-query": "^3.39.2", + "wouter": "^2.8.0-alpha.2" } } diff --git a/client/src/api/sensors.ts b/client/src/api/sensors.ts index 0b65253..873d335 100644 --- a/client/src/api/sensors.ts +++ b/client/src/api/sensors.ts @@ -1,8 +1,23 @@ import { request } from './request' export type SensorInfo = { - sensor: string - config: Record + id: number + name: string + authKey: string } export const getSensors = () => request('/api/sensors') + +export const createSensor = (name: string) => + request('/api/sensors', { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ name }), + }) + +export const updateSensor = (id: number, name: string) => + request(`/api/sensors/${id}`, { + method: 'PUT', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ name }), + }) diff --git a/client/src/assets/components/_button.scss b/client/src/assets/components/_button.scss new file mode 100644 index 0000000..edcf478 --- /dev/null +++ b/client/src/assets/components/_button.scss @@ -0,0 +1,31 @@ +button { + background: var(--button-bg-color); + color: var(--button-fg-color); + cursor: pointer; + border: none; + padding: 0.25rem 0.8rem; + border-radius: 0.25rem; + display: inline-flex; + align-items: center; + justify-content: center; +} + +button > .svg-icon { + display: flex; + align-items: center; + margin-top: 1px; + margin-right: 0.2rem; +} + +button:hover { + background-color: var(--button-hover-bg-color); + color: var(--button-hover-fg-color); +} + +button.icon { + padding: 0.25rem; +} + +button.icon > .svg-icon { + margin-right: 0; +} \ No newline at end of file diff --git a/client/src/assets/components/_sensors-page.scss b/client/src/assets/components/_sensors-page.scss new file mode 100644 index 0000000..ae276b6 --- /dev/null +++ b/client/src/assets/components/_sensors-page.scss @@ -0,0 +1,71 @@ +.sensors-page { + .sensors-head { + display: flex; + align-items: center; + flex: 1; + + > button { + margin-left: auto; + } + } + + section.content { + padding: 1rem; + + > .box { + max-width: 50rem; + padding: 1rem; + overflow: auto; + } + + .sensors-list { + .sensor-item { + display: flex; + align-items: center; + margin: 0.25rem 0; + + &.head { + opacity: 0.75; + font-size: 85%; + } + + > div { + flex-grow: 0; + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + } + + > .id { + flex: 0.3; + margin-right: 0.5rem; + } + + > .name { + margin-right: 0.5rem; + flex: 1; + } + + > .key { + display: flex; + flex: 1.5; + + button { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + padding: 0.25rem 0.5rem; + } + } + + > .actions { + flex: 2; + text-align: right; + + button { + margin-left: 0.25rem; + } + } + } + } + } +} diff --git a/client/src/assets/icons/edit.svg b/client/src/assets/icons/edit.svg new file mode 100644 index 0000000..0eeefee --- /dev/null +++ b/client/src/assets/icons/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/icons/eye-off.svg b/client/src/assets/icons/eye-off.svg new file mode 100644 index 0000000..3b09a9d --- /dev/null +++ b/client/src/assets/icons/eye-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/icons/eye.svg b/client/src/assets/icons/eye.svg new file mode 100644 index 0000000..dbd7552 --- /dev/null +++ b/client/src/assets/icons/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/src/assets/style.scss b/client/src/assets/style.scss index b46ca28..29edc40 100644 --- a/client/src/assets/style.scss +++ b/client/src/assets/style.scss @@ -1,4 +1,5 @@ :root { + --main-font: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; --main-bg-color: #eee; --main-fg-color: #000; --button-bg-color: #3988ff; @@ -66,30 +67,16 @@ html { body { background: var(--main-bg-color); color: var(--main-fg-color); - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + font-family: var(--main-font); } button, input, select { - font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + font-family: var(--main-font); } -button { - background: var(--button-bg-color); - color: var(--button-fg-color); - cursor: pointer; - border: none; - padding: 0.25rem 1rem; - border-radius: 0.25rem; - display: inline-flex; - align-items: center; -} - -button:hover { - background-color: var(--button-hover-bg-color); - color: var(--button-hover-fg-color); -} +@import 'components/button'; input, select { @@ -197,6 +184,7 @@ header.header > .inner > .menu-button { align-items: center; font-size: 125%; cursor: pointer; + margin-right: 0.75rem; } section.content { @@ -249,7 +237,7 @@ section.content { .settings-modal .inner { background-color: var(--box-bg-color); color: var(--box-fg-color); - margin-top: 5%; + margin-top: 5vh; box-shadow: var(--box-shadow); border-radius: 0.5rem; width: 20rem; @@ -490,6 +478,11 @@ form.horizontal .input label { stroke: currentColor; } +.flex-center { + display: flex; + align-items: center; +} + @keyframes rotate { 0% { transform: rotate(0deg); @@ -501,3 +494,4 @@ form.horizontal .input label { @import "components/dashboard-header"; @import "components/filters-panel"; +@import "components/sensors-page"; diff --git a/client/src/icons.ts b/client/src/icons.ts index 6cd8254..f6383b2 100644 --- a/client/src/icons.ts +++ b/client/src/icons.ts @@ -4,3 +4,6 @@ export { ReactComponent as MenuIcon } from '@/assets/icons/menu.svg' export { ReactComponent as CancelIcon } from '@/assets/icons/cancel.svg' export { ReactComponent as FiltersIcon } from '@/assets/icons/filters.svg' export { ReactComponent as PlusIcon } from '@/assets/icons/plus.svg' +export { ReactComponent as EyeIcon } from '@/assets/icons/eye.svg' +export { ReactComponent as EyeOffIcon } from '@/assets/icons/eye-off.svg' +export { ReactComponent as EditIcon } from '@/assets/icons/edit.svg' diff --git a/client/src/layouts/UserLayout/components/UserMenu.tsx b/client/src/layouts/UserLayout/components/UserMenu.tsx index f0618a6..29c227f 100644 --- a/client/src/layouts/UserLayout/components/UserMenu.tsx +++ b/client/src/layouts/UserLayout/components/UserMenu.tsx @@ -1,4 +1,5 @@ import { CancelIcon } from '@/icons' +import { Link } from 'wouter' type Props = { shown: boolean @@ -16,7 +17,7 @@ export const UserMenu = ({ shown, onHide }: Props) => { diff --git a/client/src/pages/Router.tsx b/client/src/pages/Router.tsx index 0e08c55..ec050fa 100644 --- a/client/src/pages/Router.tsx +++ b/client/src/pages/Router.tsx @@ -1,13 +1,25 @@ import { useAppContext } from '@/contexts/AppContext' +import { useHashLocation } from '@/utils/hooks/useHashLocation' +import { Route, Router as Wouter } from 'wouter' import { NewDashboardPage } from './dashboard/NewDashboardPage' import { LoginPage } from './login/LoginPage' +import { SensorsPage } from './sensors/SensorsPage' export const Router = () => { const { loggedIn } = useAppContext() return ( <> - {loggedIn && } + {loggedIn && ( + + + + + + + + + )} {!loggedIn && } ) diff --git a/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx b/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx index 117b463..b572014 100644 --- a/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx +++ b/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx @@ -69,8 +69,8 @@ export const BoxSettings = ({ value, onSave, onClose, onRemove }: Props) => { onChange={handleChange} > {sensors.data?.map((s) => ( - ))} diff --git a/client/src/pages/dashboard/components/DashboardGrid/components/EditableBox.tsx b/client/src/pages/dashboard/components/DashboardGrid/components/EditableBox.tsx index 6f4b2c4..b69d3b3 100644 --- a/client/src/pages/dashboard/components/DashboardGrid/components/EditableBox.tsx +++ b/client/src/pages/dashboard/components/DashboardGrid/components/EditableBox.tsx @@ -132,7 +132,7 @@ export const EditableBox = ({
-
{box.title ?? box.sensor ?? ''}
+
{box.title || box.sensor || ''}
refreshRef.current?.()}> diff --git a/client/src/pages/dashboard/components/SensorSettings.tsx b/client/src/pages/dashboard/components/SensorSettings.tsx index 9bb3b47..ffcddb9 100644 --- a/client/src/pages/dashboard/components/SensorSettings.tsx +++ b/client/src/pages/dashboard/components/SensorSettings.tsx @@ -25,7 +25,7 @@ export const SensorSettings = ({ sensor, onClose, onUpdate }: Props) => { try { await Promise.all( Object.entries(value).map(([name, value]) => - setSensorConfig({ sensor: sensor.sensor, name, value }) + setSensorConfig({ sensor: sensor.id, name, value }) ) ) } catch (err) { @@ -64,7 +64,7 @@ export const SensorSettings = ({ sensor, onClose, onUpdate }: Props) => {
- +
diff --git a/client/src/pages/sensors/SensorsPage.tsx b/client/src/pages/sensors/SensorsPage.tsx new file mode 100644 index 0000000..a241cb4 --- /dev/null +++ b/client/src/pages/sensors/SensorsPage.tsx @@ -0,0 +1,37 @@ +import { getSensors } from '@/api/sensors' +import { PlusIcon } from '@/icons' +import { UserLayout } from '@/layouts/UserLayout/UserLayout' +import { useQuery } from 'react-query' +import { SensorItem } from './components/SensorItem' + +export const SensorsPage = () => { + const sensors = useQuery(['/sensors'], getSensors) + + return ( + +
Sensors
+ +
+ } + className="sensors-page" + > +
+
+
+
ID
+
Name
+
Key
+
+
+ {sensors.data?.map((i) => ( + + ))} +
+
+ + ) +} diff --git a/client/src/pages/sensors/components/SensorItem.tsx b/client/src/pages/sensors/components/SensorItem.tsx new file mode 100644 index 0000000..cb69b4f --- /dev/null +++ b/client/src/pages/sensors/components/SensorItem.tsx @@ -0,0 +1,40 @@ +import { SensorInfo } from '@/api/sensors' +import { CancelIcon, EditIcon, EyeIcon, RefreshIcon } from '@/icons' +import { useState } from 'preact/hooks' + +type Props = { + sensor: SensorInfo +} + +export const SensorItem = ({ sensor }: Props) => { + const [showPassword, setShowPassword] = useState(false) + + return ( +
+
{sensor.id}
+
{sensor.name}
+
+ + +
+
+ + + +
+
+ ) +} diff --git a/client/src/utils/hooks/useHashLocation.ts b/client/src/utils/hooks/useHashLocation.ts new file mode 100644 index 0000000..b068dda --- /dev/null +++ b/client/src/utils/hooks/useHashLocation.ts @@ -0,0 +1,25 @@ +/** @source wouter example */ +import { useState, useEffect } from 'preact/hooks' + +// (excluding the leading '#' symbol) +const currentLocation = () => { + return window.location.hash.replace(/^#/, '') || '/' +} + +const navigate = (to: string) => (window.location.hash = to) + +export const useHashLocation = () => { + const [loc, setLoc] = useState(currentLocation()) + + useEffect(() => { + // this function is called whenever the hash changes + const handler = () => setLoc(currentLocation()) + + // subscribe to hash changes + window.addEventListener('hashchange', handler) + + return () => window.removeEventListener('hashchange', handler) + }, []) + + return [loc, navigate] +} diff --git a/server/database/migrations/1661668805489_sensors_table.sql b/server/database/migrations/1661668805489_sensors_table.sql new file mode 100644 index 0000000..bd721ae --- /dev/null +++ b/server/database/migrations/1661668805489_sensors_table.sql @@ -0,0 +1,7 @@ +CREATE TABLE IF NOT EXISTS sensors ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + auth_key TEXT NOT NULL, + /* Temporary column used for migration */ + ident TEXT NOT NULL +); diff --git a/server/database/migrations/1661668953214_migrate_sensors.sql b/server/database/migrations/1661668953214_migrate_sensors.sql new file mode 100644 index 0000000..7c52ff6 --- /dev/null +++ b/server/database/migrations/1661668953214_migrate_sensors.sql @@ -0,0 +1,31 @@ +/* Add rows for sensors */ +INSERT INTO sensors (ident, name, auth_key) + SELECT + sensor, + IFNULL( + (SELECT c.value FROM sensor_config c WHERE c.sensor = sensor), + sensor + ) as "name", + hex(randomblob(32)) as "auth_key" + FROM sensor_values + GROUP BY sensor; + +/* We need to add FK key and the only way is to create new table */ +/* So we rename old table, create a new one and migrate the data there */ +ALTER TABLE sensor_values RENAME TO sensor_values_old; + +CREATE TABLE IF NOT EXISTS sensor_values ( + timestamp INTEGER NOT NULL, + sensor_id INTEGER NOT NULL, + value REAL NOT NULL, + FOREIGN KEY (sensor_id) REFERENCES sensors(id) +); + +INSERT INTO sensor_values (timestamp, sensor_id, value) + SELECT timestamp, (SELECT s.id FROM sensors s WHERE s.ident = sensor), value + FROM sensor_values_old; + +DROP TABLE sensor_values_old; + +/* this column was temporary */ +ALTER TABLE sensors DROP COLUMN ident; diff --git a/server/main.go b/server/main.go index c56bb93..d08233a 100644 --- a/server/main.go +++ b/server/main.go @@ -39,10 +39,12 @@ func main() { // Routes that are only accessible after logging in loginProtected := router.Group("/", middleware.LoginAuthMiddleware(server)) loginProtected.GET("/api/sensors", routes.GetSensors(server)) + loginProtected.POST("/api/sensors", routes.PostSensors(server)) + loginProtected.PUT("/api/sensors/:sensor", routes.PutSensors(server)) loginProtected.GET("/api/sensors/:sensor/values/latest", routes.GetSensorLatestValue(server)) loginProtected.GET("/api/sensors/:sensor/values", routes.GetSensorValues(server)) - loginProtected.GET("/api/sensors/:sensor/config", routes.GetSensorConfig(server)) - loginProtected.PUT("/api/sensors/:sensor/config/:key", routes.PutSensorConfig(server)) + //loginProtected.GET("/api/sensors/:sensor/config", routes.GetSensorConfig(server)) + //loginProtected.PUT("/api/sensors/:sensor/config/:key", routes.PutSensorConfig(server)) loginProtected.GET("/api/dashboards", routes.GetDashboards(server)) loginProtected.POST("/api/dashboards", routes.PostDashboard(server)) loginProtected.GET("/api/dashboards/:id", routes.GetDashboardById(server)) diff --git a/server/middleware/auth.go b/server/middleware/auth.go index 3e164e0..818549f 100644 --- a/server/middleware/auth.go +++ b/server/middleware/auth.go @@ -3,6 +3,7 @@ package middleware import ( "basic-sensor-receiver/app" "net/http" + "strconv" "github.com/gin-gonic/gin" ) @@ -22,10 +23,23 @@ func LoginAuthMiddleware(server *app.Server) gin.HandlerFunc { } func KeyAuthMiddleware(server *app.Server) gin.HandlerFunc { - keyWithBearer := "Bearer " + server.Config.AuthKey - return func(c *gin.Context) { - if c.GetHeader("authorization") != keyWithBearer { + sensorParam := c.Param("sensor") + sensorId, err := strconv.ParseInt(sensorParam, 10, 64) + + if err != nil { + c.AbortWithStatus(http.StatusBadRequest) + return + } + + sensor, err := server.Services.Sensors.GetById(sensorId) + + if err != nil { + c.AbortWithStatus(http.StatusNotFound) + return + } + + if c.GetHeader("authorization") != "Bearer "+sensor.AuthKey { c.AbortWithStatus(http.StatusUnauthorized) return diff --git a/server/routes/sensor_values.go b/server/routes/sensor_values.go index 82480d4..81d2fe8 100644 --- a/server/routes/sensor_values.go +++ b/server/routes/sensor_values.go @@ -3,6 +3,7 @@ package routes import ( "basic-sensor-receiver/app" "net/http" + "strconv" "github.com/gin-gonic/gin" ) @@ -23,14 +24,20 @@ type getLatestSensorValueQuery struct { func PostSensorValues(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { var newValue postSensorValueBody - sensor := c.Param("sensor") if err := c.BindJSON(&newValue); err != nil { c.AbortWithError(400, err) return } - if _, err := s.Services.SensorValues.Push(sensor, newValue.Value); err != nil { + sensorId, err := getSensorId(c) + + if err != nil { + c.AbortWithError(400, err) + return + } + + if _, err := s.Services.SensorValues.Push(sensorId, newValue.Value); err != nil { c.AbortWithError(400, err) return } @@ -43,14 +50,19 @@ func GetSensorValues(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { var query getSensorValuesQuery - sensor := c.Param("sensor") + sensorId, err := getSensorId(c) + + if err != nil { + c.AbortWithError(400, err) + return + } if err := c.BindQuery(&query); err != nil { c.AbortWithError(500, err) return } - values, err := s.Services.SensorValues.GetList(sensor, query.From, query.To) + values, err := s.Services.SensorValues.GetList(sensorId, query.From, query.To) if err != nil { c.AbortWithError(500, err) @@ -64,14 +76,19 @@ func GetSensorLatestValue(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { var query getLatestSensorValueQuery - sensor := c.Param("sensor") + sensorId, err := getSensorId(c) + + if err != nil { + c.AbortWithError(400, err) + return + } if err := c.BindQuery(&query); err != nil { c.AbortWithError(500, err) return } - value, err := s.Services.SensorValues.GetLatest(sensor, query.To) + value, err := s.Services.SensorValues.GetLatest(sensorId, query.To) if err != nil { c.AbortWithError(500, err) @@ -81,3 +98,9 @@ func GetSensorLatestValue(s *app.Server) gin.HandlerFunc { c.JSON(http.StatusOK, value) } } + +func getSensorId(c *gin.Context) (int64, error) { + sensor := c.Param("sensor") + + return strconv.ParseInt(sensor, 10, 64) +} diff --git a/server/routes/sensors.go b/server/routes/sensors.go index c849877..ae9fbd4 100644 --- a/server/routes/sensors.go +++ b/server/routes/sensors.go @@ -7,15 +7,70 @@ import ( "github.com/gin-gonic/gin" ) +type postSensorsBody struct { + Name string `json:"name"` +} + +type putSensorsBody struct { + Name string `json:"name"` +} + func GetSensors(s *app.Server) gin.HandlerFunc { return func(c *gin.Context) { sensors, err := s.Services.Sensors.GetList() if err != nil { - c.AbortWithError(500, err) + c.AbortWithError(http.StatusInternalServerError, err) return } c.JSON(http.StatusOK, sensors) } } + +func PostSensors(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + body := postSensorsBody{} + + if err := c.BindJSON(&body); err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + sensor, err := s.Services.Sensors.Create(body.Name) + + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, sensor) + } +} + +func PutSensors(s *app.Server) gin.HandlerFunc { + return func(c *gin.Context) { + body := putSensorsBody{} + + sensorId, err := getSensorId(c) + + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + if err := c.BindJSON(&body); err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + sensor, err := s.Services.Sensors.Update(sensorId, body.Name) + + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.JSON(http.StatusOK, sensor) + } +} diff --git a/server/services/sensor_values_service.go b/server/services/sensor_values_service.go index 1036ce7..bf0afde 100644 --- a/server/services/sensor_values_service.go +++ b/server/services/sensor_values_service.go @@ -11,8 +11,8 @@ type sensorValue struct { Value float64 `json:"value"` } -func (s *SensorValuesService) Push(sensor string, value float64) (int64, error) { - res, err := s.ctx.DB.Exec("INSERT INTO sensor_values (timestamp, sensor, value) VALUES (?, ?, ?)", time.Now().Unix(), sensor, value) +func (s *SensorValuesService) Push(sensorId int64, value float64) (int64, error) { + res, err := s.ctx.DB.Exec("INSERT INTO sensor_values (timestamp, sensor_id, value) VALUES (?, ?, ?)", time.Now().Unix(), sensorId, value) if err != nil { return 0, err @@ -21,12 +21,12 @@ func (s *SensorValuesService) Push(sensor string, value float64) (int64, error) return res.LastInsertId() } -func (s *SensorValuesService) GetList(sensor string, from int64, to int64) ([]sensorValue, error) { +func (s *SensorValuesService) GetList(sensorId int64, from int64, to int64) ([]sensorValue, error) { var value float64 var timestamp int64 values := make([]sensorValue, 0) - rows, err := s.ctx.DB.Query("SELECT timestamp, value FROM sensor_values WHERE sensor = ? AND timestamp > ? AND timestamp < ? ORDER BY timestamp ASC", sensor, from, to) + rows, err := s.ctx.DB.Query("SELECT timestamp, value FROM sensor_values WHERE sensor_id = ? AND timestamp > ? AND timestamp < ? ORDER BY timestamp ASC", sensorId, from, to) if err != nil { return nil, err @@ -51,10 +51,10 @@ func (s *SensorValuesService) GetList(sensor string, from int64, to int64) ([]se return values, nil } -func (s *SensorValuesService) GetLatest(sensor string, to int64) (*sensorValue, error) { +func (s *SensorValuesService) GetLatest(sensorId int64, to int64) (*sensorValue, error) { var value = sensorValue{} - row := s.ctx.DB.QueryRow("SELECT timestamp, value FROM sensor_values WHERE sensor = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT 1", sensor, to) + row := s.ctx.DB.QueryRow("SELECT timestamp, value FROM sensor_values WHERE sensor_id = ? AND timestamp < ? ORDER BY timestamp DESC LIMIT 1", sensorId, to) err := row.Scan(&value.Timestamp, &value.Value) if err != nil { diff --git a/server/services/sensors_service.go b/server/services/sensors_service.go index c1008dc..e70af69 100644 --- a/server/services/sensors_service.go +++ b/server/services/sensors_service.go @@ -1,21 +1,21 @@ package services +import "encoding/hex" + type SensorsService struct { ctx *Context } -type sensorItem struct { - Sensor string `json:"sensor"` - LastUpdate string `json:"lastUpdate"` - Config map[string]string `json:"config"` +type SensorItem struct { + Id int64 `json:"id"` + Name string `json:"name"` + AuthKey string `json:"authKey"` } -func (s *SensorsService) GetList() ([]sensorItem, error) { - sensors := make([]sensorItem, 0) - var sensor string - var lastUpdate string +func (s *SensorsService) GetList() ([]SensorItem, error) { + sensors := make([]SensorItem, 0) - rows, err := s.ctx.DB.Query("SELECT sensor, MAX(timestamp) last_update FROM sensor_values GROUP BY sensor") + rows, err := s.ctx.DB.Query("SELECT id, name, auth_key FROM sensors") if err != nil { return nil, err @@ -24,18 +24,14 @@ func (s *SensorsService) GetList() ([]sensorItem, error) { defer rows.Close() for rows.Next() { - err := rows.Scan(&sensor, &lastUpdate) + item := SensorItem{} + + err := rows.Scan(&item.Id, &item.Name, &item.AuthKey) if err != nil { return nil, err } - config, err := s.ctx.Services.SensorConfig.GetValues(sensor) - - if err != nil { - return nil, err - } - - sensors = append(sensors, sensorItem{Sensor: sensor, LastUpdate: lastUpdate, Config: config}) + sensors = append(sensors, item) } err = rows.Err() @@ -45,3 +41,55 @@ func (s *SensorsService) GetList() ([]sensorItem, error) { return sensors, nil } + +func (s *SensorsService) Create(name string) (*SensorItem, error) { + item := SensorItem{ + Name: name, + AuthKey: hex.EncodeToString(generateRandomKey(32)), + } + + res, err := s.ctx.DB.Exec("INSERT INTO sensors (id, name, auth_key) VALUES (?, ?)", item.Name, item.AuthKey) + + if err != nil { + return nil, err + } + + item.Id, err = res.LastInsertId() + + if err != nil { + return nil, err + } + + return &item, nil +} + +func (s *SensorsService) GetById(id int64) (*SensorItem, error) { + item := SensorItem{} + + row := s.ctx.DB.QueryRow("SELECT id, name, auth_key FROM sensors WHERE id = ?", id) + + err := row.Scan(&item.Id, &item.Name, &item.AuthKey) + if err != nil { + return nil, err + } + + return &item, nil +} + +func (s *SensorsService) Update(id int64, name string) (*SensorItem, error) { + item, err := s.GetById(id) + + if err != nil { + return nil, err + } + + item.Name = name + + _, err = s.ctx.DB.Exec("UPDATE sensors SET name = ? WHERE id = ?", item.Name, item.Id) + + if err != nil { + return nil, err + } + + return item, nil +}