152 lines
4.7 KiB
Go
152 lines
4.7 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package project
|
|
|
|
import (
|
|
"context"
|
|
|
|
project_model "code.gitea.io/gitea/models/project"
|
|
"code.gitea.io/gitea/services/convert"
|
|
"code.gitea.io/gitea/services/project_events"
|
|
)
|
|
|
|
// CreateColumn inserts a new column into a project and publishes a
|
|
// ColumnCreated event. Routers should call this instead of
|
|
// project_model.NewColumn so the SSE side-effect fires uniformly across
|
|
// repo, user, and org scopes.
|
|
func CreateColumn(ctx context.Context, column *project_model.Column) error {
|
|
if err := project_model.NewColumn(ctx, column); err != nil {
|
|
return err
|
|
}
|
|
project_events.PublishColumnCreated(ctx, project_events.ColumnCreated{
|
|
ProjectID: column.ProjectID,
|
|
ColumnID: column.ID,
|
|
Title: column.Title,
|
|
Color: column.Color,
|
|
Sorting: int64(column.Sorting),
|
|
IsDefault: column.Default,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// EditColumn updates a column and publishes a ColumnUpdated event.
|
|
func EditColumn(ctx context.Context, column *project_model.Column) error {
|
|
if err := project_model.UpdateColumn(ctx, column); err != nil {
|
|
return err
|
|
}
|
|
project_events.PublishColumnUpdated(ctx, project_events.ColumnUpdated{
|
|
ProjectID: column.ProjectID,
|
|
ColumnID: column.ID,
|
|
Title: column.Title,
|
|
Color: column.Color,
|
|
Sorting: int64(column.Sorting),
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// DeleteColumn removes a column from a project and publishes the
|
|
// matching ColumnDeleted event. The model layer also moves the
|
|
// column's issues to the project's default column; we publish those
|
|
// individual moves so receiving tabs can patch the DOM without a full
|
|
// reload. We snapshot affected issues *before* the delete so we have
|
|
// their ids; the destination column id is resolved after.
|
|
func DeleteColumn(ctx context.Context, columnID int64) error {
|
|
// Snapshot the column + its issues so we know what to publish
|
|
// after the delete commits. Errors here are non-fatal: we still
|
|
// run the delete, and just skip per-issue events.
|
|
col, snapErr := project_model.GetColumn(ctx, columnID)
|
|
var (
|
|
projectID int64
|
|
movedIssues []int64
|
|
)
|
|
if snapErr == nil {
|
|
projectID = col.ProjectID
|
|
issues, err := col.GetIssues(ctx)
|
|
if err == nil {
|
|
movedIssues = make([]int64, 0, len(issues))
|
|
for _, pi := range issues {
|
|
movedIssues = append(movedIssues, pi.IssueID)
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := project_model.DeleteColumnByID(ctx, columnID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if snapErr != nil || projectID == 0 {
|
|
return nil
|
|
}
|
|
project_events.PublishColumnDeleted(ctx, project_events.ColumnDeleted{
|
|
ProjectID: projectID,
|
|
ColumnID: columnID,
|
|
})
|
|
|
|
// Resolve the new (default) column to attach to the per-issue
|
|
// CardMoved events. Failures here are tolerated; the frontend
|
|
// already knows the column is gone and will simply render its
|
|
// next refresh as authoritative.
|
|
project, err := project_model.GetProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defaultCol, err := project.MustDefaultColumn(ctx)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
for _, issueID := range movedIssues {
|
|
project_events.PublishCardMoved(ctx, project_events.CardMoved{
|
|
ProjectID: projectID,
|
|
IssueID: issueID,
|
|
FromColumnID: columnID,
|
|
ToColumnID: defaultCol.ID,
|
|
})
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ReorderColumns persists a new sort order for project columns and
|
|
// publishes a ColumnReordered batch event.
|
|
func ReorderColumns(ctx context.Context, project *project_model.Project, sortedColumnIDs map[int64]int64) error {
|
|
if err := project_model.MoveColumnsOnProject(ctx, project, sortedColumnIDs); err != nil {
|
|
return err
|
|
}
|
|
cols := make([]project_events.ColumnSort, 0, len(sortedColumnIDs))
|
|
for sorting, columnID := range sortedColumnIDs {
|
|
cols = append(cols, project_events.ColumnSort{
|
|
ColumnID: columnID,
|
|
Sorting: sorting,
|
|
})
|
|
}
|
|
project_events.PublishColumnReordered(ctx, project_events.ColumnReordered{
|
|
ProjectID: project.ID,
|
|
Columns: cols,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// DeleteProject deletes a project and publishes a ProjectDeleted event.
|
|
func DeleteProject(ctx context.Context, projectID int64) error {
|
|
if err := project_model.DeleteProjectByID(ctx, projectID); err != nil {
|
|
return err
|
|
}
|
|
project_events.PublishProjectDeleted(ctx, project_events.ProjectDeleted{
|
|
ProjectID: projectID,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// publishProjectUpdated emits a ProjectUpdated event for the current
|
|
// state of the given project. It is exported via UpdateProject in
|
|
// project.go after the txn commits.
|
|
func publishProjectUpdated(ctx context.Context, project *project_model.Project) {
|
|
project_events.PublishProjectUpdated(ctx, project_events.ProjectUpdated{
|
|
ProjectID: project.ID,
|
|
Title: project.Title,
|
|
Description: project.Description,
|
|
CardType: convert.ProjectCardTypeToString(project.CardType),
|
|
IsClosed: project.IsClosed,
|
|
})
|
|
}
|