feat(analytics): small refactoring of the service's architecture
This commit is contained in:
parent
5fe204020f
commit
f05e84777b
11 changed files with 270 additions and 322 deletions
|
|
@ -37,7 +37,7 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(ctx, "failed while creating router: %s", err)
|
||||
}
|
||||
router.AddHandlers(api.NoPrefix, builder.AnalyticsAPI)
|
||||
router.AddHandlers(api.NoPrefix, builder.CardsAPI, builder.DashboardsAPI)
|
||||
router.AddMiddlewares(builder.Auth.Middleware, builder.RateLimiter.Middleware, builder.AuditTrail.Middleware)
|
||||
|
||||
server.Run(ctx, log, &cfg.HTTP, router)
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// CardBase Common fields for the Card entity
|
||||
type CardBase struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
IsPublic bool `json:"isPublic" validate:"omitempty"`
|
||||
DefaultConfig map[string]any `json:"defaultConfig"`
|
||||
Thumbnail *string `json:"thumbnail" validate:"omitempty,url"`
|
||||
MetricType string `json:"metricType" validate:"required,oneof=timeseries table funnel"`
|
||||
MetricOf string `json:"metricOf" validate:"required,oneof=session_count user_count"`
|
||||
MetricFormat string `json:"metricFormat" validate:"required,oneof=default percentage"`
|
||||
ViewType string `json:"viewType" validate:"required,oneof=line_chart table_view"`
|
||||
MetricValue []string `json:"metricValue" validate:"omitempty"`
|
||||
SessionID *int64 `json:"sessionId" validate:"omitempty"`
|
||||
Series []CardSeries `json:"series" validate:"required,dive"`
|
||||
}
|
||||
|
||||
// Card Fields specific to database operations
|
||||
type Card struct {
|
||||
CardBase
|
||||
ProjectID int64 `json:"projectId" validate:"required"`
|
||||
UserID int64 `json:"userId" validate:"required"`
|
||||
CardID int64 `json:"cardId"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||
EditedAt *time.Time `json:"edited_at,omitempty"`
|
||||
}
|
||||
|
||||
type CardSeries struct {
|
||||
SeriesID int64 `json:"seriesId" validate:"omitempty"`
|
||||
MetricID int64 `json:"metricId" validate:"omitempty"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
CreatedAt time.Time `json:"createdAt" validate:"omitempty"`
|
||||
DeletedAt *time.Time `json:"deletedAt" validate:"omitempty"`
|
||||
Index int64 `json:"index" validate:"required"`
|
||||
Filter SeriesFilter `json:"filter"`
|
||||
}
|
||||
|
||||
type SeriesFilter struct {
|
||||
EventOrder string `json:"eventOrder" validate:"required,oneof=then or and"`
|
||||
Filters []FilterItem `json:"filters"`
|
||||
}
|
||||
|
||||
type FilterItem struct {
|
||||
Type string `json:"type" validate:"required"`
|
||||
Operator string `json:"operator" validate:"required"`
|
||||
Source string `json:"source" validate:"required"`
|
||||
SourceOperator string `json:"sourceOperator" validate:"required"`
|
||||
Value []string `json:"value" validate:"required,dive,required"`
|
||||
IsEvent bool `json:"isEvent"`
|
||||
}
|
||||
|
||||
// CardCreateRequest Fields required for creating a card (from the frontend)
|
||||
type CardCreateRequest struct {
|
||||
CardBase
|
||||
}
|
||||
|
||||
type CardGetResponse struct {
|
||||
Card
|
||||
}
|
||||
|
||||
type CardUpdateRequest struct {
|
||||
CardBase
|
||||
}
|
||||
|
||||
type GetCardsResponse struct {
|
||||
Cards []Card `json:"cards"`
|
||||
Total int64 `json:"total"`
|
||||
}
|
||||
|
||||
type DataPoint struct {
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Series map[string]int64 `json:"series"`
|
||||
}
|
||||
|
||||
type GetCardChartDataRequest struct {
|
||||
ProjectID int64 `json:"projectId" validate:"required"`
|
||||
MetricType string `json:"metricType" validate:"required,oneof=timeseries table funnel"`
|
||||
MetricOf string `json:"metricOf" validate:"required,oneof=session_count user_count"`
|
||||
MetricFormat string `json:"metricFormat" validate:"required,oneof=default percentage"`
|
||||
SessionID int64 `json:"sessionId" validate:"required"`
|
||||
Series []CardSeries `json:"series"`
|
||||
}
|
||||
|
||||
type GetCardChartDataResponse struct {
|
||||
Data []DataPoint `json:"data"`
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
config "openreplay/backend/internal/config/analytics"
|
||||
"openreplay/backend/pkg/analytics/service"
|
||||
"openreplay/backend/pkg/logger"
|
||||
"openreplay/backend/pkg/server/api"
|
||||
)
|
||||
|
||||
type handlersImpl struct {
|
||||
log logger.Logger
|
||||
responser *api.Responser
|
||||
jsonSizeLimit int64
|
||||
service service.Service
|
||||
}
|
||||
|
||||
func (e *handlersImpl) GetAll() []*api.Description {
|
||||
return []*api.Description{
|
||||
{"/v1/analytics/{projectId}/dashboards", e.createDashboard, "POST"},
|
||||
{"/v1/analytics/{projectId}/dashboards", e.getDashboards, "GET"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.getDashboard, "GET"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.updateDashboard, "PUT"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.deleteDashboard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}/cards", e.addCardToDashboard, "POST"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}/cards/{cardId}", e.removeCardFromDashboard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/cards", e.createCard, "POST"},
|
||||
{"/v1/analytics/{projectId}/cards", e.getCardsPaginated, "GET"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.getCard, "GET"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.updateCard, "PUT"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.deleteCard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}/chart", e.getCardChartData, "POST"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}/try", e.getCardChartData, "POST"},
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandlers(log logger.Logger, cfg *config.Config, responser *api.Responser, service service.Service) (api.Handlers, error) {
|
||||
return &handlersImpl{
|
||||
log: log,
|
||||
responser: responser,
|
||||
jsonSizeLimit: cfg.JsonSizeLimit,
|
||||
service: service,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getIDFromRequest(r *http.Request, key string) (int, error) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars[key]
|
||||
if idStr == "" {
|
||||
return 0, fmt.Errorf("missing %s in request", key)
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s format", key)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"time"
|
||||
|
||||
"openreplay/backend/internal/config/analytics"
|
||||
analyticsAPI "openreplay/backend/pkg/analytics/api"
|
||||
"openreplay/backend/pkg/analytics/service"
|
||||
"openreplay/backend/pkg/analytics/cards"
|
||||
"openreplay/backend/pkg/analytics/dashboards"
|
||||
"openreplay/backend/pkg/db/postgres/pool"
|
||||
"openreplay/backend/pkg/logger"
|
||||
"openreplay/backend/pkg/metrics/web"
|
||||
|
|
@ -16,34 +16,40 @@ import (
|
|||
)
|
||||
|
||||
type ServicesBuilder struct {
|
||||
Auth auth.Auth
|
||||
RateLimiter *limiter.UserRateLimiter
|
||||
AuditTrail tracer.Tracer
|
||||
AnalyticsAPI api.Handlers
|
||||
Auth auth.Auth
|
||||
RateLimiter *limiter.UserRateLimiter
|
||||
AuditTrail tracer.Tracer
|
||||
CardsAPI api.Handlers
|
||||
DashboardsAPI api.Handlers
|
||||
}
|
||||
|
||||
func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, webMetrics web.Web, pgconn pool.Pool) (*ServicesBuilder, error) {
|
||||
responser := api.NewResponser(webMetrics)
|
||||
|
||||
audiTrail, err := tracer.NewTracer(log, pgconn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
analyticsService, err := service.NewService(log, pgconn)
|
||||
cardsService, err := cards.New(log, pgconn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handlers, err := analyticsAPI.NewHandlers(log, cfg, responser, analyticsService)
|
||||
cardsHandlers, err := cards.NewHandlers(log, cfg, responser, cardsService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dashboardsService, err := dashboards.New(log, pgconn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dashboardsHandlers, err := dashboards.NewHandlers(log, cfg, responser, dashboardsService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ServicesBuilder{
|
||||
Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, nil),
|
||||
RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute),
|
||||
AuditTrail: audiTrail,
|
||||
AnalyticsAPI: handlers,
|
||||
Auth: auth.NewAuth(log, cfg.JWTSecret, cfg.JWTSpotSecret, pgconn, nil),
|
||||
RateLimiter: limiter.NewUserRateLimiter(10, 30, 1*time.Minute, 5*time.Minute),
|
||||
AuditTrail: audiTrail,
|
||||
CardsAPI: cardsHandlers,
|
||||
DashboardsAPI: dashboardsHandlers,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,42 @@
|
|||
package service
|
||||
package cards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jackc/pgx/v4"
|
||||
"github.com/lib/pq"
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
"strings"
|
||||
|
||||
"openreplay/backend/pkg/db/postgres/pool"
|
||||
"openreplay/backend/pkg/logger"
|
||||
)
|
||||
|
||||
func (s *serviceImpl) CreateCard(projectId int, userID uint64, req *models.CardCreateRequest) (*models.CardGetResponse, error) {
|
||||
type Cards interface {
|
||||
GetCard(projectId int, cardId int) (*CardGetResponse, error)
|
||||
GetCardWithSeries(projectId int, cardId int) (*CardGetResponse, error)
|
||||
GetCards(projectId int) (*GetCardsResponse, error)
|
||||
GetCardsPaginated(projectId int, filters CardListFilter, sort CardListSort, limit int, offset int) (*GetCardsResponsePaginated, error)
|
||||
CreateCard(projectId int, userId uint64, req *CardCreateRequest) (*CardGetResponse, error)
|
||||
UpdateCard(projectId int, cardId int64, userId uint64, req *CardUpdateRequest) (*CardGetResponse, error)
|
||||
DeleteCard(projectId int, cardId int64, userId uint64) error
|
||||
GetCardChartData(projectId int, userId uint64, req *GetCardChartDataRequest) ([]DataPoint, error)
|
||||
}
|
||||
|
||||
type cardsImpl struct {
|
||||
log logger.Logger
|
||||
pgconn pool.Pool
|
||||
}
|
||||
|
||||
func New(log logger.Logger, conn pool.Pool) (Cards, error) {
|
||||
return &cardsImpl{
|
||||
log: log,
|
||||
pgconn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *cardsImpl) CreateCard(projectId int, userID uint64, req *CardCreateRequest) (*CardGetResponse, error) {
|
||||
if req.MetricValue == nil {
|
||||
req.MetricValue = []string{}
|
||||
}
|
||||
|
|
@ -41,7 +67,7 @@ func (s *serviceImpl) CreateCard(projectId int, userID uint64, req *models.CardC
|
|||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING metric_id, project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format, is_public, created_at, edited_at`
|
||||
|
||||
card := &models.CardGetResponse{}
|
||||
card := &CardGetResponse{}
|
||||
err = tx.QueryRow(
|
||||
ctx, sql,
|
||||
projectId, userID, req.Name, req.MetricType, req.ViewType, req.MetricOf, req.MetricValue, req.MetricFormat, req.IsPublic,
|
||||
|
|
@ -73,7 +99,7 @@ func (s *serviceImpl) CreateCard(projectId int, userID uint64, req *models.CardC
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int64, series []models.CardSeriesBase) []models.CardSeries {
|
||||
func (s *cardsImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int64, series []CardSeriesBase) []CardSeries {
|
||||
if len(series) == 0 {
|
||||
return nil // No series to create
|
||||
}
|
||||
|
|
@ -114,9 +140,9 @@ func (s *serviceImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int6
|
|||
}
|
||||
|
||||
// Collect inserted series
|
||||
var seriesList []models.CardSeries
|
||||
var seriesList []CardSeries
|
||||
for rows.Next() {
|
||||
cardSeries := models.CardSeries{}
|
||||
cardSeries := CardSeries{}
|
||||
if err := rows.Scan(&cardSeries.SeriesID, &cardSeries.MetricID, &cardSeries.Name, &cardSeries.Index, &cardSeries.Filter); err != nil {
|
||||
s.log.Error(ctx, "failed to scan series: %v", err)
|
||||
continue
|
||||
|
|
@ -127,13 +153,13 @@ func (s *serviceImpl) CreateSeries(ctx context.Context, tx pgx.Tx, metricId int6
|
|||
return seriesList
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetCard(projectId int, cardID int) (*models.CardGetResponse, error) {
|
||||
func (s *cardsImpl) GetCard(projectId int, cardID int) (*CardGetResponse, error) {
|
||||
sql :=
|
||||
`SELECT metric_id, project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format, is_public, created_at, edited_at
|
||||
FROM public.metrics
|
||||
WHERE metric_id = $1 AND project_id = $2 AND deleted_at IS NULL`
|
||||
|
||||
card := &models.CardGetResponse{}
|
||||
card := &CardGetResponse{}
|
||||
err := s.pgconn.QueryRow(sql, cardID, projectId).Scan(
|
||||
&card.CardID, &card.ProjectID, &card.UserID, &card.Name, &card.MetricType, &card.ViewType, &card.MetricOf, &card.MetricValue, &card.MetricFormat, &card.IsPublic, &card.CreatedAt, &card.EditedAt,
|
||||
)
|
||||
|
|
@ -145,7 +171,7 @@ func (s *serviceImpl) GetCard(projectId int, cardID int) (*models.CardGetRespons
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetCardWithSeries(projectId int, cardID int) (*models.CardGetResponse, error) {
|
||||
func (s *cardsImpl) GetCardWithSeries(projectId int, cardID int) (*CardGetResponse, error) {
|
||||
sql := `
|
||||
SELECT m.metric_id, m.project_id, m.user_id, m.name, m.metric_type, m.view_type, m.metric_of,
|
||||
m.metric_value, m.metric_format, m.is_public, m.created_at, m.edited_at,
|
||||
|
|
@ -166,7 +192,7 @@ func (s *serviceImpl) GetCardWithSeries(projectId int, cardID int) (*models.Card
|
|||
m.metric_of, m.metric_value, m.metric_format, m.is_public, m.created_at, m.edited_at
|
||||
`
|
||||
|
||||
card := &models.CardGetResponse{}
|
||||
card := &CardGetResponse{}
|
||||
var seriesJSON []byte
|
||||
err := s.pgconn.QueryRow(sql, cardID, projectId).Scan(
|
||||
&card.CardID, &card.ProjectID, &card.UserID, &card.Name, &card.MetricType, &card.ViewType, &card.MetricOf,
|
||||
|
|
@ -184,7 +210,7 @@ func (s *serviceImpl) GetCardWithSeries(projectId int, cardID int) (*models.Card
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetCards(projectId int) (*models.GetCardsResponse, error) {
|
||||
func (s *cardsImpl) GetCards(projectId int) (*GetCardsResponse, error) {
|
||||
sql := `
|
||||
SELECT metric_id, project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format, is_public, created_at, edited_at
|
||||
FROM public.metrics
|
||||
|
|
@ -196,9 +222,9 @@ func (s *serviceImpl) GetCards(projectId int) (*models.GetCardsResponse, error)
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
cards := make([]models.Card, 0)
|
||||
cards := make([]Card, 0)
|
||||
for rows.Next() {
|
||||
card := models.Card{}
|
||||
card := Card{}
|
||||
if err := rows.Scan(
|
||||
&card.CardID, &card.ProjectID, &card.UserID, &card.Name, &card.MetricType, &card.ViewType, &card.MetricOf,
|
||||
&card.MetricValue, &card.MetricFormat, &card.IsPublic, &card.CreatedAt, &card.EditedAt,
|
||||
|
|
@ -208,21 +234,21 @@ func (s *serviceImpl) GetCards(projectId int) (*models.GetCardsResponse, error)
|
|||
cards = append(cards, card)
|
||||
}
|
||||
|
||||
return &models.GetCardsResponse{Cards: cards}, nil
|
||||
return &GetCardsResponse{Cards: cards}, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetCardsPaginated(
|
||||
func (s *cardsImpl) GetCardsPaginated(
|
||||
projectId int,
|
||||
filters models.CardListFilter,
|
||||
sort models.CardListSort,
|
||||
filters CardListFilter,
|
||||
sort CardListSort,
|
||||
limit,
|
||||
offset int,
|
||||
) (*models.GetCardsResponsePaginated, error) {
|
||||
) (*GetCardsResponsePaginated, error) {
|
||||
// Validate inputs
|
||||
if err := models.ValidateStruct(filters); err != nil {
|
||||
if err := ValidateStruct(filters); err != nil {
|
||||
return nil, fmt.Errorf("invalid filters: %w", err)
|
||||
}
|
||||
if err := models.ValidateStruct(sort); err != nil {
|
||||
if err := ValidateStruct(sort); err != nil {
|
||||
return nil, fmt.Errorf("invalid sort: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -289,9 +315,9 @@ func (s *serviceImpl) GetCardsPaginated(
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
var cards []models.Card
|
||||
var cards []Card
|
||||
for rows.Next() {
|
||||
var card models.Card
|
||||
var card Card
|
||||
if err := rows.Scan(
|
||||
&card.CardID, &card.ProjectID, &card.UserID, &card.Name, &card.MetricType, &card.ViewType, &card.MetricOf,
|
||||
&card.MetricValue, &card.MetricFormat, &card.IsPublic, &card.CreatedAt, &card.EditedAt,
|
||||
|
|
@ -315,13 +341,13 @@ func (s *serviceImpl) GetCardsPaginated(
|
|||
return nil, fmt.Errorf("failed to get total count: %w", err)
|
||||
}
|
||||
|
||||
return &models.GetCardsResponsePaginated{
|
||||
return &GetCardsResponsePaginated{
|
||||
Cards: cards,
|
||||
Total: total,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) UpdateCard(projectId int, cardID int64, userID uint64, req *models.CardUpdateRequest) (*models.CardGetResponse, error) {
|
||||
func (s *cardsImpl) UpdateCard(projectId int, cardID int64, userID uint64, req *CardUpdateRequest) (*CardGetResponse, error) {
|
||||
if req.MetricValue == nil {
|
||||
req.MetricValue = []string{}
|
||||
}
|
||||
|
|
@ -353,7 +379,7 @@ func (s *serviceImpl) UpdateCard(projectId int, cardID int64, userID uint64, req
|
|||
WHERE metric_id = $8 AND project_id = $9 AND deleted_at IS NULL
|
||||
RETURNING metric_id, project_id, user_id, name, metric_type, view_type, metric_of, metric_value, metric_format, is_public, created_at, edited_at`
|
||||
|
||||
card := &models.CardGetResponse{}
|
||||
card := &CardGetResponse{}
|
||||
err = tx.QueryRow(ctx, sql,
|
||||
req.Name, req.MetricType, req.ViewType, req.MetricOf, req.MetricValue, req.MetricFormat, req.IsPublic, cardID, projectId,
|
||||
).Scan(
|
||||
|
|
@ -380,7 +406,7 @@ func (s *serviceImpl) UpdateCard(projectId int, cardID int64, userID uint64, req
|
|||
return card, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) DeleteCardSeries(cardId int64) error {
|
||||
func (s *cardsImpl) DeleteCardSeries(cardId int64) error {
|
||||
sql := `DELETE FROM public.metric_series WHERE metric_id = $1`
|
||||
err := s.pgconn.Exec(sql, cardId)
|
||||
if err != nil {
|
||||
|
|
@ -389,7 +415,7 @@ func (s *serviceImpl) DeleteCardSeries(cardId int64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) DeleteCard(projectId int, cardID int64, userID uint64) error {
|
||||
func (s *cardsImpl) DeleteCard(projectId int, cardID int64, userID uint64) error {
|
||||
sql := `
|
||||
UPDATE public.metrics
|
||||
SET deleted_at = now()
|
||||
|
|
@ -402,7 +428,7 @@ func (s *serviceImpl) DeleteCard(projectId int, cardID int64, userID uint64) err
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetCardChartData(projectId int, userID uint64, req *models.GetCardChartDataRequest) ([]models.DataPoint, error) {
|
||||
func (s *cardsImpl) GetCardChartData(projectId int, userID uint64, req *GetCardChartDataRequest) ([]DataPoint, error) {
|
||||
jsonInput := `
|
||||
{
|
||||
"data": [
|
||||
|
|
@ -419,7 +445,7 @@ func (s *serviceImpl) GetCardChartData(projectId int, userID uint64, req *models
|
|||
]
|
||||
}`
|
||||
|
||||
var resp models.GetCardChartDataResponse
|
||||
var resp GetCardChartDataResponse
|
||||
if err := json.Unmarshal([]byte(jsonInput), &resp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
|
@ -1,18 +1,64 @@
|
|||
package api
|
||||
package cards
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
"openreplay/backend/pkg/server/api"
|
||||
"openreplay/backend/pkg/server/user"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
config "openreplay/backend/internal/config/analytics"
|
||||
"openreplay/backend/pkg/logger"
|
||||
"openreplay/backend/pkg/server/api"
|
||||
"openreplay/backend/pkg/server/user"
|
||||
)
|
||||
|
||||
func getIDFromRequest(r *http.Request, key string) (int, error) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars[key]
|
||||
if idStr == "" {
|
||||
return 0, fmt.Errorf("missing %s in request", key)
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s format", key)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
type handlersImpl struct {
|
||||
log logger.Logger
|
||||
responser *api.Responser
|
||||
jsonSizeLimit int64
|
||||
cards Cards
|
||||
}
|
||||
|
||||
func (e *handlersImpl) GetAll() []*api.Description {
|
||||
return []*api.Description{
|
||||
{"/v1/analytics/{projectId}/cards", e.createCard, "POST"},
|
||||
{"/v1/analytics/{projectId}/cards", e.getCardsPaginated, "GET"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.getCard, "GET"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.updateCard, "PUT"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}", e.deleteCard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}/chart", e.getCardChartData, "POST"},
|
||||
{"/v1/analytics/{projectId}/cards/{id}/try", e.getCardChartData, "POST"},
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandlers(log logger.Logger, cfg *config.Config, responser *api.Responser, cards Cards) (api.Handlers, error) {
|
||||
return &handlersImpl{
|
||||
log: log,
|
||||
responser: responser,
|
||||
jsonSizeLimit: cfg.JsonSizeLimit,
|
||||
cards: cards,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *handlersImpl) createCard(w http.ResponseWriter, r *http.Request) {
|
||||
startTime := time.Now()
|
||||
bodySize := 0
|
||||
|
|
@ -24,7 +70,7 @@ func (e *handlersImpl) createCard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &models.CardCreateRequest{}
|
||||
req := &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
|
||||
|
|
@ -44,7 +90,7 @@ func (e *handlersImpl) createCard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.CreateCard(projectID, currentUser.ID, req)
|
||||
resp, err := e.cards.CreateCard(projectID, currentUser.ID, req)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -70,7 +116,7 @@ func (e *handlersImpl) getCard(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
resp, err := e.service.GetCardWithSeries(projectID, id)
|
||||
resp, err := e.cards.GetCardWithSeries(projectID, id)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -90,7 +136,7 @@ func (e *handlersImpl) getCards(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
//currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.GetCards(projectID)
|
||||
resp, err := e.cards.GetCards(projectID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -114,7 +160,7 @@ func (e *handlersImpl) getCardsPaginated(w http.ResponseWriter, r *http.Request)
|
|||
query := r.URL.Query()
|
||||
|
||||
// Filters
|
||||
filters := models.CardListFilter{
|
||||
filters := CardListFilter{
|
||||
Filters: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +182,7 @@ func (e *handlersImpl) getCardsPaginated(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
// Sorting
|
||||
sort := models.CardListSort{
|
||||
sort := CardListSort{
|
||||
Field: query.Get("sort_field"),
|
||||
Order: query.Get("sort_order"),
|
||||
}
|
||||
|
|
@ -163,17 +209,17 @@ func (e *handlersImpl) getCardsPaginated(w http.ResponseWriter, r *http.Request)
|
|||
offset := (page - 1) * limit
|
||||
|
||||
// Validate inputs
|
||||
if err := models.ValidateStruct(filters); err != nil {
|
||||
if err := ValidateStruct(filters); err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, fmt.Errorf("invalid filters: %w", err), startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
if err := models.ValidateStruct(sort); err != nil {
|
||||
if err := ValidateStruct(sort); err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusBadRequest, fmt.Errorf("invalid sort: %w", err), startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the service
|
||||
resp, err := e.service.GetCardsPaginated(projectID, filters, sort, limit, offset)
|
||||
resp, err := e.cards.GetCardsPaginated(projectID, filters, sort, limit, offset)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -206,7 +252,7 @@ func (e *handlersImpl) updateCard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &models.CardUpdateRequest{}
|
||||
req := &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
|
||||
|
|
@ -220,7 +266,7 @@ func (e *handlersImpl) updateCard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.UpdateCard(projectID, int64(cardId), currentUser.ID, req)
|
||||
resp, err := e.cards.UpdateCard(projectID, int64(cardId), currentUser.ID, req)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -246,7 +292,7 @@ func (e *handlersImpl) deleteCard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
err = e.service.DeleteCard(projectID, int64(cardId), currentUser.ID)
|
||||
err = e.cards.DeleteCard(projectID, int64(cardId), currentUser.ID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -272,7 +318,7 @@ func (e *handlersImpl) getCardChartData(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &models.GetCardChartDataRequest{}
|
||||
req := &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
|
||||
|
|
@ -286,7 +332,7 @@ func (e *handlersImpl) getCardChartData(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.GetCardChartData(projectID, currentUser.ID, req)
|
||||
resp, err := e.cards.GetCardChartData(projectID, currentUser.ID, req)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package models
|
||||
package cards
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
|
|
@ -1,21 +1,46 @@
|
|||
package service
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
|
||||
"openreplay/backend/pkg/db/postgres/pool"
|
||||
"openreplay/backend/pkg/logger"
|
||||
)
|
||||
|
||||
type Dashboards interface {
|
||||
GetDashboard(projectId int, dashboardId int, userId uint64) (*GetDashboardResponse, error)
|
||||
GetDashboardsPaginated(projectId int, userId uint64, req *GetDashboardsRequest) (*GetDashboardsResponsePaginated, error)
|
||||
GetDashboards(projectId int, userId uint64) (*GetDashboardsResponse, error)
|
||||
CreateDashboard(projectId int, userId uint64, req *CreateDashboardRequest) (*GetDashboardResponse, error)
|
||||
UpdateDashboard(projectId int, dashboardId int, userId uint64, req *UpdateDashboardRequest) (*GetDashboardResponse, error)
|
||||
DeleteDashboard(projectId int, dashboardId int, userId uint64) error
|
||||
AddCardsToDashboard(projectId int, dashboardId int, userId uint64, req *AddCardToDashboardRequest) error
|
||||
DeleteCardFromDashboard(dashboardId int, cardId int) error
|
||||
}
|
||||
|
||||
type dashboardsImpl struct {
|
||||
log logger.Logger
|
||||
pgconn pool.Pool
|
||||
}
|
||||
|
||||
func New(log logger.Logger, conn pool.Pool) (Dashboards, error) {
|
||||
return &dashboardsImpl{
|
||||
log: log,
|
||||
pgconn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateDashboard Create a new dashboard
|
||||
func (s *serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.CreateDashboardRequest) (*models.GetDashboardResponse, error) {
|
||||
func (s *dashboardsImpl) CreateDashboard(projectId int, userID uint64, req *CreateDashboardRequest) (*GetDashboardResponse, error) {
|
||||
sql := `
|
||||
INSERT INTO dashboards (project_id, user_id, name, description, is_public, is_pinned)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING dashboard_id, project_id, user_id, name, description, is_public, is_pinned`
|
||||
|
||||
dashboard := &models.GetDashboardResponse{}
|
||||
dashboard := &GetDashboardResponse{}
|
||||
err := s.pgconn.QueryRow(sql, projectId, userID, req.Name, req.Description, req.IsPublic, req.IsPinned).Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.ProjectID,
|
||||
|
|
@ -32,7 +57,7 @@ func (s *serviceImpl) CreateDashboard(projectId int, userID uint64, req *models.
|
|||
}
|
||||
|
||||
// GetDashboard Fetch a specific dashboard by ID
|
||||
func (s *serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64) (*models.GetDashboardResponse, error) {
|
||||
func (s *dashboardsImpl) GetDashboard(projectId int, dashboardID int, userID uint64) (*GetDashboardResponse, error) {
|
||||
sql := `
|
||||
WITH series_agg AS (
|
||||
SELECT
|
||||
|
|
@ -75,7 +100,7 @@ func (s *serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64
|
|||
WHERE d.dashboard_id = $1 AND d.project_id = $2 AND d.deleted_at IS NULL
|
||||
GROUP BY d.dashboard_id, d.project_id, d.name, d.description, d.is_public, d.is_pinned, d.user_id`
|
||||
|
||||
dashboard := &models.GetDashboardResponse{}
|
||||
dashboard := &GetDashboardResponse{}
|
||||
var ownerID int
|
||||
var metricsJSON []byte
|
||||
|
||||
|
|
@ -108,7 +133,7 @@ func (s *serviceImpl) GetDashboard(projectId int, dashboardID int, userID uint64
|
|||
return dashboard, nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDashboardsResponse, error) {
|
||||
func (s *dashboardsImpl) GetDashboards(projectId int, userID uint64) (*GetDashboardsResponse, error) {
|
||||
sql := `
|
||||
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
|
||||
|
|
@ -121,9 +146,9 @@ func (s *serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDa
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dashboards []models.Dashboard
|
||||
var dashboards []Dashboard
|
||||
for rows.Next() {
|
||||
var dashboard models.Dashboard
|
||||
var dashboard 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 {
|
||||
|
|
@ -137,13 +162,13 @@ func (s *serviceImpl) GetDashboards(projectId int, userID uint64) (*models.GetDa
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &models.GetDashboardsResponse{
|
||||
return &GetDashboardsResponse{
|
||||
Dashboards: dashboards,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetDashboardsPaginated Fetch dashboards with pagination
|
||||
func (s *serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *models.GetDashboardsRequest) (*models.GetDashboardsResponsePaginated, error) {
|
||||
func (s *dashboardsImpl) GetDashboardsPaginated(projectId int, userID uint64, req *GetDashboardsRequest) (*GetDashboardsResponsePaginated, error) {
|
||||
baseSQL, args := buildBaseQuery(projectId, userID, req)
|
||||
|
||||
// Count total dashboards
|
||||
|
|
@ -165,9 +190,9 @@ func (s *serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dashboards []models.Dashboard
|
||||
var dashboards []Dashboard
|
||||
for rows.Next() {
|
||||
var dashboard models.Dashboard
|
||||
var dashboard Dashboard
|
||||
err := rows.Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.UserID,
|
||||
|
|
@ -185,21 +210,21 @@ func (s *serviceImpl) GetDashboardsPaginated(projectId int, userID uint64, req *
|
|||
dashboards = append(dashboards, dashboard)
|
||||
}
|
||||
|
||||
return &models.GetDashboardsResponsePaginated{
|
||||
return &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) {
|
||||
func (s *dashboardsImpl) UpdateDashboard(projectId int, dashboardID int, userID uint64, req *UpdateDashboardRequest) (*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 AND deleted_at IS NULL
|
||||
RETURNING dashboard_id, project_id, user_id, name, description, is_public, is_pinned`
|
||||
|
||||
dashboard := &models.GetDashboardResponse{}
|
||||
dashboard := &GetDashboardResponse{}
|
||||
err := s.pgconn.QueryRow(sql, req.Name, req.Description, req.IsPublic, req.IsPinned, dashboardID, projectId, userID).Scan(
|
||||
&dashboard.DashboardID,
|
||||
&dashboard.ProjectID,
|
||||
|
|
@ -216,7 +241,7 @@ func (s *serviceImpl) UpdateDashboard(projectId int, dashboardID int, userID uin
|
|||
}
|
||||
|
||||
// DeleteDashboard Soft-delete a dashboard
|
||||
func (s *serviceImpl) DeleteDashboard(projectId int, dashboardID int, userID uint64) error {
|
||||
func (s *dashboardsImpl) DeleteDashboard(projectId int, dashboardID int, userID uint64) error {
|
||||
sql := `
|
||||
UPDATE dashboards
|
||||
SET deleted_at = now()
|
||||
|
|
@ -231,7 +256,7 @@ func (s *serviceImpl) DeleteDashboard(projectId int, dashboardID int, userID uin
|
|||
}
|
||||
|
||||
// Helper to build the base query for dashboards
|
||||
func buildBaseQuery(projectId int, userID uint64, req *models.GetDashboardsRequest) (string, []interface{}) {
|
||||
func buildBaseQuery(projectId int, userID uint64, req *GetDashboardsRequest) (string, []interface{}) {
|
||||
var conditions []string
|
||||
args := []interface{}{projectId}
|
||||
|
||||
|
|
@ -282,7 +307,7 @@ func getOrder(order string) string {
|
|||
return "ASC"
|
||||
}
|
||||
|
||||
func (s *serviceImpl) CardsExist(projectId int, cardIDs []int) (bool, error) {
|
||||
func (s *dashboardsImpl) CardsExist(projectId int, cardIDs []int) (bool, error) {
|
||||
sql := `
|
||||
SELECT COUNT(*) FROM public.metrics
|
||||
WHERE project_id = $1 AND metric_id = ANY($2)
|
||||
|
|
@ -295,7 +320,7 @@ func (s *serviceImpl) CardsExist(projectId int, cardIDs []int) (bool, error) {
|
|||
return count == len(cardIDs), nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) AddCardsToDashboard(projectId int, dashboardId int, userId uint64, req *models.AddCardToDashboardRequest) error {
|
||||
func (s *dashboardsImpl) AddCardsToDashboard(projectId int, dashboardId int, userId uint64, req *AddCardToDashboardRequest) error {
|
||||
_, err := s.GetDashboard(projectId, dashboardId, userId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get dashboard: %w", err)
|
||||
|
|
@ -370,7 +395,7 @@ func (s *serviceImpl) AddCardsToDashboard(projectId int, dashboardId int, userId
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *serviceImpl) DeleteCardFromDashboard(dashboardId int, cardId int) error {
|
||||
func (s *dashboardsImpl) DeleteCardFromDashboard(dashboardId int, cardId int) error {
|
||||
sql := `DELETE FROM public.dashboard_widgets WHERE dashboard_id = $1 AND metric_id = $2`
|
||||
err := s.pgconn.Exec(sql, dashboardId, cardId)
|
||||
if err != nil {
|
||||
|
|
@ -1,15 +1,64 @@
|
|||
package api
|
||||
package dashboards
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
config "openreplay/backend/internal/config/analytics"
|
||||
"openreplay/backend/pkg/logger"
|
||||
"openreplay/backend/pkg/server/api"
|
||||
"openreplay/backend/pkg/server/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
func getIDFromRequest(r *http.Request, key string) (int, error) {
|
||||
vars := mux.Vars(r)
|
||||
idStr := vars[key]
|
||||
if idStr == "" {
|
||||
return 0, fmt.Errorf("missing %s in request", key)
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid %s format", key)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
type handlersImpl struct {
|
||||
log logger.Logger
|
||||
responser *api.Responser
|
||||
jsonSizeLimit int64
|
||||
dashboards Dashboards
|
||||
}
|
||||
|
||||
func (e *handlersImpl) GetAll() []*api.Description {
|
||||
return []*api.Description{
|
||||
{"/v1/analytics/{projectId}/dashboards", e.createDashboard, "POST"},
|
||||
{"/v1/analytics/{projectId}/dashboards", e.getDashboards, "GET"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.getDashboard, "GET"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.updateDashboard, "PUT"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}", e.deleteDashboard, "DELETE"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}/cards", e.addCardToDashboard, "POST"},
|
||||
{"/v1/analytics/{projectId}/dashboards/{id}/cards/{cardId}", e.removeCardFromDashboard, "DELETE"},
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandlers(log logger.Logger, cfg *config.Config, responser *api.Responser, dashboards Dashboards) (api.Handlers, error) {
|
||||
return &handlersImpl{
|
||||
log: log,
|
||||
responser: responser,
|
||||
jsonSizeLimit: cfg.JsonSizeLimit,
|
||||
dashboards: dashboards,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *handlersImpl) createDashboard(w http.ResponseWriter, r *http.Request) {
|
||||
startTime := time.Now()
|
||||
bodySize := 0
|
||||
|
|
@ -21,7 +70,7 @@ func (e *handlersImpl) createDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &models.CreateDashboardRequest{}
|
||||
req := &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
|
||||
|
|
@ -41,7 +90,7 @@ func (e *handlersImpl) createDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.CreateDashboard(projectID, currentUser.ID, req)
|
||||
resp, err := e.dashboards.CreateDashboard(projectID, currentUser.ID, req)
|
||||
|
||||
e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
|
|
@ -58,7 +107,7 @@ func (e *handlersImpl) getDashboards(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
u := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.GetDashboards(projectID, u.ID)
|
||||
resp, err := e.dashboards.GetDashboards(projectID, u.ID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -84,7 +133,7 @@ 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)
|
||||
res, err := e.dashboards.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
// Map errors to appropriate HTTP status codes
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
|
|
@ -124,7 +173,7 @@ 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)
|
||||
_, err = e.dashboards.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
// Map errors to appropriate HTTP status codes
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
|
|
@ -137,14 +186,14 @@ func (e *handlersImpl) updateDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
req := &models.UpdateDashboardRequest{}
|
||||
req := &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
|
||||
}
|
||||
|
||||
currentUser := r.Context().Value("userData").(*user.User)
|
||||
resp, err := e.service.UpdateDashboard(projectID, dashboardID, currentUser.ID, req)
|
||||
resp, err := e.dashboards.UpdateDashboard(projectID, dashboardID, currentUser.ID, req)
|
||||
|
||||
e.responser.ResponseWithJSON(e.log, r.Context(), w, resp, startTime, r.URL.Path, bodySize)
|
||||
}
|
||||
|
|
@ -166,7 +215,7 @@ 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)
|
||||
_, err = e.dashboards.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
// Map errors to appropriate HTTP status codes
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
|
|
@ -179,7 +228,7 @@ func (e *handlersImpl) deleteDashboard(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
err = e.service.DeleteDashboard(projectID, dashboardID, u.ID)
|
||||
err = e.dashboards.DeleteDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -224,7 +273,7 @@ func (e *handlersImpl) addCardToDashboard(w http.ResponseWriter, r *http.Request
|
|||
|
||||
bodySize = len(bodyBytes)
|
||||
|
||||
req := &models.AddCardToDashboardRequest{}
|
||||
req := &AddCardToDashboardRequest{}
|
||||
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
|
||||
|
|
@ -237,7 +286,7 @@ func (e *handlersImpl) addCardToDashboard(w http.ResponseWriter, r *http.Request
|
|||
return
|
||||
}
|
||||
|
||||
err = e.service.AddCardsToDashboard(projectID, dashboardID, u.ID, req)
|
||||
err = e.dashboards.AddCardsToDashboard(projectID, dashboardID, u.ID, req)
|
||||
if err != nil {
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
|
|
@ -276,7 +325,7 @@ func (e *handlersImpl) removeCardFromDashboard(w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
|
||||
u := r.Context().Value("userData").(*user.User)
|
||||
_, err = e.service.GetDashboard(projectID, dashboardID, u.ID)
|
||||
_, err = e.dashboards.GetDashboard(projectID, dashboardID, u.ID)
|
||||
if err != nil {
|
||||
if err.Error() == "not_found: dashboard not found" {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusNotFound, err, startTime, r.URL.Path, bodySize)
|
||||
|
|
@ -287,7 +336,7 @@ func (e *handlersImpl) removeCardFromDashboard(w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
}
|
||||
|
||||
err = e.service.DeleteCardFromDashboard(dashboardID, cardID)
|
||||
err = e.dashboards.DeleteCardFromDashboard(dashboardID, cardID)
|
||||
if err != nil {
|
||||
e.responser.ResponseWithError(e.log, r.Context(), w, http.StatusInternalServerError, err, startTime, r.URL.Path, bodySize)
|
||||
return
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
package models
|
||||
package dashboards
|
||||
|
||||
import "openreplay/backend/pkg/analytics/cards"
|
||||
|
||||
type Dashboard struct {
|
||||
DashboardID int `json:"dashboardId"`
|
||||
ProjectID int `json:"projectId"`
|
||||
UserID int `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
IsPinned bool `json:"isPinned"`
|
||||
OwnerEmail string `json:"ownerEmail"`
|
||||
OwnerName string `json:"ownerName"`
|
||||
Metrics []CardBase `json:"cards"`
|
||||
DashboardID int `json:"dashboardId"`
|
||||
ProjectID int `json:"projectId"`
|
||||
UserID int `json:"userId"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
IsPublic bool `json:"isPublic"`
|
||||
IsPinned bool `json:"isPinned"`
|
||||
OwnerEmail string `json:"ownerEmail"`
|
||||
OwnerName string `json:"ownerName"`
|
||||
Metrics []cards.CardBase `json:"cards"`
|
||||
}
|
||||
|
||||
type CreateDashboardResponse struct {
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"openreplay/backend/pkg/analytics/api/models"
|
||||
"openreplay/backend/pkg/db/postgres/pool"
|
||||
"openreplay/backend/pkg/logger"
|
||||
)
|
||||
|
||||
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)
|
||||
DeleteDashboard(projectId int, dashboardId int, userId uint64) error
|
||||
AddCardsToDashboard(projectId int, dashboardId int, userId uint64, req *models.AddCardToDashboardRequest) error
|
||||
DeleteCardFromDashboard(dashboardId int, cardId int) error
|
||||
GetCard(projectId int, cardId int) (*models.CardGetResponse, error)
|
||||
GetCardWithSeries(projectId int, cardId int) (*models.CardGetResponse, error)
|
||||
GetCards(projectId int) (*models.GetCardsResponse, error)
|
||||
GetCardsPaginated(projectId int, filters models.CardListFilter, sort models.CardListSort, limit int, offset int) (*models.GetCardsResponsePaginated, error)
|
||||
CreateCard(projectId int, userId uint64, req *models.CardCreateRequest) (*models.CardGetResponse, error)
|
||||
UpdateCard(projectId int, cardId int64, userId uint64, req *models.CardUpdateRequest) (*models.CardGetResponse, error)
|
||||
DeleteCard(projectId int, cardId int64, userId uint64) error
|
||||
GetCardChartData(projectId int, userId uint64, req *models.GetCardChartDataRequest) ([]models.DataPoint, error)
|
||||
}
|
||||
|
||||
type serviceImpl struct {
|
||||
log logger.Logger
|
||||
pgconn pool.Pool
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func NewService(log logger.Logger, conn pool.Pool) (Service, error) {
|
||||
switch {
|
||||
case log == nil:
|
||||
return nil, errors.New("logger is empty")
|
||||
case conn == nil:
|
||||
return nil, errors.New("connection pool is empty")
|
||||
}
|
||||
|
||||
return &serviceImpl{
|
||||
log: log,
|
||||
pgconn: conn,
|
||||
ctx: context.Background(),
|
||||
}, nil
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue