diff --git a/backend/pkg/analytics/api/card-handlers.go b/backend/pkg/analytics/api/card-handlers.go index 41079c330..edb8e3559 100644 --- a/backend/pkg/analytics/api/card-handlers.go +++ b/backend/pkg/analytics/api/card-handlers.go @@ -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) diff --git a/backend/pkg/analytics/api/dashboard-handlers.go b/backend/pkg/analytics/api/dashboard-handlers.go index 777180847..7e94aa16b 100644 --- a/backend/pkg/analytics/api/dashboard-handlers.go +++ b/backend/pkg/analytics/api/dashboard-handlers.go @@ -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, diff --git a/backend/pkg/analytics/api/handlers.go b/backend/pkg/analytics/api/handlers.go index e3ad6ac6d..010a8cd78 100644 --- a/backend/pkg/analytics/api/handlers.go +++ b/backend/pkg/analytics/api/handlers.go @@ -1,4 +1,4 @@ -package models +package api import ( config "openreplay/backend/internal/config/analytics" diff --git a/backend/pkg/analytics/api/card.go b/backend/pkg/analytics/api/models/card.go similarity index 100% rename from backend/pkg/analytics/api/card.go rename to backend/pkg/analytics/api/models/card.go diff --git a/backend/pkg/analytics/api/model.go b/backend/pkg/analytics/api/models/model.go similarity index 91% rename from backend/pkg/analytics/api/model.go rename to backend/pkg/analytics/api/models/model.go index a5c231159..f69b1684a 100644 --- a/backend/pkg/analytics/api/model.go +++ b/backend/pkg/analytics/api/models/model.go @@ -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 { diff --git a/backend/pkg/analytics/service/analytics.go b/backend/pkg/analytics/service/analytics.go index ce36b0958..ddb2aeee3 100644 --- a/backend/pkg/analytics/service/analytics.go +++ b/backend/pkg/analytics/service/analytics.go @@ -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 } diff --git a/backend/pkg/analytics/service/dashboard-service.go b/backend/pkg/analytics/service/dashboard-service.go new file mode 100644 index 000000000..ab7d80ec2 --- /dev/null +++ b/backend/pkg/analytics/service/dashboard-service.go @@ -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 +}