mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-24 13:48:49 +08:00

* MF-488 - Remove Thing type (app or device) Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Typo fix Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix reviews Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix reviews Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com> * Fix reviews Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
285 lines
6.2 KiB
Go
285 lines
6.2 KiB
Go
//
|
|
// Copyright (c) 2018
|
|
// Mainflux
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package postgres
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/jmoiron/sqlx"
|
|
"github.com/lib/pq" // required for DB access
|
|
"github.com/mainflux/mainflux/logger"
|
|
"github.com/mainflux/mainflux/things"
|
|
)
|
|
|
|
var _ things.ThingRepository = (*thingRepository)(nil)
|
|
|
|
type thingRepository struct {
|
|
db *sqlx.DB
|
|
log logger.Logger
|
|
}
|
|
|
|
// NewThingRepository instantiates a PostgreSQL implementation of thing
|
|
// repository.
|
|
func NewThingRepository(db *sqlx.DB, log logger.Logger) things.ThingRepository {
|
|
return &thingRepository{db: db, log: log}
|
|
}
|
|
|
|
func (tr thingRepository) Save(thing things.Thing) (string, error) {
|
|
q := `INSERT INTO things (id, owner, name, key, metadata)
|
|
VALUES (:id, :owner, :name, :key, :metadata);`
|
|
|
|
dbth, err := toDBThing(thing)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
_, err = tr.db.NamedExec(q, dbth)
|
|
if err != nil {
|
|
pqErr, ok := err.(*pq.Error)
|
|
if ok && errInvalid == pqErr.Code.Name() {
|
|
return "", things.ErrMalformedEntity
|
|
}
|
|
|
|
return "", err
|
|
}
|
|
|
|
return dbth.ID, nil
|
|
}
|
|
|
|
func (tr thingRepository) Update(thing things.Thing) error {
|
|
q := `UPDATE things SET name = :name, metadata = :metadata WHERE owner = :owner AND id = :id;`
|
|
|
|
dbth, err := toDBThing(thing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
res, err := tr.db.NamedExec(q, dbth)
|
|
if err != nil {
|
|
pqErr, ok := err.(*pq.Error)
|
|
if ok && errInvalid == pqErr.Code.Name() {
|
|
return things.ErrMalformedEntity
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
cnt, err := res.RowsAffected()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if cnt == 0 {
|
|
return things.ErrNotFound
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (tr thingRepository) RetrieveByID(owner, id string) (things.Thing, error) {
|
|
q := `SELECT name, key, metadata FROM things WHERE id = $1 AND owner = $2;`
|
|
|
|
dbth := dbThing{
|
|
ID: id,
|
|
Owner: owner,
|
|
}
|
|
|
|
if err := tr.db.QueryRowx(q, id, owner).StructScan(&dbth); err != nil {
|
|
empty := things.Thing{}
|
|
|
|
pqErr, ok := err.(*pq.Error)
|
|
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
|
|
return empty, things.ErrNotFound
|
|
}
|
|
|
|
return empty, err
|
|
}
|
|
|
|
return toThing(dbth)
|
|
}
|
|
|
|
func (tr thingRepository) RetrieveByKey(key string) (string, error) {
|
|
q := `SELECT id FROM things WHERE key = $1;`
|
|
var id string
|
|
if err := tr.db.QueryRowx(q, key).Scan(&id); err != nil {
|
|
if err == sql.ErrNoRows {
|
|
return "", things.ErrNotFound
|
|
}
|
|
return "", err
|
|
}
|
|
|
|
return id, nil
|
|
}
|
|
|
|
func (tr thingRepository) RetrieveAll(owner string, offset, limit uint64) things.ThingsPage {
|
|
q := `SELECT id, name, key, metadata FROM things
|
|
WHERE owner = :owner ORDER BY id LIMIT :limit OFFSET :offset;`
|
|
|
|
params := map[string]interface{}{
|
|
"owner": owner,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
}
|
|
|
|
rows, err := tr.db.NamedQuery(q, params)
|
|
if err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to retrieve things due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := []things.Thing{}
|
|
for rows.Next() {
|
|
dbth := dbThing{Owner: owner}
|
|
if err := rows.StructScan(&dbth); err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to read retrieved thing due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
|
|
th, err := toThing(dbth)
|
|
if err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to read retrieved thing due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
|
|
items = append(items, th)
|
|
}
|
|
|
|
q = `SELECT COUNT(*) FROM things WHERE owner = $1;`
|
|
|
|
var total uint64
|
|
if err := tr.db.Get(&total, q, owner); err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to count things due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
|
|
page := things.ThingsPage{
|
|
Things: items,
|
|
PageMetadata: things.PageMetadata{
|
|
Total: total,
|
|
Offset: offset,
|
|
Limit: limit,
|
|
},
|
|
}
|
|
|
|
return page
|
|
}
|
|
|
|
func (tr thingRepository) RetrieveByChannel(owner, channel string, offset, limit uint64) things.ThingsPage {
|
|
q := `SELECT id, name, key, metadata
|
|
FROM things th
|
|
INNER JOIN connections co
|
|
ON th.id = co.thing_id
|
|
WHERE th.owner = :owner AND co.channel_id = :channel
|
|
ORDER BY th.id
|
|
LIMIT :limit
|
|
OFFSET :offset;`
|
|
|
|
params := map[string]interface{}{
|
|
"owner": owner,
|
|
"channel": channel,
|
|
"limit": limit,
|
|
"offset": offset,
|
|
}
|
|
|
|
rows, err := tr.db.NamedQuery(q, params)
|
|
if err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to retrieve things due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := []things.Thing{}
|
|
for rows.Next() {
|
|
dbth := dbThing{Owner: owner}
|
|
if err := rows.StructScan(&dbth); err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to read retrieved thing due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
|
|
th, err := toThing(dbth)
|
|
if err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to read retrieved thing due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
|
|
items = append(items, th)
|
|
}
|
|
|
|
q = `SELECT COUNT(*)
|
|
FROM things th
|
|
INNER JOIN connections co
|
|
ON th.id = co.thing_id
|
|
WHERE th.owner = $1 AND co.channel_id = $2;`
|
|
|
|
var total uint64
|
|
if err := tr.db.Get(&total, q, owner, channel); err != nil {
|
|
tr.log.Error(fmt.Sprintf("Failed to count things due to %s", err))
|
|
return things.ThingsPage{}
|
|
}
|
|
|
|
return things.ThingsPage{
|
|
Things: items,
|
|
PageMetadata: things.PageMetadata{
|
|
Total: total,
|
|
Offset: offset,
|
|
Limit: limit,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (tr thingRepository) Remove(owner, id string) error {
|
|
dbth := dbThing{
|
|
ID: id,
|
|
Owner: owner,
|
|
}
|
|
q := `DELETE FROM things WHERE id = :id AND owner = :owner;`
|
|
tr.db.NamedExec(q, dbth)
|
|
return nil
|
|
}
|
|
|
|
type dbThing struct {
|
|
ID string `db:"id"`
|
|
Owner string `db:"owner"`
|
|
Name string `db:"name"`
|
|
Key string `db:"key"`
|
|
Metadata string `db:"metadata"`
|
|
}
|
|
|
|
func toDBThing(th things.Thing) (dbThing, error) {
|
|
data, err := json.Marshal(th.Metadata)
|
|
if err != nil {
|
|
return dbThing{}, err
|
|
}
|
|
|
|
return dbThing{
|
|
ID: th.ID,
|
|
Owner: th.Owner,
|
|
Name: th.Name,
|
|
Key: th.Key,
|
|
Metadata: string(data),
|
|
}, nil
|
|
}
|
|
|
|
func toThing(dbth dbThing) (things.Thing, error) {
|
|
var metadata map[string]interface{}
|
|
if err := json.Unmarshal([]byte(dbth.Metadata), &metadata); err != nil {
|
|
return things.Thing{}, err
|
|
}
|
|
|
|
return things.Thing{
|
|
ID: dbth.ID,
|
|
Owner: dbth.Owner,
|
|
Name: dbth.Name,
|
|
Key: dbth.Key,
|
|
Metadata: metadata,
|
|
}, nil
|
|
}
|