-
Notifications
You must be signed in to change notification settings - Fork 4
/
instrument.go
341 lines (299 loc) · 9.78 KB
/
instrument.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
333
334
335
336
337
338
339
340
341
package models
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/google/uuid"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/paulmach/orb"
"github.com/paulmach/orb/encoding/wkb"
"github.com/paulmach/orb/encoding/wkt"
"github.com/paulmach/orb/geojson"
)
// Instrument is an instrument
type Instrument struct {
ID uuid.UUID `json:"id"`
AwareID *uuid.UUID `json:"aware_id,omitempty"`
Groups []uuid.UUID `json:"groups"`
Constants []uuid.UUID `json:"constants"`
AlertConfigs []uuid.UUID `json:"alert_configs"`
Formula *string `json:"formula"`
StatusID uuid.UUID `json:"status_id" db:"status_id"`
Status string `json:"status"`
StatusTime time.Time `json:"status_time" db:"status_time"`
Deleted bool `json:"-"`
Slug string `json:"slug"`
Name string `json:"name"`
TypeID uuid.UUID `json:"type_id" db:"type_id"`
Type string `json:"type"`
Geometry geojson.Geometry `json:"geometry,omitempty"`
Station *int `json:"station"`
StationOffset *int `json:"offset" db:"station_offset"`
ProjectID *uuid.UUID `json:"project_id" db:"project_id"`
NIDID *string `json:"nid_id" db:"nid_id"`
USGSID *string `json:"usgs_id" db:"usgs_id"`
AuditInfo
}
// CreateInstrumentsValidationResult holds results of checking InstrumentCollection POST
type CreateInstrumentsValidationResult struct {
IsValid bool `json:"is_valid"`
Errors []string `json:"errors"`
}
// InstrumentCollection is a collection of Instrument items
type InstrumentCollection struct {
Items []Instrument
}
// Shorten returns an instrument collection with individual objects limited to ID and Struct fields
func (c InstrumentCollection) Shorten() IDAndSlugCollection {
ss := IDAndSlugCollection{Items: make([]IDAndSlug, 0)}
for _, n := range c.Items {
s := IDAndSlug{ID: n.ID, Slug: n.Slug}
ss.Items = append(ss.Items, s)
}
return ss
}
// UnmarshalJSON implements UnmarshalJSON interface
func (c *InstrumentCollection) UnmarshalJSON(b []byte) error {
switch JSONType(b) {
case "ARRAY":
if err := json.Unmarshal(b, &c.Items); err != nil {
return err
}
case "OBJECT":
var n Instrument
if err := json.Unmarshal(b, &n); err != nil {
return err
}
c.Items = []Instrument{n}
default:
c.Items = make([]Instrument, 0)
}
return nil
}
// ListInstrumentSlugs lists used instrument slugs in the database
func ListInstrumentSlugs(db *sqlx.DB) ([]string, error) {
ss := make([]string, 0)
if err := db.Select(&ss, "SELECT slug FROM instrument"); err != nil {
return make([]string, 0), err
}
return ss, nil
}
// ListInstruments returns an array of instruments from the database
func ListInstruments(db *sqlx.DB) ([]Instrument, error) {
rows, err := db.Queryx(listInstrumentsSQL + " WHERE NOT deleted")
if err != nil {
return make([]Instrument, 0), err
}
return InstrumentsFactory(rows)
}
// GetInstrument returns a single instrument
func GetInstrument(db *sqlx.DB, id *uuid.UUID) (*Instrument, error) {
rows, err := db.Queryx(listInstrumentsSQL+" WHERE id = $1", id)
if err != nil {
return nil, err
}
ii, err := InstrumentsFactory(rows)
if err != nil {
return nil, err
}
return &ii[0], nil
}
// GetInstrumentCount returns the number of instruments in the database
func GetInstrumentCount(db *sqlx.DB) (int, error) {
var count int
if err := db.Get(&count, "SELECT COUNT(id) FROM instrument WHERE NOT deleted"); err != nil {
return 0, err
}
return count, nil
}
// CreateInstruments creates many instruments from an array of instruments
func CreateInstruments(db *sqlx.DB, instruments []Instrument) ([]IDAndSlug, error) {
txn, err := db.Beginx()
if err != nil {
return make([]IDAndSlug, 0), err
}
// Instrument
stmt1, err := txn.Preparex(
`INSERT INTO instrument
(slug, name, type_id, geometry, station, station_offset, creator, create_date, project_id, formula, nid_id, usgs_id)
VALUES
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
RETURNING id, slug`,
)
if err != nil {
return make([]IDAndSlug, 0), err
}
// Instrument Status
stmt2, err := txn.Preparex(createInstrumentStatusSQL())
if err != nil {
return make([]IDAndSlug, 0), err
}
// AWARE Gage Info (if provided)
stmt3, err := txn.Preparex(`INSERT INTO aware_platform (instrument_id, aware_id) VALUES ($1, $2)`)
if err != nil {
return make([]IDAndSlug, 0), err
}
ii := make([]IDAndSlug, len(instruments))
for idx, i := range instruments {
if err := stmt1.Get(
&ii[idx],
i.Slug, i.Name, i.TypeID, wkt.MarshalString(i.Geometry.Geometry()),
i.Station, i.StationOffset, i.Creator, i.CreateDate, i.ProjectID, i.Formula, i.NIDID, i.USGSID,
); err != nil {
return make([]IDAndSlug, 0), err
}
if _, err := stmt2.Exec(ii[idx].ID, i.StatusID, i.StatusTime); err != nil {
return make([]IDAndSlug, 0), err
}
// Store Aware ID if Provided
if i.AwareID != nil {
if _, err := stmt3.Exec(ii[idx].ID, i.AwareID); err != nil {
return make([]IDAndSlug, 0), err
}
}
}
if err := stmt1.Close(); err != nil {
return make([]IDAndSlug, 0), err
}
if err := stmt2.Close(); err != nil {
return make([]IDAndSlug, 0), err
}
if err := stmt3.Close(); err != nil {
return make([]IDAndSlug, 0), err
}
if err := txn.Commit(); err != nil {
return make([]IDAndSlug, 0), err
}
return ii, nil
}
// ValidateCreateInstruments creates many instruments from an array of instruments
func ValidateCreateInstruments(db *sqlx.DB, instruments []Instrument) (CreateInstrumentsValidationResult, error) {
validationResult := CreateInstrumentsValidationResult{Errors: make([]string, 0)}
// Project IDs associated with instruments
projectIDs := make([]uuid.UUID, 0)
for idx := range instruments {
projectIDs = append(projectIDs, *instruments[idx].ProjectID)
}
// Get Map of Taken Instrument Names by Project
namesMap, err := projectInstrumentNamesMap(db, projectIDs)
if err != nil {
return validationResult, err
}
// Check that instrument names are unique name within project
validationResult.IsValid = true // Start with assumption that POST is valid
for _, n := range instruments {
if namesMap[*n.ProjectID][strings.ToUpper(n.Name)] != true {
continue
}
// Add message to Errors and make sure isValid is false
validationResult.IsValid = false
validationResult.Errors = append(
validationResult.Errors,
fmt.Sprintf("Instrument name '%s' is already taken. Instrument names must be unique within a project", n.Name),
)
}
return validationResult, nil
}
// UpdateInstrument updates a single instrument
func UpdateInstrument(db *sqlx.DB, i *Instrument) (*Instrument, error) {
txn, err := db.Begin()
if err != nil {
return nil, err
}
// Instrument
stmt1, err := txn.Prepare(
`UPDATE instrument
SET name = $3,
type_id = $4,
geometry = ST_GeomFromWKB($5),
updater = $6,
update_date = $7,
project_id = $8,
station = $9,
station_offset = $10,
formula = $11,
nid_id = $12,
usgs_id = $13
WHERE project_id = $1 AND id = $2
RETURNING id`,
)
// Update Instrument
var updatedID uuid.UUID
if err := stmt1.QueryRow(
i.ProjectID, i.ID, i.Name, i.TypeID, wkb.Value(i.Geometry.Geometry()),
i.Updater, i.UpdateDate, i.ProjectID, i.Station, i.StationOffset, i.Formula, i.NIDID, i.USGSID,
).Scan(&updatedID); err != nil {
return nil, err
}
if err := stmt1.Close(); err != nil {
return nil, err
}
// Instrument Status
stmt2, err := txn.Prepare(createInstrumentStatusSQL())
if err != nil {
return nil, err
}
if _, err := stmt2.Exec(i.ID, i.StatusID, i.StatusTime); err != nil {
return nil, err
}
if err := stmt2.Close(); err != nil {
return nil, err
}
if err := txn.Commit(); err != nil {
return nil, err
}
// Get Updated Row
return GetInstrument(db, &updatedID)
}
// UpdateInstrumentGeometry updates instrument geometry property
func UpdateInstrumentGeometry(db *sqlx.DB, projectID *uuid.UUID, instrumentID *uuid.UUID, geom *geojson.Geometry, p *Profile) (*Instrument, error) {
var nID uuid.UUID
if err := db.Get(
&nID, `UPDATE instrument SET geometry=ST_GeomFromWKB($3), updater=$4, update_date=now()
WHERE project_id=$1 AND id=$2
RETURNING id`, projectID, instrumentID, wkb.Value(geom.Geometry()), p.ID,
); err != nil {
return nil, err
}
return GetInstrument(db, &nID)
}
// DeleteFlagInstrument changes delete flag to true
func DeleteFlagInstrument(db *sqlx.DB, projectID, instrumentID *uuid.UUID) error {
if _, err := db.Exec(
`UPDATE instrument SET deleted = true WHERE project_id = $1 AND id = $2`,
projectID, instrumentID,
); err != nil {
return err
}
return nil
}
// InstrumentsFactory converts database rows to Instrument objects
func InstrumentsFactory(rows *sqlx.Rows) ([]Instrument, error) {
defer rows.Close()
ii := make([]Instrument, 0) // Instrument
for rows.Next() {
var i Instrument
var p orb.Point
err := rows.Scan(
&i.ID, &i.Deleted, &i.StatusID, &i.Status, &i.StatusTime, &i.Slug, &i.Name, &i.TypeID, &i.Type, wkb.Scanner(&p), &i.Station, &i.StationOffset,
&i.Creator, &i.CreateDate, &i.Updater, &i.UpdateDate, &i.ProjectID, pq.Array(&i.Constants), pq.Array(&i.Groups), pq.Array(&i.AlertConfigs),
&i.Formula, &i.NIDID, &i.USGSID,
)
if err != nil {
return make([]Instrument, 0), err
}
// Set Geometry field
i.Geometry = *geojson.NewGeometry(p)
// Add
ii = append(ii, i)
}
return ii, nil
}
// ListInstrumentsSQL is the base SQL to retrieve all instrumentsJSON
var listInstrumentsSQL = `SELECT id, deleted, status_id, status, status_time, slug,
name, type_id, type, geometry, station, station_offset, creator, create_date,
updater, update_date, project_id, constants, groups, alert_configs, formula, nid_id,
usgs_id FROM v_instrument
`