@@ -9,24 +9,50 @@ import (
99 "time"
1010 "windshift/internal/database"
1111 "windshift/internal/models"
12+ "windshift/internal/services"
1213 "windshift/internal/utils"
1314)
1415
1516type IterationHandler struct {
16- db database.Database
17+ db database.Database
18+ permissionService * services.PermissionService
1719}
1820
19- func NewIterationHandler (db database.Database ) * IterationHandler {
20- return & IterationHandler {db : db }
21+ func NewIterationHandler (db database.Database , permissionService * services.PermissionService ) * IterationHandler {
22+ return & IterationHandler {
23+ db : db ,
24+ permissionService : permissionService ,
25+ }
2126}
2227
2328func (h * IterationHandler ) GetAll (w http.ResponseWriter , r * http.Request ) {
29+ user , ok := RequireAuth (w , r )
30+ if ! ok {
31+ return
32+ }
33+
2434 // Parse query parameters
2535 workspaceID := r .URL .Query ().Get ("workspace_id" )
2636 typeID := r .URL .Query ().Get ("type_id" )
2737 status := r .URL .Query ().Get ("status" )
2838 includeGlobal := r .URL .Query ().Get ("include_global" ) != "false" // Default to true
2939
40+ // Check workspace permission if workspace_id is specified
41+ if workspaceID != "" {
42+ if wsID , err := strconv .Atoi (workspaceID ); err == nil {
43+ if ! RequireWorkspacePermission (w , user .ID , wsID , models .PermissionItemView , h .permissionService ) {
44+ return
45+ }
46+ }
47+ } else {
48+ // For global-only iterations, check global iteration permission
49+ hasGlobalPerm , err := h .permissionService .HasGlobalPermission (user .ID , models .PermissionIterationManage )
50+ if err != nil || ! hasGlobalPerm {
51+ http .Error (w , "Forbidden" , http .StatusForbidden )
52+ return
53+ }
54+ }
55+
3056 query := `
3157 SELECT i.id, i.name, i.description, i.start_date, i.end_date, i.status,
3258 i.type_id, i.is_global, i.workspace_id, i.created_at, i.updated_at,
@@ -118,6 +144,11 @@ func (h *IterationHandler) GetAll(w http.ResponseWriter, r *http.Request) {
118144}
119145
120146func (h * IterationHandler ) Get (w http.ResponseWriter , r * http.Request ) {
147+ user , ok := RequireAuth (w , r )
148+ if ! ok {
149+ return
150+ }
151+
121152 id , ok := requireIDParam (w , r , "id" )
122153 if ! ok {
123154 return
@@ -153,6 +184,19 @@ func (h *IterationHandler) Get(w http.ResponseWriter, r *http.Request) {
153184 return
154185 }
155186
187+ // Check permission based on whether iteration is global or workspace-scoped
188+ if iteration .IsGlobal {
189+ hasGlobalPerm , err := h .permissionService .HasGlobalPermission (user .ID , models .PermissionIterationManage )
190+ if err != nil || ! hasGlobalPerm {
191+ http .Error (w , "Forbidden" , http .StatusForbidden )
192+ return
193+ }
194+ } else if workspaceID .Valid {
195+ if ! RequireWorkspacePermission (w , user .ID , int (workspaceID .Int64 ), models .PermissionItemView , h .permissionService ) {
196+ return
197+ }
198+ }
199+
156200 iteration .Description = description .String
157201 iteration .TypeID = utils .NullInt64ToPtr (typeID )
158202 iteration .TypeName = typeName .String
@@ -164,6 +208,11 @@ func (h *IterationHandler) Get(w http.ResponseWriter, r *http.Request) {
164208}
165209
166210func (h * IterationHandler ) Create (w http.ResponseWriter , r * http.Request ) {
211+ user , ok := RequireAuth (w , r )
212+ if ! ok {
213+ return
214+ }
215+
167216 var iteration models.Iteration
168217 if err := json .NewDecoder (r .Body ).Decode (& iteration ); err != nil {
169218 http .Error (w , err .Error (), http .StatusBadRequest )
@@ -209,6 +258,19 @@ func (h *IterationHandler) Create(w http.ResponseWriter, r *http.Request) {
209258 return
210259 }
211260
261+ // Check permission based on whether iteration is global or workspace-scoped
262+ if iteration .IsGlobal {
263+ hasGlobalPerm , err := h .permissionService .HasGlobalPermission (user .ID , models .PermissionIterationManage )
264+ if err != nil || ! hasGlobalPerm {
265+ http .Error (w , "Forbidden" , http .StatusForbidden )
266+ return
267+ }
268+ } else {
269+ if ! RequireWorkspacePermission (w , user .ID , * iteration .WorkspaceID , models .PermissionItemEdit , h .permissionService ) {
270+ return
271+ }
272+ }
273+
212274 // Validate type_id if provided
213275 if iteration .TypeID != nil {
214276 var typeExists int
@@ -288,6 +350,11 @@ func (h *IterationHandler) Create(w http.ResponseWriter, r *http.Request) {
288350}
289351
290352func (h * IterationHandler ) Update (w http.ResponseWriter , r * http.Request ) {
353+ user , ok := RequireAuth (w , r )
354+ if ! ok {
355+ return
356+ }
357+
291358 id , ok := requireIDParam (w , r , "id" )
292359 if ! ok {
293360 return
@@ -339,6 +406,19 @@ func (h *IterationHandler) Update(w http.ResponseWriter, r *http.Request) {
339406 return
340407 }
341408
409+ // Check permission based on whether iteration is global or workspace-scoped
410+ if iteration .IsGlobal {
411+ hasGlobalPerm , err := h .permissionService .HasGlobalPermission (user .ID , models .PermissionIterationManage )
412+ if err != nil || ! hasGlobalPerm {
413+ http .Error (w , "Forbidden" , http .StatusForbidden )
414+ return
415+ }
416+ } else {
417+ if ! RequireWorkspacePermission (w , user .ID , * iteration .WorkspaceID , models .PermissionItemEdit , h .permissionService ) {
418+ return
419+ }
420+ }
421+
342422 // Validate type_id if provided
343423 if iteration .TypeID != nil {
344424 var typeExists int
@@ -418,12 +498,43 @@ func (h *IterationHandler) Update(w http.ResponseWriter, r *http.Request) {
418498}
419499
420500func (h * IterationHandler ) Delete (w http.ResponseWriter , r * http.Request ) {
501+ user , ok := RequireAuth (w , r )
502+ if ! ok {
503+ return
504+ }
505+
421506 id , ok := requireIDParam (w , r , "id" )
422507 if ! ok {
423508 return
424509 }
425510
426- _ , err := h .db .ExecWrite ("DELETE FROM iterations WHERE id = ?" , id )
511+ // First, fetch the iteration to check its properties for permission validation
512+ var isGlobal bool
513+ var workspaceID sql.NullInt64
514+ err := h .db .QueryRow ("SELECT is_global, workspace_id FROM iterations WHERE id = ?" , id ).Scan (& isGlobal , & workspaceID )
515+ if err == sql .ErrNoRows {
516+ http .Error (w , "Iteration not found" , http .StatusNotFound )
517+ return
518+ }
519+ if err != nil {
520+ http .Error (w , err .Error (), http .StatusInternalServerError )
521+ return
522+ }
523+
524+ // Check permission based on whether iteration is global or workspace-scoped
525+ if isGlobal {
526+ hasGlobalPerm , err := h .permissionService .HasGlobalPermission (user .ID , models .PermissionIterationManage )
527+ if err != nil || ! hasGlobalPerm {
528+ http .Error (w , "Forbidden" , http .StatusForbidden )
529+ return
530+ }
531+ } else if workspaceID .Valid {
532+ if ! RequireWorkspacePermission (w , user .ID , int (workspaceID .Int64 ), models .PermissionItemEdit , h .permissionService ) {
533+ return
534+ }
535+ }
536+
537+ _ , err = h .db .ExecWrite ("DELETE FROM iterations WHERE id = ?" , id )
427538 if err != nil {
428539 http .Error (w , err .Error (), http .StatusInternalServerError )
429540 return
@@ -473,25 +584,32 @@ type IterationProgressReport struct {
473584
474585// GetProgress handles GET /api/iterations/{id}/progress - returns iteration progress report
475586func (h * IterationHandler ) GetProgress (w http.ResponseWriter , r * http.Request ) {
587+ user , ok := RequireAuth (w , r )
588+ if ! ok {
589+ return
590+ }
591+
476592 iterationID , ok := requireIDParam (w , r , "id" )
477593 if ! ok {
478594 return
479595 }
480596
481- // Get iteration details
597+ // Get iteration details including is_global and workspace_id for permission check
482598 var report IterationProgressReport
483599 report .IterationID = iterationID
484600 report .ItemsByCategory = make (map [string ][]IterationProgressItem )
485601
486602 var description sql.NullString
487603 var typeColor sql.NullString
604+ var isGlobal bool
605+ var workspaceID sql.NullInt64
488606
489607 err := h .db .QueryRow (`
490- SELECT i.name, i.description, i.start_date, i.end_date, i.status, it.color
608+ SELECT i.name, i.description, i.start_date, i.end_date, i.status, it.color, i.is_global, i.workspace_id
491609 FROM iterations i
492610 LEFT JOIN iteration_types it ON i.type_id = it.id
493611 WHERE i.id = ?
494- ` , iterationID ).Scan (& report .IterationName , & description , & report .StartDate , & report .EndDate , & report .Status , & typeColor )
612+ ` , iterationID ).Scan (& report .IterationName , & description , & report .StartDate , & report .EndDate , & report .Status , & typeColor , & isGlobal , & workspaceID )
495613
496614 if err == sql .ErrNoRows {
497615 http .Error (w , "Iteration not found" , http .StatusNotFound )
@@ -502,6 +620,19 @@ func (h *IterationHandler) GetProgress(w http.ResponseWriter, r *http.Request) {
502620 return
503621 }
504622
623+ // Check permission based on whether iteration is global or workspace-scoped
624+ if isGlobal {
625+ hasGlobalPerm , err := h .permissionService .HasGlobalPermission (user .ID , models .PermissionIterationManage )
626+ if err != nil || ! hasGlobalPerm {
627+ http .Error (w , "Forbidden" , http .StatusForbidden )
628+ return
629+ }
630+ } else if workspaceID .Valid {
631+ if ! RequireWorkspacePermission (w , user .ID , int (workspaceID .Int64 ), models .PermissionItemView , h .permissionService ) {
632+ return
633+ }
634+ }
635+
505636 report .Description = description .String
506637 report .TypeColor = typeColor .String
507638
0 commit comments