@@ -52,6 +52,10 @@ type CampaignRepository interface {
5252 // Pass nil to revert to the hardcoded default dashboard.
5353 UpdateDashboardLayout (ctx context.Context , campaignID string , layoutJSON * string ) error
5454
55+ // UpdateOwnerDashboardLayout updates only the owner_dashboard_layout JSON column.
56+ // Pass nil to revert to the hardcoded default owner dashboard.
57+ UpdateOwnerDashboardLayout (ctx context.Context , campaignID string , layoutJSON * string ) error
58+
5559 // TransferOwnership atomically transfers campaign ownership from one user
5660 // to another within a database transaction.
5761 TransferOwnership (ctx context.Context , campaignID , fromUserID , toUserID string ) error
@@ -75,12 +79,12 @@ func NewCampaignRepository(db *sql.DB) CampaignRepository {
7579
7680// Create inserts a new campaign row.
7781func (r * campaignRepository ) Create (ctx context.Context , campaign * Campaign ) error {
78- query := `INSERT INTO campaigns (id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, created_by, created_at, updated_at)
79- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
82+ query := `INSERT INTO campaigns (id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, owner_dashboard_layout, created_by, created_at, updated_at)
83+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )`
8084
8185 _ , err := r .db .ExecContext (ctx , query ,
8286 campaign .ID , campaign .Name , campaign .Slug , campaign .Description , campaign .IsPublic ,
83- campaign .Settings , campaign .BackdropPath , campaign .SidebarConfig , campaign .DashboardLayout ,
87+ campaign .Settings , campaign .BackdropPath , campaign .SidebarConfig , campaign .DashboardLayout , campaign . OwnerDashboardLayout ,
8488 campaign .CreatedBy , campaign .CreatedAt , campaign .UpdatedAt ,
8589 )
8690 if err != nil {
@@ -91,13 +95,13 @@ func (r *campaignRepository) Create(ctx context.Context, campaign *Campaign) err
9195
9296// FindByID retrieves a campaign by its UUID.
9397func (r * campaignRepository ) FindByID (ctx context.Context , id string ) (* Campaign , error ) {
94- query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, created_by, created_at, updated_at
98+ query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, owner_dashboard_layout, created_by, created_at, updated_at
9599 FROM campaigns WHERE id = ?`
96100
97101 c := & Campaign {}
98102 err := r .db .QueryRowContext (ctx , query , id ).Scan (
99103 & c .ID , & c .Name , & c .Slug , & c .Description , & c .IsPublic ,
100- & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout ,
104+ & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout , & c . OwnerDashboardLayout ,
101105 & c .CreatedBy , & c .CreatedAt , & c .UpdatedAt ,
102106 )
103107 if errors .Is (err , sql .ErrNoRows ) {
@@ -111,13 +115,13 @@ func (r *campaignRepository) FindByID(ctx context.Context, id string) (*Campaign
111115
112116// FindBySlug retrieves a campaign by its URL slug.
113117func (r * campaignRepository ) FindBySlug (ctx context.Context , slug string ) (* Campaign , error ) {
114- query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, created_by, created_at, updated_at
118+ query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, owner_dashboard_layout, created_by, created_at, updated_at
115119 FROM campaigns WHERE slug = ?`
116120
117121 c := & Campaign {}
118122 err := r .db .QueryRowContext (ctx , query , slug ).Scan (
119123 & c .ID , & c .Name , & c .Slug , & c .Description , & c .IsPublic ,
120- & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout ,
124+ & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout , & c . OwnerDashboardLayout ,
121125 & c .CreatedBy , & c .CreatedAt , & c .UpdatedAt ,
122126 )
123127 if errors .Is (err , sql .ErrNoRows ) {
@@ -161,7 +165,7 @@ func (r *campaignRepository) ListByUser(ctx context.Context, userID string, opts
161165 var c Campaign
162166 if err := rows .Scan (
163167 & c .ID , & c .Name , & c .Slug , & c .Description , & c .IsPublic ,
164- & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout ,
168+ & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout , & c . OwnerDashboardLayout ,
165169 & c .CreatedBy , & c .CreatedAt , & c .UpdatedAt ,
166170 ); err != nil {
167171 return nil , 0 , fmt .Errorf ("scanning campaign row: %w" , err )
@@ -179,7 +183,7 @@ func (r *campaignRepository) ListAll(ctx context.Context, opts ListOptions) ([]C
179183 return nil , 0 , fmt .Errorf ("counting all campaigns: %w" , err )
180184 }
181185
182- query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, created_by, created_at, updated_at
186+ query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, owner_dashboard_layout, created_by, created_at, updated_at
183187 FROM campaigns ORDER BY updated_at DESC LIMIT ? OFFSET ?`
184188
185189 rows , err := r .db .QueryContext (ctx , query , opts .PerPage , opts .Offset ())
@@ -193,7 +197,7 @@ func (r *campaignRepository) ListAll(ctx context.Context, opts ListOptions) ([]C
193197 var c Campaign
194198 if err := rows .Scan (
195199 & c .ID , & c .Name , & c .Slug , & c .Description , & c .IsPublic ,
196- & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout ,
200+ & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout , & c . OwnerDashboardLayout ,
197201 & c .CreatedBy , & c .CreatedAt , & c .UpdatedAt ,
198202 ); err != nil {
199203 return nil , 0 , fmt .Errorf ("scanning campaign row: %w" , err )
@@ -206,7 +210,7 @@ func (r *campaignRepository) ListAll(ctx context.Context, opts ListOptions) ([]C
206210// ListPublic returns public campaigns ordered by most recently updated.
207211// Used for the public landing page to showcase discoverable campaigns.
208212func (r * campaignRepository ) ListPublic (ctx context.Context , limit int ) ([]Campaign , error ) {
209- query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, created_by, created_at, updated_at
213+ query := `SELECT id, name, slug, description, is_public, settings, backdrop_path, sidebar_config, dashboard_layout, owner_dashboard_layout, created_by, created_at, updated_at
210214 FROM campaigns WHERE is_public = 1
211215 ORDER BY updated_at DESC LIMIT ?`
212216
@@ -221,7 +225,7 @@ func (r *campaignRepository) ListPublic(ctx context.Context, limit int) ([]Campa
221225 var c Campaign
222226 if err := rows .Scan (
223227 & c .ID , & c .Name , & c .Slug , & c .Description , & c .IsPublic ,
224- & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout ,
228+ & c .Settings , & c .BackdropPath , & c .SidebarConfig , & c .DashboardLayout , & c . OwnerDashboardLayout ,
225229 & c .CreatedBy , & c .CreatedAt , & c .UpdatedAt ,
226230 ); err != nil {
227231 return nil , fmt .Errorf ("scanning public campaign row: %w" , err )
@@ -353,6 +357,23 @@ func (r *campaignRepository) UpdateDashboardLayout(ctx context.Context, campaign
353357 return nil
354358}
355359
360+ // UpdateOwnerDashboardLayout updates only the owner_dashboard_layout JSON for a campaign.
361+ // Pass nil to revert to the hardcoded default owner dashboard.
362+ func (r * campaignRepository ) UpdateOwnerDashboardLayout (ctx context.Context , campaignID string , layoutJSON * string ) error {
363+ result , err := r .db .ExecContext (ctx ,
364+ `UPDATE campaigns SET owner_dashboard_layout = ?, updated_at = NOW() WHERE id = ?` ,
365+ layoutJSON , campaignID ,
366+ )
367+ if err != nil {
368+ return fmt .Errorf ("updating owner dashboard layout: %w" , err )
369+ }
370+ rows , _ := result .RowsAffected ()
371+ if rows == 0 {
372+ return apperror .NewNotFound ("campaign not found" )
373+ }
374+ return nil
375+ }
376+
356377// --- Membership ---
357378
358379// AddMember inserts a new campaign membership row.
0 commit comments