Adds models, handlers, and tests for tagClass, tag, and TagValue classes

This commit is contained in:
Patrick McDonagh
2017-09-28 17:40:39 -05:00
parent 0101ceaccc
commit 2794594780
15 changed files with 1414 additions and 3 deletions

18
app.go
View File

@@ -85,6 +85,24 @@ func (a *App) initializeRoutes() {
a.Router.HandleFunc("/api/v1/file/{id:[0-9]+}", a.updateFile).Methods("PUT")
a.Router.HandleFunc("/api/v1/file/{id:[0-9]+}", a.deleteFile).Methods("DELETE")
a.Router.HandleFunc("/api/v1/tagClasses", a.getTagClasses).Methods("GET")
a.Router.HandleFunc("/api/v1/tagClass", a.createTagClass).Methods("POST")
a.Router.HandleFunc("/api/v1/tagClass/{id:[0-9]+}", a.getTagClass).Methods("GET")
a.Router.HandleFunc("/api/v1/tagClass/{id:[0-9]+}", a.updateTagClass).Methods("PUT")
a.Router.HandleFunc("/api/v1/tagClass/{id:[0-9]+}", a.deleteTagClass).Methods("DELETE")
a.Router.HandleFunc("/api/v1/tags", a.getTags).Methods("GET")
a.Router.HandleFunc("/api/v1/tag", a.createTag).Methods("POST")
a.Router.HandleFunc("/api/v1/tag/{id:[0-9]+}", a.getTag).Methods("GET")
a.Router.HandleFunc("/api/v1/tag/{id:[0-9]+}", a.updateTag).Methods("PUT")
a.Router.HandleFunc("/api/v1/tag/{id:[0-9]+}", a.deleteTag).Methods("DELETE")
a.Router.HandleFunc("/api/v1/tagValues", a.getTagValues).Methods("GET")
a.Router.HandleFunc("/api/v1/tagValue", a.createTagValue).Methods("POST")
a.Router.HandleFunc("/api/v1/tagValue/{id:[0-9]+}", a.getTagValue).Methods("GET")
a.Router.HandleFunc("/api/v1/tagValue/{id:[0-9]+}", a.updateTagValue).Methods("PUT")
a.Router.HandleFunc("/api/v1/tagValue/{id:[0-9]+}", a.deleteTagValue).Methods("DELETE")
//Serve public files
var dir string
flag.StringVar(&dir, "dir", "./public", "the directory to serve files from. Defaults to the current dir")

14
db.sql Normal file
View File

@@ -0,0 +1,14 @@
CREATE DATABASE IF NOT EXISTS lumberjack ;
USE lumberjack;
CREATE USER 'website'@'localhost' IDENTIFIED BY 'henrypump';
GRANT ALL ON *.* TO 'website'@'localhost';
CREATE USER 'website'@'127.0.0.1' IDENTIFIED BY 'henrypump';
GRANT ALL ON *.* TO 'website'@'127.0.0.1';
CREATE USER 'admin'@'localhost' IDENTIFIED BY 'henrypump';
GRANT ALL ON *.* to 'admin'@'localhost';
CREATE USER 'admin'@'%' IDENTIFIED BY 'henrypump';
GRANT ALL ON *.* to 'admin'@'%';
FLUSH PRIVILEGES;

110
handler_tag.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func (a *App) getTag(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid tag ID")
return
}
c := Tag{ID: id}
if err := c.getTag(a.DB); err != nil {
switch err {
case sql.ErrNoRows:
respondWithError(w, http.StatusNotFound, "Tag not found")
default:
respondWithError(w, http.StatusInternalServerError, err.Error())
}
return
}
respondWithJSON(w, http.StatusOK, c)
}
func (a *App) getTags(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
start, _ := strconv.Atoi(r.FormValue("start"))
if count > 10 || count < 1 {
count = 10
}
if start < 0 {
start = 0
}
tags, err := getTags(a.DB, start, count)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, tags)
}
func (a *App) createTag(w http.ResponseWriter, r *http.Request) {
var c Tag
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&c); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
if err := c.createTag(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusCreated, c)
}
func (a *App) updateTag(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid tag ID")
return
}
var c Tag
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&c); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
c.ID = id
if err := c.updateTag(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, c)
}
func (a *App) deleteTag(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid Tag ID")
return
}
c := Tag{ID: id}
if err := c.deleteTag(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"})
}

110
handler_tagclass.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func (a *App) getTagClass(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid config ID")
return
}
c := TagClass{ID: id}
if err := c.getTagClass(a.DB); err != nil {
switch err {
case sql.ErrNoRows:
respondWithError(w, http.StatusNotFound, "TagClass not found")
default:
respondWithError(w, http.StatusInternalServerError, err.Error())
}
return
}
respondWithJSON(w, http.StatusOK, c)
}
func (a *App) getTagClasses(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
start, _ := strconv.Atoi(r.FormValue("start"))
if count > 10 || count < 1 {
count = 10
}
if start < 0 {
start = 0
}
configs, err := getTagClasses(a.DB, start, count)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, configs)
}
func (a *App) createTagClass(w http.ResponseWriter, r *http.Request) {
var c TagClass
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&c); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
if err := c.createTagClass(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusCreated, c)
}
func (a *App) updateTagClass(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid TagClass ID")
return
}
var c TagClass
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&c); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
c.ID = id
if err := c.updateTagClass(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, c)
}
func (a *App) deleteTagClass(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid TagClass ID")
return
}
c := TagClass{ID: id}
if err := c.deleteTagClass(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"})
}

110
handler_tagvalue.go Normal file
View File

@@ -0,0 +1,110 @@
package main
import (
"database/sql"
"encoding/json"
"net/http"
"strconv"
"github.com/gorilla/mux"
)
func (a *App) getTagValue(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid tagValue ID")
return
}
c := TagValue{ID: id}
if err := c.getTagValue(a.DB); err != nil {
switch err {
case sql.ErrNoRows:
respondWithError(w, http.StatusNotFound, "TagValue not found")
default:
respondWithError(w, http.StatusInternalServerError, err.Error())
}
return
}
respondWithJSON(w, http.StatusOK, c)
}
func (a *App) getTagValues(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
start, _ := strconv.Atoi(r.FormValue("start"))
if count > 10 || count < 1 {
count = 10
}
if start < 0 {
start = 0
}
tagValues, err := getTagValues(a.DB, start, count)
if err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, tagValues)
}
func (a *App) createTagValue(w http.ResponseWriter, r *http.Request) {
var c TagValue
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&c); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
if err := c.createTagValue(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusCreated, c)
}
func (a *App) updateTagValue(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid tagValue ID")
return
}
var c TagValue
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&c); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request payload")
return
}
defer r.Body.Close()
c.ID = id
if err := c.updateTagValue(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, c)
}
func (a *App) deleteTagValue(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid TagValue ID")
return
}
c := TagValue{ID: id}
if err := c.deleteTagValue(a.DB); err != nil {
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
respondWithJSON(w, http.StatusOK, map[string]string{"result": "success"})
}

View File

@@ -15,8 +15,14 @@ func main() {
ensureDeviceTypeTableExists(a.DB)
ensureDeviceTableExists(a.DB)
ensureFileTableExists(a.DB)
ensureTagClassTableExists(a.DB)
ensureTagTableExists(a.DB)
seedDeviceTypeData(a.DB)
seedDataTypeData(a.DB)
seedTagClassData(a.DB)
seedDeviceData(a.DB)
a.Run(":8080")
}

View File

@@ -33,6 +33,20 @@ func ensureDataTypeTableExists(db *sql.DB) {
log.Fatal(err)
}
}
func seedDataTypeData(db *sql.DB) {
sqlQuery := `INSERT INTO dataTypes VALUES
(1,'Floating Point','REAL','2016-10-13 15:05:32','2016-10-13 15:05:32'),
(2,'Integer','INT','2016-10-13 15:05:32','2016-10-13 15:05:32'),
(3,'Boolean','BOOL','2016-10-13 15:05:32','2016-10-13 15:05:32');`
insStmt, insErr := db.Prepare(sqlQuery)
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer insStmt.Close() // Close the statement when we leave main() / the program terminates
insStmt.Exec()
}
// getDataType : used during GET command
func (c *DataType) getDataType(db *sql.DB) error {

View File

@@ -37,6 +37,19 @@ func ensureDeviceTableExists(db *sql.DB) {
}
}
func seedDeviceData(db *sql.DB) {
sqlQuery := `INSERT INTO devices VALUES
(1,'Default PLC',1,'192.168.1.10','2016-10-13 15:05:32','2016-10-13 15:05:32');`
insStmt, insErr := db.Prepare(sqlQuery)
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer insStmt.Close() // Close the statement when we leave main() / the program terminates
insStmt.Exec()
}
// getDevice : used during GET command
func (d *Device) getDevice(db *sql.DB) error {
sqlQuery := `SELECT

View File

@@ -1,15 +1,22 @@
package main
import "time"
import (
"database/sql"
"log"
"time"
)
// Tag : holds information about a specific Tag
type Tag struct {
ID int `json:"id"`
Name string `json:"name"`
TagClassID int `json:"tagClassId"`
TagClass TagClass `json:"tagClass"`
Tag string `json:"tag"`
TagName string `json:"tagName"`
DeviceID int `json:"deviceId"`
Device Device `json:"device"`
Description string `json:"description"`
DataTypeID int `json:"dataTypeId"`
DataType DataType `json:"dataType"`
ChangeThreshold float32 `json:"changeThreshold"`
GuaranteeSec int `json:"guaranteeSec"`
@@ -23,3 +30,281 @@ type Tag struct {
// Tags : a list of Tag items
type Tags []Tag
const tagTableCreationQuery = `CREATE TABLE IF NOT EXISTS tags (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
name varchar(255),
tagName varchar(255),
tagClassId int(10) unsigned,
deviceId int(10) unsigned,
description varchar(255),
dataTypeId int(10) unsigned,
changeThreshold float,
guaranteeSec int(10) unsigned,
mapFunction varchar(255),
units varchar(255),
minExpected float,
maxExpected float,
createdAt datetime,
updatedAt datetime,
PRIMARY KEY (id),
CONSTRAINT fk_tagclass FOREIGN KEY (tagClassId) REFERENCES tagClasses(id),
CONSTRAINT fk_device FOREIGN KEY (deviceId) REFERENCES devices(id),
CONSTRAINT fk_datatype FOREIGN KEY (dataTypeId) REFERENCES dataTypes(id)
);`
func ensureTagTableExists(db *sql.DB) {
if _, err := db.Exec(tagTableCreationQuery); err != nil {
log.Fatal(err)
}
}
// getTag : used during GET command
func (t *Tag) getTag(db *sql.DB) error {
sqlQuery := `SELECT
tags.name,
tags.tagName,
tags.tagClassId,
tagClasses.id,
tagClasses.classType,
tagClasses.description,
tagClasses.updatedAt,
tagClasses.createdAt,
tags.deviceId,
devices.id,
devices.name,
devices.deviceTypeId,
deviceTypes.id,
deviceTypes.name,
deviceTypes.createdAt,
deviceTypes.updatedAt,
devices.address,
devices.createdAt,
devices.updatedAt,
tags.description,
tags.dataTypeId,
dataTypes.id,
dataTypes.dataType,
dataTypes.plcType,
dataTypes.updatedAt,
dataTypes.createdAt,
tags.changeThreshold,
tags.guaranteeSec,
tags.mapFunction,
tags.units,
tags.maxExpected,
tags.minExpected,
tags.createdAt,
tags.updatedAt
FROM tags
JOIN tagClasses ON tags.tagClassId = tagClasses.id
JOIN devices ON tags.deviceId = devices.id
JOIN deviceTypes ON devices.deviceTypeId = deviceTypes.id
JOIN dataTypes ON tags.dataTypeId = dataTypes.id
WHERE tags.id=?`
return db.QueryRow(sqlQuery, t.ID).Scan(
&t.Name,
&t.TagName,
&t.TagClassID,
&t.TagClass.ID,
&t.TagClass.ClassType,
&t.TagClass.Description,
&t.TagClass.UpdatedAt,
&t.TagClass.UpdatedAt,
&t.DeviceID,
&t.Device.ID,
&t.Device.Name,
&t.Device.DeviceTypeID,
&t.Device.DeviceType.ID,
&t.Device.DeviceType.Name,
&t.Device.DeviceType.CreatedAt,
&t.Device.DeviceType.UpdatedAt,
&t.Device.Address,
&t.Device.CreatedAt,
&t.Device.UpdatedAt,
&t.Description,
&t.DataTypeID,
&t.DataType.ID,
&t.DataType.DataType,
&t.DataType.PlcType,
&t.DataType.UpdatedAt,
&t.DataType.CreatedAt,
&t.ChangeThreshold,
&t.GuaranteeSec,
&t.MapFunction,
&t.Units,
&t.MaxExpected,
&t.MinExpected,
&t.CreatedAt,
&t.UpdatedAt)
}
// updateTag : used during PUT command
func (t *Tag) updateTag(db *sql.DB) error {
sqlQuery := `UPDATE tags SET
name=?,
tagName=?,
tagClassId=?,
deviceId=?,
description=?,
dataTypeId=?,
changeThreshold=?,
guaranteeSec=?,
mapFunction=?,
units=?,
minExpected=?,
maxExpected=?,
updatedAt=?
WHERE id=?`
updStmt, updErr := db.Prepare(sqlQuery)
if updErr != nil {
panic(updErr.Error()) // proper error handling instead of panic in your app
}
defer updStmt.Close() // Close the statement when we leave main() / the program terminates
_, err := updStmt.Exec(t.Name, t.TagName, t.TagClassID, t.DeviceID,
t.Description, t.DataTypeID, t.ChangeThreshold, t.GuaranteeSec,
t.MapFunction, t.Units, t.MinExpected, t.MaxExpected, time.Now(), t.ID)
return err
}
// deleteTag : used during DELETE command
func (t *Tag) deleteTag(db *sql.DB) error {
_, err := db.Exec("DELETE FROM tags WHERE id=?", t.ID)
return err
}
// createTag : used during PUSH command
func (t *Tag) createTag(db *sql.DB) error {
sqlQuery := `INSERT INTO tags (
name,
tagName,
tagClassId,
deviceId,
description,
dataTypeId,
changeThreshold,
guaranteeSec,
mapFunction,
units,
minExpected,
maxExpected,
createdAt,
updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
stmtIns, insErr := db.Prepare(sqlQuery)
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer stmtIns.Close() // Close the statement when we leave main() / the program terminates
_, err := stmtIns.Exec(t.Name, t.TagName, t.TagClassID, t.DeviceID,
t.Description, t.DataTypeID, t.ChangeThreshold, t.GuaranteeSec,
t.MapFunction, t.Units, t.MinExpected, t.MaxExpected, time.Now(), time.Now())
return err
}
// getTags : used during GET command for all
func getTags(db *sql.DB, start, count int) (Tags, error) {
sqlQuery := `SELECT
tags.ID,
tags.name,
tags.tagName,
tags.tagClassId,
tagClasses.id,
tagClasses.classType,
tagClasses.description,
tagClasses.updatedAt,
tagClasses.createdAt,
tags.deviceId,
devices.id,
devices.name,
devices.deviceTypeId,
deviceTypes.id,
deviceTypes.name,
deviceTypes.createdAt,
deviceTypes.updatedAt,
devices.address,
devices.createdAt,
devices.updatedAt,
tags.description,
tags.dataTypeId,
dataTypes.id,
dataTypes.dataType,
dataTypes.plcType,
dataTypes.updatedAt,
dataTypes.createdAt,
tags.changeThreshold,
tags.guaranteeSec,
tags.mapFunction,
tags.units,
tags.maxExpected,
tags.minExpected,
tags.createdAt,
tags.updatedAt
FROM tags
JOIN tagClasses ON tags.tagClassId = tagClasses.id
JOIN devices ON tags.deviceId = devices.id
JOIN deviceTypes ON devices.deviceTypeId = deviceTypes.id
JOIN dataTypes ON tags.dataTypeId = dataTypes.id
LIMIT ? OFFSET ?;`
getStmt, prepErr := db.Prepare(sqlQuery)
if prepErr != nil {
panic(prepErr.Error()) // proper error handling instead of panic in your app
}
defer getStmt.Close() // Close the statement when we leave main() / the program terminates
rows, err := getStmt.Query(count, start)
if err != nil {
return nil, err
}
defer rows.Close()
tags := Tags{}
for rows.Next() {
var t Tag
if err := rows.Scan(&t.ID,
&t.Name,
&t.TagName,
&t.TagClassID,
&t.TagClass.ID,
&t.TagClass.ClassType,
&t.TagClass.Description,
&t.TagClass.UpdatedAt,
&t.TagClass.UpdatedAt,
&t.DeviceID,
&t.Device.ID,
&t.Device.Name,
&t.Device.DeviceTypeID,
&t.Device.DeviceType.ID,
&t.Device.DeviceType.Name,
&t.Device.DeviceType.CreatedAt,
&t.Device.DeviceType.UpdatedAt,
&t.Device.Address,
&t.Device.CreatedAt,
&t.Device.UpdatedAt,
&t.Description,
&t.DataTypeID,
&t.DataType.ID,
&t.DataType.DataType,
&t.DataType.PlcType,
&t.DataType.UpdatedAt,
&t.DataType.CreatedAt,
&t.ChangeThreshold,
&t.GuaranteeSec,
&t.MapFunction,
&t.Units,
&t.MaxExpected,
&t.MinExpected,
&t.CreatedAt,
&t.UpdatedAt); err != nil {
return nil, err
}
tags = append(tags, t)
}
return tags, nil
}

View File

@@ -1,6 +1,10 @@
package main
import "time"
import (
"database/sql"
"log"
"time"
)
// TagClass : holds information about a specific TagClass
type TagClass struct {
@@ -13,3 +17,93 @@ type TagClass struct {
// TagClasses : a list of TagClass items
type TagClasses []TagClass
const tagClassTableCreationQuery = `CREATE TABLE IF NOT EXISTS tagClasses (
id int(10) unsigned AUTO_INCREMENT,
classType varchar(255),
description varchar(255),
createdAt datetime,
updatedAt datetime,
UNIQUE KEY classType (classType),
PRIMARY KEY (id)
)`
func ensureTagClassTableExists(db *sql.DB) {
if _, err := db.Exec(tagClassTableCreationQuery); err != nil {
log.Fatal(err)
}
}
func seedTagClassData(db *sql.DB) {
sqlQuery := `INSERT INTO tagClasses VALUES
(1,'normal','Historical Data','2016-10-13 15:05:32','2016-10-13 15:05:32'),
(2,'handshake','PLC Handshake','2016-10-13 15:05:32','2016-10-13 15:05:32');`
insStmt, insErr := db.Prepare(sqlQuery)
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer insStmt.Close() // Close the statement when we leave main() / the program terminates
insStmt.Exec()
}
// getTagClass : used during GET command
func (c *TagClass) getTagClass(db *sql.DB) error {
return db.QueryRow("SELECT classType, description, createdAt, updatedAt FROM tagClasses WHERE id=?", c.ID).Scan(&c.ClassType, &c.Description, &c.CreatedAt, &c.UpdatedAt)
}
// updateTagClass : used during PUT command
func (c *TagClass) updateTagClass(db *sql.DB) error {
updStmt, updErr := db.Prepare("UPDATE tagClasses SET classType=?, description=?, updatedAt=? WHERE id=?")
if updErr != nil {
panic(updErr.Error()) // proper error handling instead of panic in your app
}
defer updStmt.Close() // Close the statement when we leave main() / the program terminates
_, err := updStmt.Exec(c.ClassType, c.Description, time.Now(), c.ID)
return err
}
// deleteTagClass : used during DELETE command
func (c *TagClass) deleteTagClass(db *sql.DB) error {
_, err := db.Exec("DELETE FROM tagClasses WHERE id=?", c.ID)
return err
}
// createTagClass : used during PUSH command
func (c *TagClass) createTagClass(db *sql.DB) error {
stmtIns, insErr := db.Prepare("INSERT INTO tagClasses (classType, description, createdAt, updatedAt) VALUES (?, ?, ?, ?);")
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer stmtIns.Close() // Close the statement when we leave main() / the program terminates
_, err := stmtIns.Exec(c.ClassType, c.Description, time.Now(), time.Now())
return err
}
// getTagClasses : used during GET command for all
func getTagClasses(db *sql.DB, start, count int) (TagClasses, error) {
rows, err := db.Query(
"SELECT id, classType, description, createdAt, updatedAt FROM tagClasses LIMIT ? OFFSET ?",
count, start)
if err != nil {
return nil, err
}
defer rows.Close()
tagClasses := TagClasses{}
for rows.Next() {
var c TagClass
if err := rows.Scan(&c.ID, &c.ClassType, &c.Description, &c.CreatedAt, &c.UpdatedAt); err != nil {
return nil, err
}
tagClasses = append(tagClasses, c)
}
return tagClasses, nil
}

121
model_tagvalue.go Normal file
View File

@@ -0,0 +1,121 @@
package main
import (
"database/sql"
"log"
"time"
)
// TagValue : holds information about a specific TagValue
type TagValue struct {
ID int `json:"id"`
TagID int `json:"tagId"`
Val string `json:"val"`
TagName int `json:"tagName"`
Units int `json:"units"`
DataType string `json:"dataType"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// TagValues : a list of TagValue items
type TagValues []TagValue
const tagValueTableCreationQuery = `CREATE TABLE IF NOT EXISTS tagValues (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
tagId int(10) unsigned,
val varchar(64),
createdAt datetime,
updatedAt datetime,
PRIMARY KEY (id),
CONSTRAINT fk_tag FOREIGN KEY (tagId) REFERENCES tags(id)
)`
func ensureTagValueTableExists(db *sql.DB) {
if _, err := db.Exec(tagValueTableCreationQuery); err != nil {
log.Fatal(err)
}
}
// getTagValue : used during GET command
func (t *TagValue) getTagValue(db *sql.DB) error {
sqlQuery := `SELECT
tagValues.tagId,
tagValues.val,
tags.name,
tags.units,
dataTypes.dataType,
tagValues.createdAt,
tagValues.updatedAt
FROM tagValues
JOIN tags ON tagValues.tagId = tags.id
JOIN dataTypes ON tags.dataTypeId = dataTypes.id
WHERE tagValues.id=?`
return db.QueryRow(sqlQuery, t.ID).Scan(&t.TagID, &t.Val, &t.TagName, &t.Units, &t.DataType, &t.CreatedAt, &t.UpdatedAt)
}
// updateTagValue : used during PUT command
func (t *TagValue) updateTagValue(db *sql.DB) error {
updStmt, updErr := db.Prepare("UPDATE tagValues SET tagId=?, val=?, updatedAt=? WHERE id=?")
if updErr != nil {
panic(updErr.Error()) // proper error handling instead of panic in your app
}
defer updStmt.Close() // Close the statement when we leave main() / the program terminates
_, err := updStmt.Exec(t.TagID, t.Val, time.Now(), t.ID)
return err
}
// deleteTagValue : used during DELETE command
func (t *TagValue) deleteTagValue(db *sql.DB) error {
_, err := db.Exec("DELETE FROM tagValues WHERE id=?", t.ID)
return err
}
// createTagValue : used during PUSH command
func (t *TagValue) createTagValue(db *sql.DB) error {
stmtIns, insErr := db.Prepare("INSERT INTO tagValues (tagId, val, createdAt, updatedAt) VALUES (?, ?, ?, ?);")
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer stmtIns.Close() // Close the statement when we leave main() / the program terminates
_, err := stmtIns.Exec(t.TagID, t.Val, time.Now(), time.Now())
return err
}
// getTagValues : used during GET command for all
func getTagValues(db *sql.DB, start, count int) (TagValues, error) {
sqlQuery := `SELECT
tagValues.id,
tagValues.tagId,
tagValues.val,
tags.name,
tags.units,
dataTypes.dataType,
tagValues.createdAt,
tagValues.updatedAt
FROM tagValues
JOIN tags ON tagValues.tagId = tags.id
JOIN dataTypes ON tags.dataTypeId = dataTypes.id
LIMIT ? OFFSET ?`
rows, err := db.Query(sqlQuery, count, start)
if err != nil {
return nil, err
}
defer rows.Close()
tagValues := TagValues{}
for rows.Next() {
var t TagValue
if err := rows.Scan(&t.ID, &t.TagID, &t.Val, &t.TagName, &t.Units, &t.DataType, &t.CreatedAt, &t.UpdatedAt); err != nil {
return nil, err
}
tagValues = append(tagValues, t)
}
return tagValues, nil
}

View File

@@ -21,6 +21,9 @@ func TestMain(m *testing.M) {
ensureDeviceTypeTableExists(a.DB)
ensureDeviceTableExists(a.DB)
ensureFileTableExists(a.DB)
ensureTagClassTableExists(a.DB)
ensureTagTableExists(a.DB)
ensureTagValueTableExists(a.DB)
code := m.Run()
@@ -29,6 +32,9 @@ func TestMain(m *testing.M) {
clearDeviceTypeTable()
clearDeviceTable()
clearFileTable()
clearTagClassTable()
clearTagTable()
clearTagValueTable()
os.Exit(code)
}

230
testtag_test.go Normal file
View File

@@ -0,0 +1,230 @@
package main
import (
"bytes"
"encoding/json"
"net/http"
"reflect"
"strconv"
"testing"
"time"
)
func clearTagTable() {
a.DB.Exec("DELETE FROM tags")
a.DB.Exec("ALTER TABLE tags AUTO_INCREMENT = 1")
}
func TestEmptyTagTable(t *testing.T) {
clearTagTable()
req, _ := http.NewRequest("GET", "/api/v1/tags", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
if body := response.Body.String(); body != "[]" {
t.Errorf("Expected an empty array. Got %s", body)
}
}
func TestGetNonExistentTag(t *testing.T) {
clearTagTable()
req, _ := http.NewRequest("GET", "/api/v1/tag/11", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
var m map[string]string
json.Unmarshal(response.Body.Bytes(), &m)
if m["error"] != "Tag not found" {
t.Errorf("Expected the 'error' key of the response to be set to 'Tag not found'. Got '%s'", m["error"])
}
}
func TestCreateTag(t *testing.T) {
clearTagTable()
seedDataTypeData(a.DB)
seedDeviceTypeData(a.DB)
seedTagClassData(a.DB)
seedDeviceData(a.DB)
payload := []byte(`{"name":"TestTag","tagName":"testTag","tagClassId":1,"deviceId":1,"description":"This is a test tag","dataTypeId":1,"changeThreshold":4.5,"guaranteeSec":600,"mapFunction":"double","units":"in.","minExpected":0.0,"maxExpected":1000.0}`)
req, _ := http.NewRequest("POST", "/api/v1/tag", bytes.NewBuffer(payload))
response := executeRequest(req)
checkResponseCode(t, http.StatusCreated, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["name"] != "TestTag" {
t.Errorf("Expected tag name to be 'TestTag'. Got '%v'", m["name"])
}
if m["tagName"] != "testTag" {
t.Errorf("Expected tag tagName to be 'testTag'. Got '%v'", m["tagName"])
}
if m["tagClassId"] != 1.0 {
t.Errorf("Expected tag tagClassId to be '1'. Got '%v'", m["tagClassId"])
}
if m["deviceId"] != 1.0 {
t.Errorf("Expected tag deviceId to be '1'. Got '%v'", m["deviceId"])
}
if m["description"] != "This is a test tag" {
t.Errorf("Expected tag description to be 'This is a test tag'. Got '%v'", m["description"])
}
if m["dataTypeId"] != 1.0 {
t.Errorf("Expected tag dataTypeId to be 'testTag'. Got '%v'", m["dataTypeId"])
}
if m["changeThreshold"] != 4.5 {
t.Errorf("Expected tag changeThreshold to be '4.5'. Got '%v'", m["changeThreshold"])
}
if m["guaranteeSec"] != 600.0 {
t.Errorf("Expected tag guaranteeSec to be '600'. Got '%v'", m["guaranteeSec"])
}
if m["mapFunction"] != "double" {
t.Errorf("Expected tag mapFunction to be 'double'. Got '%v'", m["mapFunction"])
}
if m["units"] != "in." {
t.Errorf("Expected tag units to be 'in.'. Got '%v'", m["units"])
}
if m["minExpected"] != 0.0 {
t.Errorf("Expected tag minExpected to be '0.0'. Got '%v'", m["minExpected"])
}
if m["maxExpected"] != 1000.0 {
t.Errorf("Expected tag maxExpected to be '1000.0'. Got '%v'", m["maxExpected"])
}
}
func TestGetTag(t *testing.T) {
clearTagTable()
addTags(1)
req, _ := http.NewRequest("GET", "/api/v1/tag/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
}
func addTags(count int) {
if count < 1 {
count = 1
}
sqlQuery := `INSERT INTO tags (
name,
tagName,
tagClassId,
deviceId,
description,
dataTypeId,
changeThreshold,
guaranteeSec,
mapFunction,
units,
minExpected,
maxExpected,
createdAt,
updatedAt) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
insStmt, insErr := a.DB.Prepare(sqlQuery)
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer insStmt.Close() // Close the statement when we leave main() / the program terminates
for i := 0; i < count; i++ {
insStmt.Exec("Name"+strconv.Itoa(i), "tag"+strconv.Itoa(i), 1, 1,
"Description"+strconv.Itoa(i), 1, i*100.0, 600,
"mapFn"+strconv.Itoa(i), "in.", 0.0, 1000.0, time.Now(), time.Now())
}
}
func TestUpdateTag(t *testing.T) {
clearTagTable()
addTags(1)
req, _ := http.NewRequest("GET", "/api/v1/tag/1", nil)
response := executeRequest(req)
var originalTag map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &originalTag)
payload := []byte(`{"name":"TestTag","tagName":"testTag","tagClassId":1,"deviceId":1,"description":"This is a test tag","dataTypeId":1,"changeThreshold":4.5,"guaranteeSec":600,"mapFunction":"double","units":"in.2","minExpected":1.0,"maxExpected":1001.0}`)
req, _ = http.NewRequest("PUT", "/api/v1/tag/1", bytes.NewBuffer(payload))
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["id"] != originalTag["id"] {
t.Errorf("Expected the id to remain the same (%v). Got %v", originalTag["id"], m["id"])
}
if m["name"] == originalTag["name"] {
t.Errorf("Expected the name to change from '%v' to '%v'. Got '%v'", originalTag["name"], m["name"], m["name"])
}
if m["tagName"] == originalTag["tagName"] {
t.Errorf("Expected the tagName to change from '%v' to '%v'. Got '%v'", originalTag["tagName"], m["tagName"], m["tagName"])
}
if int(m["tagClassId"].(float64)) == originalTag["tagClassId"] {
t.Errorf("Expected the tagClassId to change from '%v' to '%v'. Got '%v'", originalTag["tagClassId"], m["tagClassId"], m["tagClassId"])
}
if int(m["deviceId"].(float64)) == originalTag["deviceId"] {
t.Errorf("Expected the deviceId to change from '%v' to '%v'. Got '%v'", originalTag["deviceId"], m["deviceId"], m["deviceId"])
}
if m["description"] == originalTag["description"] {
t.Errorf("Expected the description to change from '%v' to '%v'. Got '%v'", originalTag["description"], m["description"], m["description"])
}
if int(m["dataTypeId"].(float64)) == originalTag["dataTypeId"] {
t.Errorf("Expected the dataTypeId to change from '%v' to '%v'. Got '%v'", originalTag["dataTypeId"], m["dataTypeId"], m["dataTypeId"])
}
if m["changeThreshold"] == originalTag["changeThreshold"] {
t.Errorf("Expected the changeThreshold to change from '%v' to '%v'. Got '%v'", originalTag["changeThreshold"], m["changeThreshold"], m["changeThreshold"])
}
if int(m["guaranteeSec"].(float64)) == originalTag["guaranteeSec"] {
t.Errorf("Expected the guaranteeSec to change from '%v' to '%v'. Got '%v'", originalTag["guaranteeSec"], m["guaranteeSec"], m["guaranteeSec"])
}
if m["mapFunction"] == originalTag["mapFunction"] {
t.Errorf("Expected the mapFunction to change from '%v' to '%v'. Got '%v'", originalTag["changeThreshold"], m["changeThreshold"], m["changeThreshold"])
}
if m["units"] == originalTag["units"] {
t.Errorf("Expected the units to change from '%v' to '%v'. Got '%v'", reflect.TypeOf(originalTag["units"]), reflect.TypeOf(m["units"]), reflect.TypeOf(m["units"]))
}
if m["minExpected"] == originalTag["minExpected"] {
t.Errorf("Expected the minExpected to change from '%v' to '%v'. Got '%v'", originalTag["minExpected"], m["minExpected"], m["minExpected"])
}
if m["maxExpected"] == originalTag["maxExpected"] {
t.Errorf("Expected the maxExpected to change from '%v' to '%v'. Got '%v'", originalTag["maxExpected"], m["maxExpected"], m["maxExpected"])
}
}
func TestDeleteTag(t *testing.T) {
clearTagTable()
addTags(1)
req, _ := http.NewRequest("GET", "/api/v1/tag/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("DELETE", "/api/v1/tag/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("GET", "/api/v1/tag/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
}

139
testtagclass_test.go Normal file
View File

@@ -0,0 +1,139 @@
package main
import (
"bytes"
"encoding/json"
"net/http"
"strconv"
"testing"
"time"
)
func clearTagClassTable() {
a.DB.Exec("DELETE FROM tagClasses")
a.DB.Exec("ALTER TABLE tagClasses AUTO_INCREMENT = 1")
}
func TestEmptyTagClassTable(t *testing.T) {
clearTagClassTable()
req, _ := http.NewRequest("GET", "/api/v1/tagClasses", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
if body := response.Body.String(); body != "[]" {
t.Errorf("Expected an empty array. Got %s", body)
}
}
func TestGetNonExistentTagClass(t *testing.T) {
clearTagClassTable()
req, _ := http.NewRequest("GET", "/api/v1/tagClass/11", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
var m map[string]string
json.Unmarshal(response.Body.Bytes(), &m)
if m["error"] != "TagClass not found" {
t.Errorf("Expected the 'error' key of the response to be set to 'TagClass not found'. Got '%s'", m["error"])
}
}
func TestCreateTagClass(t *testing.T) {
clearTagClassTable()
payload := []byte(`{"classType":"Normal","description":"Historical Data"}`)
req, _ := http.NewRequest("POST", "/api/v1/tagClass", bytes.NewBuffer(payload))
response := executeRequest(req)
checkResponseCode(t, http.StatusCreated, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["classType"] != "Normal" {
t.Errorf("Expected tagClass classType to be 'Normal'. Got '%v'", m["classType"])
}
if m["description"] != "Historical Data" {
t.Errorf("Expected tagClass description to be 'Historical Data'. Got '%v'", m["description"])
}
}
func TestGetTagClass(t *testing.T) {
clearTagClassTable()
addTagClasses(1)
req, _ := http.NewRequest("GET", "/api/v1/tagClass/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
}
func addTagClasses(count int) {
if count < 1 {
count = 1
}
insStmt, insErr := a.DB.Prepare("INSERT INTO tagClasses(classType, description, createdAt, updatedAt) VALUES(?, ?, ?, ?)")
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer insStmt.Close() // Close the statement when we leave main() / the program terminates
for i := 0; i < count; i++ {
insStmt.Exec("TESTTYPE"+strconv.Itoa(i), "TESTDESCRIPTION"+strconv.Itoa(i), time.Now(), time.Now())
}
}
func TestUpdateTagClass(t *testing.T) {
clearTagClassTable()
addTagClasses(1)
req, _ := http.NewRequest("GET", "/api/v1/tagClass/1", nil)
response := executeRequest(req)
var originalTagClass map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &originalTagClass)
payload := []byte(`{"classType":"handshake","description":"PLC Handshake"}`)
req, _ = http.NewRequest("PUT", "/api/v1/tagClass/1", bytes.NewBuffer(payload))
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["id"] != originalTagClass["id"] {
t.Errorf("Expected the id to remain the same (%v). Got %v", originalTagClass["id"], m["id"])
}
if m["classType"] == originalTagClass["tagClass"] {
t.Errorf("Expected the classType to change from '%v' to '%v'. Got '%v'", originalTagClass["classType"], m["classType"], m["classType"])
}
if m["description"] == originalTagClass["description"] {
t.Errorf("Expected the description to change from '%v' to '%v'. Got '%v'", originalTagClass["description"], m["description"], m["description"])
}
}
func TestDeleteTagClass(t *testing.T) {
clearTagClassTable()
addTagClasses(1)
req, _ := http.NewRequest("GET", "/api/v1/tagClass/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("DELETE", "/api/v1/tagClass/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("GET", "/api/v1/tagClass/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
}

141
testtagvalue_test.go Normal file
View File

@@ -0,0 +1,141 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"strconv"
"testing"
"time"
)
func clearTagValueTable() {
a.DB.Exec("DELETE FROM tagValues")
a.DB.Exec("ALTER TABLE tagValues AUTO_INCREMENT = 1")
}
func TestEmptyTagValueTable(t *testing.T) {
clearTagValueTable()
req, _ := http.NewRequest("GET", "/api/v1/tagValues", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
if body := response.Body.String(); body != "[]" {
t.Errorf("Expected an empty array. Got %s", body)
}
}
func TestGetNonExistentTagValue(t *testing.T) {
clearTagValueTable()
req, _ := http.NewRequest("GET", "/api/v1/tagValue/11", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
var m map[string]string
json.Unmarshal(response.Body.Bytes(), &m)
if m["error"] != "TagValue not found" {
t.Errorf("Expected the 'error' key of the response to be set to 'TagValue not found'. Got '%s'", m["error"])
}
}
func TestCreateTagValue(t *testing.T) {
clearTagValueTable()
addTags(1)
payload := []byte(`{"tagId":1,"val":"1234.56"}`)
req, _ := http.NewRequest("POST", "/api/v1/tagValue", bytes.NewBuffer(payload))
response := executeRequest(req)
fmt.Printf("\n\n%s\n\n", response.Body)
checkResponseCode(t, http.StatusCreated, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["tagId"] != 1.0 {
t.Errorf("Expected tagValue name to be '1'. Got '%v'", m["name"])
}
if m["val"] != "1234.56" {
t.Errorf("Expected tagValue address to be '1234.56'. Got '%v'", m["address"])
}
}
func TestGetTagValue(t *testing.T) {
clearTagValueTable()
addTagValues(1)
req, _ := http.NewRequest("GET", "/api/v1/tagValue/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
}
func addTagValues(count int) {
if count < 1 {
count = 1
}
insStmt, insErr := a.DB.Prepare("INSERT INTO tagValues(tagId, val, createdAt, updatedAt) VALUES(?, ?, ?, ?)")
if insErr != nil {
panic(insErr.Error()) // proper error handling instead of panic in your app
}
defer insStmt.Close() // Close the statement when we leave main() / the program terminates
for i := 0; i < count; i++ {
insStmt.Exec(1, strconv.Itoa(i*10), time.Now(), time.Now())
}
}
func TestUpdateTagValue(t *testing.T) {
clearTagValueTable()
addTagValues(1)
req, _ := http.NewRequest("GET", "/api/v1/tagValue/1", nil)
response := executeRequest(req)
var originalTagValue map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &originalTagValue)
payload := []byte(`{"tagId":1,"val":"111.111"}`)
req, _ = http.NewRequest("PUT", "/api/v1/tagValue/1", bytes.NewBuffer(payload))
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
var m map[string]interface{}
json.Unmarshal(response.Body.Bytes(), &m)
if m["id"] != originalTagValue["id"] {
t.Errorf("Expected the id to remain the same (%v). Got %v", originalTagValue["id"], m["id"])
}
if m["tagId"] != originalTagValue["tagId"] {
t.Errorf("Expected the id to remain the same (%v). Got %v", originalTagValue["tagId"], m["tagId"])
}
if m["val"] == originalTagValue["val"] {
t.Errorf("Expected the val to change from '%v' to '%v'. Got '%v'", originalTagValue["val"], m["val"], m["val"])
}
}
func TestDeleteTagValue(t *testing.T) {
clearTagValueTable()
addTagValues(1)
req, _ := http.NewRequest("GET", "/api/v1/tagValue/1", nil)
response := executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("DELETE", "/api/v1/tagValue/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusOK, response.Code)
req, _ = http.NewRequest("GET", "/api/v1/tagValue/1", nil)
response = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, response.Code)
}