Skip to content

Commit

Permalink
Implement CRUD operations for dashboards
Browse files Browse the repository at this point in the history
Signed-off-by: felix.gateru <[email protected]>
  • Loading branch information
felixgateru committed Feb 1, 2024
1 parent 777141b commit 9e93b0d
Show file tree
Hide file tree
Showing 15 changed files with 725 additions and 176 deletions.
4 changes: 2 additions & 2 deletions cmd/ui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type config struct {
TLSVerification bool `env:"MG_UI_VERIFICATION_TLS" envDefault:"false"`
}

const envPrefix = "MG_UI_DB_"
const envDBPrefix = "MG_UI_DB_"

func main() {
cfg := config{}
Expand Down Expand Up @@ -75,7 +75,7 @@ func main() {

sdk := sdk.NewSDK(sdkConfig)

db, err := postgres.Setup(envPrefix, *repo.Migration())
db, err := postgres.Setup(envDBPrefix, *repo.Migration())
if err != nil {
log.Fatalf("failed to setup postgres db : %s", err)
} else {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ require (
github.com/mainflux/export v0.1.1-0.20230724124847-67d0bc7f38cb // indirect
github.com/mainflux/mainflux v0.12.0 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/oklog/ulid/v2 v2.1.0
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pkg/errors v0.9.1
github.com/prometheus/client_model v0.5.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,11 @@ github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OS
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68=
github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU=
github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ=
Expand Down
147 changes: 133 additions & 14 deletions postgres/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ package postgres

import (
"context"
"encoding/json"

"github.com/absmach/magistrala-ui/ui"
"github.com/absmach/magistrala/pkg/errors"
"github.com/jmoiron/sqlx"
)

Expand All @@ -18,40 +20,157 @@ func New(db *sqlx.DB) ui.DashboardRepository {
return &repo{db: db}
}

// Create or update dashboard layout for a user.
func (r *repo) Save(ctx context.Context, dashboard ui.Dashboard) error {
// Create a non-existing dashboard for a user.
func (r *repo) Create(ctx context.Context, dashboard ui.Dashboard) (Dashboard error) {
q := `
INSERT INTO dashboards (user_id, metadata)
VALUES (:user_id, :metadata)
ON CONFLICT (user_id)
DO UPDATE SET metadata = EXCLUDED.metadata`
INSERT INTO dashboards (dashboard_id, user_id, description, metadata, layout)
VALUES (:user_id, :user_id, :description, :metadata, :layout)`

if _, err := r.db.NamedQueryContext(ctx, q, dashboard); err != nil {
dbDs, err := toDBDashboard(dashboard)
if err != nil {
return err
}
if _, err := r.db.NamedQueryContext(ctx, q, dbDs); err != nil {
return HandleError(err, ErrCreateEntity)
}
return nil
}

// Get dashboard layout for a user.
func (r *repo) Get(ctx context.Context, id string) (ui.Dashboard, error) {
q := `SELECT * FROM dashboards WHERE user_id = :user_id`
var dashboard ui.Dashboard
// Retreive a dashboard using a dashboard id and user id.
func (r *repo) Retrieve(ctx context.Context, dashboardID, userID string) (ui.Dashboard, error) {
q := `SELECT dashboard_id, user_id, description, metadata, layout FROM dashboards WHERE dashboard_id = :dashboard_id AND user_id = :user_id`

tmp := ui.Dashboard{
UserID: id,
DashboardID: dashboardID,
UserID: userID,
}
rows, err := r.db.NamedQueryContext(ctx, q, tmp)
if err != nil {
return ui.Dashboard{}, HandleError(err, ErrViewEntity)
}
defer rows.Close()

dbDs := dbDashboard{}
if rows.Next() {
if err = rows.StructScan(&dashboard); err != nil {
if err = rows.StructScan(&dbDs); err != nil {
return ui.Dashboard{}, HandleError(err, ErrViewEntity)
}
return dashboard, nil
return toDashboard(dbDs)
}

return ui.Dashboard{}, ErrNotFound
}

// Retreive all dashboards for a user using a user id.
func (r *repo) RetrieveAll(ctx context.Context, userID string, page ui.DashboardPageMeta) (ui.DashboardPage, error) {
q := `SELECT dashboard_id, user_id, description, metadata, layout FROM dashboards WHERE user_id = :user_id`

tmp := ui.Dashboard{
UserID: userID,
}
rows, err := r.db.NamedQueryContext(ctx, q, tmp)
if err != nil {
return ui.DashboardPage{}, HandleError(err, ErrViewEntity)
}
defer rows.Close()

var dashboards []ui.Dashboard
for rows.Next() {
dbDs := dbDashboard{}
if err = rows.StructScan(&dbDs); err != nil {
return ui.DashboardPage{}, HandleError(err, ErrViewEntity)
}
ds, err := toDashboard(dbDs)
if err != nil {
return ui.DashboardPage{}, HandleError(err, ErrViewEntity)
}
dashboards = append(dashboards, ds)
}
cq := `SELECT COUNT(*) FROM dashboards WHERE user_id = :user_id`
var total uint64
if err := r.db.GetContext(ctx, &total, cq, tmp); err != nil {
return ui.DashboardPage{}, HandleError(err, ErrViewEntity)
}

return ui.DashboardPage{
Total: total,
Offset: page.Offset,
Limit: page.Limit,
Dashboards: dashboards,
}, nil
}

// Update an existing dashboard for a user.
func (r *repo) Update(ctx context.Context, dashboard ui.Dashboard) error {
q := `
UPDATE dashboards SET description = :description, metadata = :metadata, layout = :layout
WHERE dashboard_id = :dashboard_id AND user_id = :user_id`

dbDs, err := toDBDashboard(dashboard)
if err != nil {
return err
}
res, err := r.db.NamedExecContext(ctx, q, dbDs)
if err != nil {
return HandleError(err, ErrCreateEntity)
}
if rows, _ := res.RowsAffected(); rows == 0 {
return ErrNotFound
}
return nil
}

// Delete an existing dashboard for a user.
func (r *repo) Delete(ctx context.Context, dashboardID, userID string) error {
q := `DELETE FROM dashboards WHERE dashboard_id = :dashboard_id AND user_id = :user_id`

tmp := ui.Dashboard{
DashboardID: dashboardID,
UserID: userID,
}
res, err := r.db.NamedExecContext(ctx, q, tmp)
if err != nil {
return HandleError(err, ErrCreateEntity)
}
if rows, _ := res.RowsAffected(); rows == 0 {
return ErrNotFound
}
return nil
}

type dbDashboard struct {
DashboardID string `db:"dashboard_id"`
UserID string `db:"user_id"`
Description string `db:"description"`
Metadata string `db:"metadata"`
Layout []byte `db:"layout"`
}

func toDBDashboard(ds ui.Dashboard) (dbDashboard, error) {
lt, err := json.Marshal(ds.Layout)
if err != nil {
return dbDashboard{}, errors.Wrap(ErrJSONMarshal, err)
}
return dbDashboard{
DashboardID: ds.DashboardID,
UserID: ds.UserID,
Description: ds.Description,
Metadata: ds.Metadata,
Layout: lt,
}, nil
}

func toDashboard(dsDB dbDashboard) (ui.Dashboard, error) {
var lt string
err := json.Unmarshal(dsDB.Layout, &lt)
if err != nil {
return ui.Dashboard{}, errors.Wrap(ErrJSONUnmarshal, err)
}
return ui.Dashboard{
DashboardID: dsDB.DashboardID,
UserID: dsDB.UserID,
Description: dsDB.Description,
Metadata: dsDB.Metadata,
Layout: lt,
}, nil
}
2 changes: 2 additions & 0 deletions postgres/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ var (
ErrMalformedEntity = errors.New("malformed entity specification")
ErrViewEntity = errors.New("view entity failed")
ErrNotFound = errors.New("entity not found")
ErrJSONMarshal = errors.New("failed to marshal entity to json")
ErrJSONUnmarshal = errors.New("failed to unmarshal entity from json")
)

func HandleError(err, wrapper error) error {
Expand Down
14 changes: 8 additions & 6 deletions postgres/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ func Migration() *migrate.MemoryMigrationSource {
Id: "dashboard_01",
Up: []string{
`CREATE TABLE IF NOT EXISTS dashboards (
user_id VARCHAR(36) NOT NULL,
metadata TEXT,
UNIQUE (user_id),
PRIMARY KEY (user_id)
);
INSERT INTO dashboards (user_id, metadata) VALUES ('admin', '');`,
dashboard_id VARCHAR(36) NOT NULL
user_id VARCHAR(36) NOT NULL,
description TEXT,
metadata TEXT,
layout JSONB,
UNIQUE (dashboard_id),
PRIMARY KEY (dashboard_id)
);`,
},
Down: []string{
`DROP TABLE IF EXISTS dashboards`,
Expand Down
72 changes: 66 additions & 6 deletions ui/api/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -2014,32 +2014,92 @@ func deleteInvitationEndpoint(svc ui.Service) endpoint.Endpoint {
}
}

func viewDashboardsEndpoint(svc ui.Service) endpoint.Endpoint {
func viewDashboardEndpoint(svc ui.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(dashboardsReq)
req := request.(viewDashboardReq)
if err := req.validate(); err != nil {
return nil, err
}
res, err := svc.ViewDashboards(req.token)

res, err := svc.ViewDashboard(req.token, req.DashboardID)
if err != nil {
return nil, err
}

return uiRes{
code: http.StatusOK,
html: res,
}, nil
}
}

func saveDashboardsEndPoint(svc ui.Service) endpoint.Endpoint {
func createDashboardEndpoint(svc ui.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(createDashboardReq)
if err := req.validate(); err != nil {
return nil, err
}

res, err := svc.CreateDashboard(req.token, req.Description, req.Metadata, req.Layout)
if err != nil {
return nil, err
}
return uiRes{
code: http.StatusCreated,
html: res,
}, nil
}
}

func listDashboardsEndpoint(svc ui.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(saveDashboardsReq)
req := request.(listDashboardsReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.SaveDashboards(req.token, req.Metadata); err != nil {

res, err := svc.ListDashboards(req.token, req.page, req.limit)
if err != nil {
return nil, err
}

return uiRes{
code: http.StatusOK,
html: res,
}, nil
}
}

func updateDashboardEndpoint(svc ui.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateDashboardReq)
if err := req.validate(); err != nil {
return nil, err
}

res, err := svc.UpdateDashboard(req.token, req.DashboardID, req.Description, req.Metadata, req.Layout)
if err != nil {
return nil, err
}

return uiRes{
code: http.StatusOK,
html: res,
}, nil
}
}

func deleteDashboardEndpoint(svc ui.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(deleteDashboardReq)
if err := req.validate(); err != nil {
return nil, err
}

if err := svc.DeleteDashboard(req.token, req.DashboardID); err != nil {
return nil, err
}

return uiRes{
code: http.StatusOK,
}, nil
Expand Down
Loading

0 comments on commit 9e93b0d

Please sign in to comment.