feat(analytics): dashbaord check existence and paginated methods
This commit is contained in:
parent
64d9029554
commit
af761693aa
4 changed files with 166 additions and 20 deletions
|
|
@ -104,12 +104,14 @@ func (e *handlersImpl) getDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
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)
|
||||
// Map errors to appropriate HTTP status codes
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
} else if err.Error() == "access_denied: user does not have access" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize)
|
||||
} else {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -139,6 +141,20 @@ func (e *handlersImpl) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
u := r.Context().Value("userData").(*user.User)
|
||||
_, err = e.service.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
// Map errors to appropriate HTTP status codes
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
} else if err.Error() == "access_denied: user does not have access" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize)
|
||||
} else {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -168,6 +184,19 @@ func (e *handlersImpl) deleteDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
u := r.Context().Value("userData").(*user.User)
|
||||
_, err = e.service.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
// Map errors to appropriate HTTP status codes
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
} else if err.Error() == "access_denied: user does not have access" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusForbidden, err, startTime, r.URL.Path, bodySize)
|
||||
} else {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = e.service.DeleteDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
|
|
|
|||
|
|
@ -42,9 +42,10 @@ type CreateDashboardRequest struct {
|
|||
type GetDashboardsRequest struct {
|
||||
Page uint64 `json:"page"`
|
||||
Limit uint64 `json:"limit"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
Order string `json:"order"`
|
||||
Query string `json:"query"`
|
||||
FilterBy string `json:"filterBy"`
|
||||
OrderBy string `json:"orderBy"`
|
||||
}
|
||||
|
||||
type UpdateDashboardRequest struct {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
type Service interface {
|
||||
GetDashboard(projectId int, dashboardId int, userId uint64) (*models.GetDashboardResponse, error)
|
||||
GetDashboardsPaginated(projectId int, userId uint64, req *models.GetDashboardsRequest) (*models.GetDashboardsResponsePaginated, error)
|
||||
GetDashboards(projectId int, userId uint64) (*models.GetDashboardsResponse, error)
|
||||
CreateDashboard(projectId int, userId uint64, req *models.CreateDashboardRequest) (*models.GetDashboardResponse, error)
|
||||
UpdateDashboard(projectId int, dashboardId int, userId uint64, req *models.UpdateDashboardRequest) (*models.GetDashboardResponse, error)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
)
|
||||
|
||||
// CreateDashboard Create a new dashboard
|
||||
func (s serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.CreateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
sql := `
|
||||
INSERT INTO dashboards (project_id, user_id, name, description, is_public, is_pinned)
|
||||
|
|
@ -12,7 +14,6 @@ func (s serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.C
|
|||
RETURNING dashboard_id, project_id, user_id, name, description, is_public, is_pinned`
|
||||
|
||||
dashboard := &models.GetDashboardResponse{}
|
||||
|
||||
err := s.pgconn.QueryRow(sql, projectId, userID, req.Name, req.Description, req.IsPublic, req.IsPinned).Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.ProjectID,
|
||||
|
|
@ -23,27 +24,40 @@ func (s serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.C
|
|||
&dashboard.IsPinned,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("failed to create dashboard: %w", err)
|
||||
}
|
||||
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
// GetDashboard Fetch a specific dashboard by ID
|
||||
func (s serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64) (*models.GetDashboardResponse, error) {
|
||||
sql := `
|
||||
SELECT dashboard_id, project_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{}
|
||||
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.ProjectID, &dashboard.Name, &dashboard.Description, &dashboard.IsPublic, &dashboard.IsPinned, &ownerID)
|
||||
err := s.pgconn.QueryRow(sql, dashboardID, projectId).Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.ProjectID,
|
||||
&dashboard.Name,
|
||||
&dashboard.Description,
|
||||
&dashboard.IsPublic,
|
||||
&dashboard.IsPinned,
|
||||
&ownerID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err.Error() == "no rows in result set" {
|
||||
return nil, errors.New("not_found: dashboard not found")
|
||||
}
|
||||
return nil, fmt.Errorf("error fetching dashboard: %w", err)
|
||||
}
|
||||
|
||||
// Access control
|
||||
if !dashboard.IsPublic && uint64(ownerID) != userID {
|
||||
return nil, fmt.Errorf("access denied: user %d does not own dashboard %d", userID, dashboardID)
|
||||
return nil, fmt.Errorf("access_denied: user does not have access")
|
||||
}
|
||||
|
||||
return dashboard, nil
|
||||
|
|
@ -83,15 +97,64 @@ func (s serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDas
|
|||
}, nil
|
||||
}
|
||||
|
||||
// GetDashboardsPaginated Fetch dashboards with pagination
|
||||
func (s serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *models.GetDashboardsRequest) (*models.GetDashboardsResponsePaginated, error) {
|
||||
baseSQL, args := buildBaseQuery(projectId, userID, req)
|
||||
|
||||
// Count total dashboards
|
||||
countSQL := fmt.Sprintf("SELECT COUNT(*) FROM (%s) AS count_query", baseSQL)
|
||||
var total uint64
|
||||
err := s.pgconn.QueryRow(countSQL, args...).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error counting dashboards: %w", err)
|
||||
}
|
||||
|
||||
// Fetch paginated dashboards
|
||||
paginatedSQL := fmt.Sprintf("%s ORDER BY %s %s LIMIT $%d OFFSET $%d",
|
||||
baseSQL, getOrderBy(req.OrderBy), getOrder(req.Order), len(args)+1, len(args)+2)
|
||||
args = append(args, req.Limit, req.Limit*(req.Page-1))
|
||||
|
||||
rows, err := s.pgconn.Query(paginatedSQL, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching paginated dashboards: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dashboards []models.Dashboard
|
||||
for rows.Next() {
|
||||
var dashboard models.Dashboard
|
||||
err := rows.Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.UserID,
|
||||
&dashboard.ProjectID,
|
||||
&dashboard.Name,
|
||||
&dashboard.Description,
|
||||
&dashboard.IsPublic,
|
||||
&dashboard.IsPinned,
|
||||
&dashboard.OwnerEmail,
|
||||
&dashboard.OwnerName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error scanning dashboard: %w", err)
|
||||
}
|
||||
dashboards = append(dashboards, dashboard)
|
||||
}
|
||||
|
||||
return &models.GetDashboardsResponsePaginated{
|
||||
Dashboards: dashboards,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateDashboard Update a dashboard
|
||||
func (s serviceImpl) UpdateDashboard(projectId int, dashboardID int, userID uint64, req *models.UpdateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
sql := `
|
||||
UPDATE dashboards
|
||||
SET name = $1, description = $2, is_public = $3, is_pinned = $4
|
||||
WHERE dashboard_id = $5 AND project_id = $6 AND user_id = $7
|
||||
WHERE dashboard_id = $5 AND project_id = $6 AND user_id = $7 AND deleted_at IS NULL
|
||||
RETURNING dashboard_id, project_id, user_id, name, description, is_public, is_pinned`
|
||||
|
||||
dashboard := &models.GetDashboardResponse{}
|
||||
|
||||
err := s.pgconn.QueryRow(sql, req.Name, req.Description, req.IsPublic, req.IsPinned, dashboardID, projectId, userID).Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.ProjectID,
|
||||
|
|
@ -102,12 +165,12 @@ func (s serviceImpl) UpdateDashboard(projectId int, dashboardID int, userID uint
|
|||
&dashboard.IsPinned,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("error updating dashboard: %w", err)
|
||||
}
|
||||
|
||||
return dashboard, nil
|
||||
}
|
||||
|
||||
// DeleteDashboard Soft-delete a dashboard
|
||||
func (s serviceImpl) DeleteDashboard(projectId int, dashboardID int, userID uint64) error {
|
||||
sql := `
|
||||
UPDATE dashboards
|
||||
|
|
@ -116,8 +179,60 @@ func (s serviceImpl) DeleteDashboard(projectId int, dashboardID int, userID uint
|
|||
|
||||
err := s.pgconn.Exec(sql, dashboardID, projectId, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("error deleting dashboard: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to build the base query for dashboards
|
||||
func buildBaseQuery(projectId int, userID uint64, req *models.GetDashboardsRequest) (string, []interface{}) {
|
||||
var conditions []string
|
||||
args := []interface{}{projectId}
|
||||
|
||||
conditions = append(conditions, "d.project_id = $1")
|
||||
|
||||
// Handle is_public filter
|
||||
if req.IsPublic {
|
||||
conditions = append(conditions, "d.is_public = true")
|
||||
} else {
|
||||
conditions = append(conditions, "(d.is_public = true OR d.user_id = $2)")
|
||||
args = append(args, userID)
|
||||
}
|
||||
|
||||
// Handle search query
|
||||
if req.Query != "" {
|
||||
conditions = append(conditions, "(d.name ILIKE $3 OR d.description ILIKE $3)")
|
||||
args = append(args, "%"+req.Query+"%")
|
||||
}
|
||||
|
||||
conditions = append(conditions, "d.deleted_at IS NULL")
|
||||
whereClause := "WHERE " + fmt.Sprint(conditions)
|
||||
|
||||
baseSQL := fmt.Sprintf(`
|
||||
SELECT d.dashboard_id, d.user_id, d.project_id, d.name, d.description, d.is_public, d.is_pinned,
|
||||
u.email AS owner_email, u.name AS owner_name
|
||||
FROM dashboards d
|
||||
LEFT JOIN users u ON d.user_id = u.user_id
|
||||
%s`, whereClause)
|
||||
|
||||
return baseSQL, args
|
||||
}
|
||||
|
||||
func getOrderBy(orderBy string) string {
|
||||
if orderBy == "" {
|
||||
return "d.dashboard_id"
|
||||
}
|
||||
allowed := map[string]bool{"dashboard_id": true, "name": true, "description": true}
|
||||
if allowed[orderBy] {
|
||||
return fmt.Sprintf("d.%s", orderBy)
|
||||
}
|
||||
return "d.dashboard_id"
|
||||
}
|
||||
|
||||
func getOrder(order string) string {
|
||||
if order == "DESC" {
|
||||
return "DESC"
|
||||
}
|
||||
return "ASC"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue