feat(analytics): dashbaord pgconn

This commit is contained in:
Shekar Siri 2024-12-16 10:36:25 +01:00
parent 00b7f65e31
commit 0a49df3996
7 changed files with 129 additions and 58 deletions

View file

@ -1,10 +1,11 @@
package models
package api
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"net/http"
"openreplay/backend/pkg/analytics/api/models"
"openreplay/backend/pkg/server/api"
"openreplay/backend/pkg/server/user"
"strconv"
@ -40,7 +41,7 @@ func (e *handlersImpl) createCard(w http.ResponseWriter, r *http.Request) {
}
bodySize = len(bodyBytes)
req := &CardCreateRequest{}
req := &models.CardCreateRequest{}
if err := json.Unmarshal(bodyBytes, req); err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
@ -55,8 +56,8 @@ func (e *handlersImpl) createCard(w http.ResponseWriter, r *http.Request) {
// TODO save card to DB
resp := &CardGetResponse{
Card: Card{
resp := &models.CardGetResponse{
Card: models.Card{
CardID: 1,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
@ -64,7 +65,7 @@ func (e *handlersImpl) createCard(w http.ResponseWriter, r *http.Request) {
EditedAt: nil,
ProjectID: 1,
UserID: 1,
CardBase: CardBase{
CardBase: models.CardBase{
Name: req.Name,
IsPublic: req.IsPublic,
Thumbnail: req.Thumbnail,
@ -96,8 +97,8 @@ func (e *handlersImpl) getCard(w http.ResponseWriter, r *http.Request) {
// TODO get card from DB
resp := &CardGetResponse{
Card: Card{
resp := &models.CardGetResponse{
Card: models.Card{
CardID: id,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
@ -105,7 +106,7 @@ func (e *handlersImpl) getCard(w http.ResponseWriter, r *http.Request) {
EditedAt: nil,
ProjectID: 1,
UserID: 1,
CardBase: CardBase{
CardBase: models.CardBase{
Name: "My Card",
IsPublic: true,
Thumbnail: &thumbnail,
@ -126,8 +127,8 @@ func (e *handlersImpl) getCards(w http.ResponseWriter, r *http.Request) {
// TODO get cards from DB
thumbnail := "https://example.com/image.png"
resp := &GetCardsResponse{
Cards: []Card{
resp := &models.GetCardsResponse{
Cards: []models.Card{
{
CardID: 1,
CreatedAt: time.Now(),
@ -136,7 +137,7 @@ func (e *handlersImpl) getCards(w http.ResponseWriter, r *http.Request) {
EditedAt: nil,
ProjectID: 1,
UserID: 1,
CardBase: CardBase{
CardBase: models.CardBase{
Name: "My Card",
IsPublic: true,
Thumbnail: &thumbnail,
@ -168,7 +169,7 @@ func (e *handlersImpl) updateCard(w http.ResponseWriter, r *http.Request) {
}
bodySize = len(bodyBytes)
req := &CardUpdateRequest{}
req := &models.CardUpdateRequest{}
if err := json.Unmarshal(bodyBytes, req); err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
@ -183,8 +184,8 @@ func (e *handlersImpl) updateCard(w http.ResponseWriter, r *http.Request) {
// TODO update card in DB
resp := &CardGetResponse{
Card: Card{
resp := &models.CardGetResponse{
Card: models.Card{
CardID: id,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
@ -192,7 +193,7 @@ func (e *handlersImpl) updateCard(w http.ResponseWriter, r *http.Request) {
EditedAt: nil,
ProjectID: 1,
UserID: 1,
CardBase: CardBase{
CardBase: models.CardBase{
Name: req.Name,
IsPublic: req.IsPublic,
Thumbnail: req.Thumbnail,
@ -231,7 +232,7 @@ func (e *handlersImpl) getCardChartData(w http.ResponseWriter, r *http.Request)
}
bodySize = len(bodyBytes)
req := &GetCardChartDataRequest{}
req := &models.GetCardChartDataRequest{}
if err := json.Unmarshal(bodyBytes, req); err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
@ -257,7 +258,7 @@ func (e *handlersImpl) getCardChartData(w http.ResponseWriter, r *http.Request)
]
}`
var resp GetCardChartDataResponse
var resp models.GetCardChartDataResponse
err = json.Unmarshal([]byte(jsonInput), &resp)
if err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)

View file

@ -1,26 +1,27 @@
package models
package api
import (
"encoding/json"
"fmt"
"github.com/gorilla/mux"
"net/http"
"openreplay/backend/pkg/analytics/api/models"
"openreplay/backend/pkg/server/api"
"openreplay/backend/pkg/server/user"
"strconv"
"time"
)
func getDashboardId(r *http.Request) (int, error) {
func getIDFromRequest(r *http.Request, key string) (int, error) {
vars := mux.Vars(r)
idStr := vars["id"]
idStr := vars[key]
if idStr == "" {
return 0, fmt.Errorf("invalid dashboard ID")
return 0, fmt.Errorf("missing %s in request", key)
}
id, err := strconv.Atoi(idStr)
if err != nil {
return 0, fmt.Errorf("invalid dashboard ID")
return 0, fmt.Errorf("invalid %s format", key)
}
return id, nil
@ -37,14 +38,14 @@ func (e *handlersImpl) createDashboard(w http.ResponseWriter, r *http.Request) {
}
bodySize = len(bodyBytes)
req := &CreateDashboardRequest{}
req := &models.CreateDashboardRequest{}
if err := json.Unmarshal(bodyBytes, req); err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
}
resp := &GetDashboardResponse{
Dashboard: Dashboard{
resp := &models.GetDashboardResponse{
Dashboard: models.Dashboard{
DashboardID: 1,
Name: req.Name,
Description: req.Description,
@ -64,23 +65,17 @@ func (e *handlersImpl) getDashboards(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
bodySize := 0
//id, err := getDashboardId(r)
//if err != nil {
// e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
// return
//}
projectID, err := getIDFromRequest(r, "projectId")
if err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
}
resp := &GetDashboardsResponse{
Dashboards: []Dashboard{
{
DashboardID: 1,
Name: "Dashboard",
Description: "Description",
IsPublic: true,
IsPinned: false,
},
},
Total: 1,
u := r.Context().Value("userData").(*user.User)
resp, err := e.service.GetDashboards(projectID, u.ID)
if err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
return
}
e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize)
@ -90,23 +85,31 @@ func (e *handlersImpl) getDashboard(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
bodySize := 0
id, err := getDashboardId(r)
projectID, err := getIDFromRequest(r, "projectId")
if err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
}
resp := &GetDashboardResponse{
Dashboard: Dashboard{
DashboardID: id,
Name: "Dashboard",
Description: "Description",
IsPublic: true,
IsPinned: false,
},
dashboardID, err := getIDFromRequest(r, "id")
if err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
}
e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize)
u := r.Context().Value("userData").(*user.User)
res, err := e.service.GetDashboard(projectID, dashboardID, u.ID)
if err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
return
}
if res == nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, fmt.Errorf("Dashboard not found"), startTime, r.URL.Path, bodySize)
return
}
e.responser.ResponseWithJSON(e.log, r.Context(), w, res, startTime, r.URL.Path, bodySize)
}
func (e *handlersImpl) updateDashboard(w http.ResponseWriter, r *http.Request) {
@ -126,14 +129,14 @@ func (e *handlersImpl) updateDashboard(w http.ResponseWriter, r *http.Request) {
}
bodySize = len(bodyBytes)
req := &UpdateDashboardRequest{}
req := &models.UpdateDashboardRequest{}
if err := json.Unmarshal(bodyBytes, req); err != nil {
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, err, startTime, r.URL.Path, bodySize)
return
}
resp := &GetDashboardResponse{
Dashboard: Dashboard{
resp := &models.GetDashboardResponse{
Dashboard: models.Dashboard{
DashboardID: 1,
Name: req.Name,
Description: req.Description,

View file

@ -1,4 +1,4 @@
package models
package api
import (
config "openreplay/backend/internal/config/analytics"

View file

@ -2,6 +2,7 @@ package models
type Dashboard struct {
DashboardID int `json:"dashboard_id"`
UserID int `json:"user_id"`
Name string `json:"name"`
Description string `json:"description"`
IsPublic bool `json:"is_public"`
@ -16,11 +17,15 @@ type GetDashboardResponse struct {
Dashboard
}
type GetDashboardsResponse struct {
type GetDashboardsResponsePaginated struct {
Dashboards []Dashboard `json:"dashboards"`
Total uint64 `json:"total"`
}
type GetDashboardsResponse struct {
Dashboards []Dashboard `json:"dashboards"`
}
// REQUESTS
type CreateDashboardRequest struct {

View file

@ -2,17 +2,20 @@ package service
import (
"errors"
"openreplay/backend/pkg/analytics/api/models"
"openreplay/backend/pkg/db/postgres/pool"
"openreplay/backend/pkg/logger"
"openreplay/backend/pkg/objectstorage"
)
type Service interface {
GetDashboard(projectId int, dashboardId int, userId uint64) (*models.GetDashboardResponse, error)
GetDashboards(projectId int, userId uint64) (*models.GetDashboardsResponse, error)
}
type serviceImpl struct {
log logger.Logger
conn pool.Pool
pgconn pool.Pool
storage objectstorage.ObjectStorage
}
@ -28,7 +31,7 @@ func NewService(log logger.Logger, conn pool.Pool, storage objectstorage.ObjectS
return &serviceImpl{
log: log,
conn: conn,
pgconn: conn,
storage: storage,
}, nil
}

View file

@ -0,0 +1,59 @@
package service
import (
"fmt"
"openreplay/backend/pkg/analytics/api/models"
)
func (s serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64) (*models.GetDashboardResponse, error) {
sql := `
SELECT dashboard_id, name, description, is_public, is_pinned, user_id
FROM dashboards
WHERE dashboard_id = $1 AND project_id = $2 AND deleted_at is null`
dashboard := &models.GetDashboardResponse{}
var ownerID int
err := s.pgconn.QueryRow(sql, dashboardID, projectId).Scan(&dashboard.DashboardID, &dashboard.Name, &dashboard.Description, &dashboard.IsPublic, &dashboard.IsPinned, &ownerID)
if err != nil {
return nil, err
}
if !dashboard.IsPublic && uint64(ownerID) != userID {
return nil, fmt.Errorf("access denied: user %d does not own dashboard %d", userID, dashboardID)
}
return dashboard, nil
}
func (s serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDashboardsResponse, error) {
sql := `
SELECT dashboard_id, user_id, name, description, is_public, is_pinned
FROM dashboards
WHERE (is_public = true OR user_id = $1) AND user_id IS NOT NULL AND deleted_at IS NULL AND project_id = $2
ORDER BY dashboard_id`
rows, err := s.pgconn.Query(sql, userID, projectId)
if err != nil {
return nil, err
}
defer rows.Close()
var dashboards []models.Dashboard
for rows.Next() {
var dashboard models.Dashboard
err := rows.Scan(&dashboard.DashboardID, &dashboard.UserID, &dashboard.Name, &dashboard.Description, &dashboard.IsPublic, &dashboard.IsPinned)
if err != nil {
return nil, err
}
dashboards = append(dashboards, dashboard)
}
if err := rows.Err(); err != nil {
return nil, err
}
return &models.GetDashboardsResponse{
Dashboards: dashboards,
}, nil
}