Skip to content

Commit 2e531fe

Browse files
committed
feat: update API handlers
1 parent 3a96854 commit 2e531fe

File tree

4 files changed

+296
-183
lines changed

4 files changed

+296
-183
lines changed

Diff for: services/core/api/user_layout.go

+28-1
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,35 @@ type SetUserLayoutRequest struct {
3030
IsDefault bool `json:"is_default"`
3131

3232
Description string `json:"description"`
33-
LayoutConfig []map[string]any `json:"layout_config"`
33+
WidgetIDs []string `json:"widget_ids"`
3434
UpdatedAt time.Time `json:"updated_at"`
3535
Name string `json:"name"`
3636
IsPrivate bool `json:"is_private"`
37+
}
38+
39+
type UpdateWidgetDashboardsRequest struct {
40+
WidgetID string `json:"widget_id"`
41+
Dashboards []string `json:"dashboards"`
42+
}
43+
type UpdateDashboardWidgetsRequest struct {
44+
DashboardID string `json:"dashboard_id"`
45+
Widgets []string `json:"widgets"`
46+
}
47+
type SetUserWidgetRequest struct {
48+
ID string `json:"id"`
49+
Title string `json:"title"`
50+
Description string `json:"description"`
51+
WidgetType string `json:"widget_type"`
52+
WidgetProps map[string]any `json:"widget_props"`
53+
RowSpan int `json:"row_span"`
54+
ColumnSpan int `json:"column_span"`
55+
ColumnOffset int `json:"column_offset"`
56+
IsPublic bool `json:"is_public"`
57+
UserID string `json:"user_id"`
58+
CreatedAt time.Time `json:"created_at"`
59+
60+
}
61+
62+
type GetUserWidgetRequest struct {
63+
UserID string `json:"user_id"`
3764
}

Diff for: services/core/db/metadata-db.go

+77-83
Original file line numberDiff line numberDiff line change
@@ -144,66 +144,44 @@ func (db Database) ListQueryViews() ([]models.QueryView, error) {
144144
}
145145
return queryViews, nil
146146
}
147-
// get user layout
148-
// GetUserLayouts returns all dashboards for the specified user,
149-
// preloading their widgets.
147+
// get user layout// Get user layouts (with widgets)
150148
func (db Database) GetUserLayouts(userID string) ([]models.Dashboard, error) {
151-
var dashboards []models.Dashboard
152-
err := db.orm.
153-
Where("user_id = ?", userID).
154-
Preload("Widgets").
155-
Find(&dashboards).Error
149+
var userLayouts []models.Dashboard
150+
err := db.orm.Preload("Widgets").Where("user_id = ?", userID).Find(&userLayouts).Error
156151
if err != nil {
157152
return nil, err
158153
}
159-
return dashboards, nil
154+
return userLayouts, nil
160155
}
161156

162-
// GetUserDefaultLayout returns the dashboard that is marked as default for the user.
163-
// If no default is found, it returns nil.
157+
// Get the default layout for a user
164158
func (db Database) GetUserDefaultLayout(userID string) (*models.Dashboard, error) {
165-
var dashboard models.Dashboard
166-
err := db.orm.
167-
Where("user_id = ? AND is_default = ?", userID, true).
168-
Preload("Widgets").
169-
First(&dashboard).Error
159+
var userLayout models.Dashboard
160+
err := db.orm.Preload("Widgets").Where("user_id = ? AND is_default = ?", userID, true).First(&userLayout).Error
170161
if err != nil {
171162
if errors.Is(err, gorm.ErrRecordNotFound) {
172163
return nil, nil
173164
}
174165
return nil, err
175166
}
176-
return &dashboard, nil
167+
return &userLayout, nil
177168
}
178169

179-
// SetUserLayout upserts a dashboard (excluding its widgets), and then
180-
// replaces the associated widgets. This function runs in a transaction.
181-
func (db Database) SetUserLayout(layout models.Dashboard) error {
170+
// Upsert dashboard and update associated widgets
171+
func (db Database) SetUserLayout(layoutConfig models.Dashboard) error {
182172
return db.orm.Transaction(func(tx *gorm.DB) error {
183-
// Upsert the dashboard (excluding Widgets)
184173
err := tx.Clauses(clause.OnConflict{
185174
Columns: []clause.Column{{Name: "id"}},
186-
DoUpdates: clause.AssignmentColumns([]string{"name", "description", "is_default", "is_private", "updated_at", "user_id"}),
187-
}).Omit("Widgets").Create(&layout).Error
175+
DoUpdates: clause.AssignmentColumns([]string{
176+
"name", "description", "is_default", "is_private", "updated_at", "user_id",
177+
}),
178+
}).Omit("Widgets").Create(&layoutConfig).Error
188179
if err != nil {
189180
return err
190181
}
191182

192-
// Delete previous widgets for this dashboard
193-
err = tx.Where("user_id = ? AND dashboard_id = ?", layout.UserID, layout.ID).
194-
Delete(&models.Widget{}).Error
195-
if err != nil {
196-
return err
197-
}
198-
199-
// Ensure each widget has the correct DashboardID and UserID
200-
for i := range layout.Widgets {
201-
layout.Widgets[i].DashboardID = layout.ID
202-
layout.Widgets[i].UserID = layout.UserID
203-
}
204-
205-
// Insert the new widgets
206-
err = tx.Create(&layout.Widgets).Error
183+
// Replace dashboard-widgets association
184+
err = tx.Model(&layoutConfig).Association("Widgets").Replace(layoutConfig.Widgets)
207185
if err != nil {
208186
return err
209187
}
@@ -212,46 +190,35 @@ func (db Database) SetUserLayout(layout models.Dashboard) error {
212190
})
213191
}
214192

215-
// GetPublicLayouts returns dashboards that are not private (public dashboards).
193+
// Get all public dashboards
216194
func (db Database) GetPublicLayouts() ([]models.Dashboard, error) {
217-
var dashboards []models.Dashboard
218-
err := db.orm.
219-
Where("is_private = ?", false).
220-
Preload("Widgets").
221-
Find(&dashboards).Error
195+
var publicLayouts []models.Dashboard
196+
err := db.orm.Preload("Widgets").Where("is_private = ?", false).Find(&publicLayouts).Error
222197
if err != nil {
223198
return nil, err
224199
}
225-
return dashboards, nil
200+
return publicLayouts, nil
226201
}
227202

228-
// ChangeLayoutPrivacy updates the privacy status for all dashboards of a user.
203+
// Change privacy status of all layouts for a user
229204
func (db Database) ChangeLayoutPrivacy(userID string, isPrivate bool) error {
230-
err := db.orm.
231-
Model(&models.Dashboard{}).
232-
Where("user_id = ?", userID).
233-
Update("is_private", isPrivate).Error
234-
return err
205+
return db.orm.Model(&models.Dashboard{}).Where("user_id = ?", userID).Update("is_private", isPrivate).Error
235206
}
236207

237-
// GetUserWidgets returns all widgets for the specified user.
208+
// Get all widgets for a user
238209
func (db Database) GetUserWidgets(userID string) ([]models.Widget, error) {
239210
var widgets []models.Widget
240-
err := db.orm.
241-
Where("user_id = ?", userID).
242-
Find(&widgets).Error
211+
err := db.orm.Where("user_id = ?", userID).Find(&widgets).Error
243212
if err != nil {
244213
return nil, err
245214
}
246215
return widgets, nil
247216
}
248217

249-
// GetWidget returns a single widget by its ID.
218+
// Get a single widget by ID
250219
func (db Database) GetWidget(widgetID string) (*models.Widget, error) {
251220
var widget models.Widget
252-
err := db.orm.
253-
Where("id = ?", widgetID).
254-
First(&widget).Error
221+
err := db.orm.Where("id = ?", widgetID).First(&widget).Error
255222
if err != nil {
256223
if errors.Is(err, gorm.ErrRecordNotFound) {
257224
return nil, nil
@@ -261,38 +228,65 @@ func (db Database) GetWidget(widgetID string) (*models.Widget, error) {
261228
return &widget, nil
262229
}
263230

264-
// SetUserWidget upserts a widget using its ID as the conflict key.
231+
// Add widgets in bulk
232+
func (db Database) AddWidgets(widgets []models.Widget) error {
233+
return db.orm.Create(&widgets).Error
234+
}
235+
236+
// Upsert a widget
265237
func (db Database) SetUserWidget(widget models.Widget) error {
266238
return db.orm.Clauses(clause.OnConflict{
267239
Columns: []clause.Column{{Name: "id"}},
268-
DoUpdates: clause.AssignmentColumns([]string{"title", "description", "widget_type", "widget_props", "row_span", "column_span", "column_offset", "is_public", "user_id", "dashboard_id", "updated_at"}),
240+
DoUpdates: clause.AssignmentColumns([]string{
241+
"title", "description", "widget_type", "widget_props",
242+
"row_span", "column_span", "column_offset", "is_public",
243+
"user_id", "updated_at",
244+
}),
269245
}).Create(&widget).Error
270246
}
271247

272-
// DeleteUserWidget deletes a widget by its ID.
273-
func (db Database) DeleteUserWidget(widgetID string) error {
274-
return db.orm.Delete(&models.Widget{}, "id = ?", widgetID).Error
248+
// Delete widgets by IDs
249+
func (db Database) DeleteWidgets(widgetIDs []string) error {
250+
return db.orm.Where("id IN ?", widgetIDs).Delete(&models.Widget{}).Error
275251
}
276252

277-
func (db Database) AddWidgetsToDashboard(dashboardID string, widgets []models.Widget) error {
278-
return db.orm.Transaction(func(tx *gorm.DB) error {
279-
// Set the DashboardID for each widget
280-
for i := range widgets {
281-
widgets[i].DashboardID = dashboardID
282-
}
283-
// Bulk insert all widgets
284-
if err := tx.Create(&widgets).Error; err != nil {
285-
return err
286-
}
287-
return nil
288-
})
253+
// Update widget content (single widget)
254+
func (db Database) UpdateWidget(widget models.Widget) error {
255+
return db.orm.Model(&widget).Where("id = ?", widget.ID).Updates(&widget).Error
289256
}
290257

291-
func (db Database) AddWidgets(widgets []models.Widget) error {
292-
return db.orm.Transaction(func(tx *gorm.DB) error {
293-
if err := tx.Create(&widgets).Error; err != nil {
294-
return err
295-
}
296-
return nil
297-
})
258+
// Update widget-dashboard relationship (associate widgets to dashboard)
259+
func (db Database) UpdateDashboardWidgets(dashboardID string, widgetIDs []string) error {
260+
var dashboard models.Dashboard
261+
dashboard.ID = dashboardID
262+
263+
var widgets []models.Widget
264+
err := db.orm.Where("id IN ?", widgetIDs).Find(&widgets).Error
265+
if err != nil {
266+
return err
267+
}
268+
269+
return db.orm.Model(&dashboard).Association("Widgets").Replace(widgets)
270+
}
271+
272+
// DeleteUserWidget
273+
func (db Database) DeleteUserWidget(widgetID string) error {
274+
return db.orm.Where("id = ?", widgetID).Delete(&models.Widget{}).Error
275+
}
276+
277+
func (db Database) UpdateWidgetDashboards(widgetID string, dashboardIDs []string) error {
278+
var widget models.Widget
279+
// Fetch the widget by ID
280+
if err := db.orm.First(&widget, "id = ?", widgetID).Error; err != nil {
281+
return err
282+
}
283+
284+
var dashboards []models.Dashboard
285+
// Fetch the dashboards by the provided dashboardIDs
286+
if err := db.orm.Where("id IN ?", dashboardIDs).Find(&dashboards).Error; err != nil {
287+
return err
288+
}
289+
290+
// Replace the dashboards associated with the widget
291+
return db.orm.Model(&widget).Association("Dashboards").Replace(dashboards)
298292
}

Diff for: services/core/db/models/metadata-models.go

+22-22
Original file line numberDiff line numberDiff line change
@@ -51,31 +51,31 @@ type QueryView struct {
5151
Tags []QueryViewTag `gorm:"foreignKey:QueryViewID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
5252
}
5353
type Dashboard struct {
54-
ID string `gorm:"primaryKey" json:"id"`
55-
IsDefault bool `json:"is_default"`
56-
UserID string `gorm:"type:text" json:"user_id"`
57-
Widgets []Widget `gorm:"foreignKey:DashboardID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"` // One-to-many
58-
Name string `gorm:"type:text" json:"name"`
59-
CreatedAt time.Time `json:"created_at"`
60-
Description string `json:"description"`
61-
UpdatedAt time.Time `json:"updated_at"`
62-
IsPrivate bool `json:"is_private"`
54+
ID string `gorm:"primaryKey" json:"id"`
55+
IsDefault bool `json:"is_default"`
56+
UserID string `gorm:"type:text" json:"user_id"`
57+
Name string `gorm:"type:text" json:"name"`
58+
Description string `json:"description"`
59+
IsPrivate bool `json:"is_private"`
60+
CreatedAt time.Time `json:"created_at"`
61+
UpdatedAt time.Time `json:"updated_at"`
62+
Widgets []Widget `gorm:"many2many:dashboard_widgets;" json:"widgets"` // M2M link
6363
}
6464

6565
type Widget struct {
66-
ID string `gorm:"primaryKey" json:"id"`
67-
Title string `gorm:"type:text" json:"title"`
68-
Description string `gorm:"type:text" json:"description"`
69-
CreatedAt time.Time `json:"created_at"`
70-
UpdatedAt time.Time `json:"updated_at"`
71-
WidgetType string `gorm:"type:text" json:"widget_type"`
72-
WidgetProps pgtype.JSONB `json:"widget_props" gorm:"type:jsonb"`
73-
RowSpan int `json:"row_span"`
74-
ColumnSpan int `json:"column_span"`
75-
ColumnOffset int `json:"column_offset"`
76-
IsPublic bool `json:"is_public"`
77-
UserID string `gorm:"type:text" json:"user_id"`
78-
DashboardID string `gorm:"type:text" json:"dashboard_id"` // Correct: this links to Dashboard.ID
66+
ID string `gorm:"primaryKey" json:"id"`
67+
Title string `gorm:"type:text" json:"title"`
68+
Description string `gorm:"type:text" json:"description"`
69+
WidgetType string `gorm:"type:text" json:"widget_type"`
70+
WidgetProps pgtype.JSONB `json:"widget_props" gorm:"type:jsonb"`
71+
RowSpan int `json:"row_span"`
72+
ColumnSpan int `json:"column_span"`
73+
ColumnOffset int `json:"column_offset"`
74+
IsPublic bool `json:"is_public"`
75+
UserID string `gorm:"type:text" json:"user_id"`
76+
CreatedAt time.Time `json:"created_at"`
77+
UpdatedAt time.Time `json:"updated_at"`
78+
Dashboards []Dashboard `gorm:"many2many:dashboard_widgets;" json:"dashboards"` // M2M link
7979
}
8080

8181

0 commit comments

Comments
 (0)