From 438f367ab3a5dde16b136cf517a1958718aa8dea Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 27 Apr 2026 12:19:57 +0200 Subject: [PATCH] Simplify - Drop lazy LoadRepo/LoadOwner in convert.ToProject; rely on caller preloading. ListProjects sets Repo from ctx.Repo.Repository on each project; CreateProject does the same on the new project. Avoids N+1 queries for repo-scoped list endpoints. - Strip redundant API struct field comments that just restate the field name; keep the ones that document enum values. - Pre-allocate GetColumnsByIDs result slice with len(columnsIDs). - Fix CountProjectColumns doc comment (was "CountColumns"). Co-Authored-By: Claude (Opus 4.7) --- models/project/column_list.go | 4 +- modules/structs/project.go | 75 +++++++++++----------------------- routers/api/v1/repo/project.go | 4 ++ services/convert/project.go | 14 +++---- templates/swagger/v1_json.tmpl | 31 +------------- 5 files changed, 35 insertions(+), 93 deletions(-) diff --git a/models/project/column_list.go b/models/project/column_list.go index b3041a15e1..2016db3357 100644 --- a/models/project/column_list.go +++ b/models/project/column_list.go @@ -9,7 +9,7 @@ import ( "code.gitea.io/gitea/models/db" ) -// CountColumns returns the total number of columns for a project +// CountProjectColumns returns the total number of columns for a project func CountProjectColumns(ctx context.Context, projectID int64) (int64, error) { return db.GetEngine(ctx).Where("project_id=?", projectID).Count(&Column{}) } @@ -28,7 +28,7 @@ func GetProjectColumns(ctx context.Context, projectID int64, opts db.ListOptions } func GetColumnsByIDs(ctx context.Context, projectID int64, columnsIDs []int64) (ColumnList, error) { - columns := make([]*Column, 0, 5) + columns := make([]*Column, 0, len(columnsIDs)) if len(columnsIDs) == 0 { return columns, nil } diff --git a/modules/structs/project.go b/modules/structs/project.go index 5966ef0065..e9eea60f5e 100644 --- a/modules/structs/project.go +++ b/modules/structs/project.go @@ -10,51 +10,36 @@ import ( // Project represents a project // swagger:model type Project struct { - // Unique identifier of the project - ID int64 `json:"id"` - // Project title - Title string `json:"title"` - // Project description + ID int64 `json:"id"` + Title string `json:"title"` Description string `json:"description"` - // Owner ID (for organization or user projects) - OwnerID int64 `json:"owner_id,omitempty"` - // Repository ID (for repository projects) - RepoID int64 `json:"repo_id,omitempty"` - // Creator ID - CreatorID int64 `json:"creator_id"` - // Whether the project is closed - IsClosed bool `json:"is_closed"` + OwnerID int64 `json:"owner_id,omitempty"` + RepoID int64 `json:"repo_id,omitempty"` + CreatorID int64 `json:"creator_id"` + IsClosed bool `json:"is_closed"` // Template type: 0=none, 1=basic_kanban, 2=bug_triage TemplateType int `json:"template_type"` // Card type: 0=text_only, 1=images_and_text CardType int `json:"card_type"` // Project type: 1=individual, 2=repository, 3=organization - Type int `json:"type"` - // Number of open issues - NumOpenIssues int64 `json:"num_open_issues,omitempty"` - // Number of closed issues + Type int `json:"type"` + NumOpenIssues int64 `json:"num_open_issues,omitempty"` NumClosedIssues int64 `json:"num_closed_issues,omitempty"` - // Total number of issues - NumIssues int64 `json:"num_issues,omitempty"` - // Created time + NumIssues int64 `json:"num_issues,omitempty"` // swagger:strfmt date-time Created time.Time `json:"created"` - // Updated time // swagger:strfmt date-time Updated time.Time `json:"updated"` - // Closed time // swagger:strfmt date-time ClosedDate *time.Time `json:"closed_date,omitempty"` - // Project URL - URL string `json:"url,omitempty"` + URL string `json:"url,omitempty"` } // CreateProjectOption represents options for creating a project // swagger:model type CreateProjectOption struct { // required: true - Title string `json:"title" binding:"Required"` - // Project description + Title string `json:"title" binding:"Required"` Description string `json:"description"` // Template type: 0=none, 1=basic_kanban, 2=bug_triage TemplateType int `json:"template_type"` @@ -65,9 +50,7 @@ type CreateProjectOption struct { // EditProjectOption represents options for editing a project // swagger:model type EditProjectOption struct { - // Project title - Title *string `json:"title,omitempty"` - // Project description + Title *string `json:"title,omitempty"` Description *string `json:"description,omitempty"` // Card type: 0=text_only, 1=images_and_text CardType *int `json:"card_type,omitempty"` @@ -78,26 +61,16 @@ type EditProjectOption struct { // ProjectColumn represents a project column (board) // swagger:model type ProjectColumn struct { - // Unique identifier of the column - ID int64 `json:"id"` - // Column title - Title string `json:"title"` - // Whether this is the default column - Default bool `json:"default"` - // Sorting order - Sorting int `json:"sorting"` - // Column color (hex format) - Color string `json:"color,omitempty"` - // Project ID - ProjectID int64 `json:"project_id"` - // Creator ID - CreatorID int64 `json:"creator_id"` - // Number of issues in this column - NumIssues int64 `json:"num_issues,omitempty"` - // Created time + ID int64 `json:"id"` + Title string `json:"title"` + Default bool `json:"default"` + Sorting int `json:"sorting"` + Color string `json:"color,omitempty"` + ProjectID int64 `json:"project_id"` + CreatorID int64 `json:"creator_id"` + NumIssues int64 `json:"num_issues,omitempty"` // swagger:strfmt date-time Created time.Time `json:"created"` - // Updated time // swagger:strfmt date-time Updated time.Time `json:"updated"` } @@ -107,17 +80,15 @@ type ProjectColumn struct { type CreateProjectColumnOption struct { // required: true Title string `json:"title" binding:"Required"` - // Column color (hex format, e.g., #FF0000) + // Column color (hex format, e.g. #FF0000) Color string `json:"color,omitempty"` } // EditProjectColumnOption represents options for editing a project column // swagger:model type EditProjectColumnOption struct { - // Column title Title *string `json:"title,omitempty"` // Column color (hex format) - Color *string `json:"color,omitempty"` - // Sorting order - Sorting *int `json:"sorting,omitempty"` + Color *string `json:"color,omitempty"` + Sorting *int `json:"sorting,omitempty"` } diff --git a/routers/api/v1/repo/project.go b/routers/api/v1/repo/project.go index ee0dafb12a..cb9177dc1b 100644 --- a/routers/api/v1/repo/project.go +++ b/routers/api/v1/repo/project.go @@ -118,6 +118,9 @@ func ListProjects(ctx *context.APIContext) { return } + for _, p := range projects { + p.Repo = ctx.Repo.Repository + } apiProjects := convert.ToProjectList(ctx, projects) ctx.SetLinkHeader(count, listOptions.PageSize) @@ -217,6 +220,7 @@ func CreateProject(ctx *context.APIContext) { return } + p.Repo = ctx.Repo.Repository ctx.JSON(http.StatusCreated, convert.ToProject(ctx, p)) } diff --git a/services/convert/project.go b/services/convert/project.go index 75288c2330..b9c81ccd35 100644 --- a/services/convert/project.go +++ b/services/convert/project.go @@ -35,15 +35,11 @@ func ToProject(ctx context.Context, p *project_model.Project) *api.Project { project.ClosedDate = &t } - // Generate project URL - if p.Type == project_model.TypeRepository && p.RepoID > 0 { - if err := p.LoadRepo(ctx); err == nil && p.Repo != nil { - project.URL = project_model.ProjectLinkForRepo(p.Repo, p.ID) - } - } else if p.OwnerID > 0 { - if err := p.LoadOwner(ctx); err == nil && p.Owner != nil { - project.URL = project_model.ProjectLinkForOrg(p.Owner, p.ID) - } + // Repo/Owner are expected to be preloaded by the caller to avoid N+1 lookups. + if p.Type == project_model.TypeRepository && p.Repo != nil { + project.URL = project_model.ProjectLinkForRepo(p.Repo, p.ID) + } else if p.Owner != nil { + project.URL = project_model.ProjectLinkForOrg(p.Owner, p.ID) } return project diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index d895017e04..6c0cd24f00 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -24160,7 +24160,7 @@ ], "properties": { "color": { - "description": "Column color (hex format, e.g., #FF0000)", + "description": "Column color (hex format, e.g. #FF0000)", "type": "string", "x-go-name": "Color" }, @@ -24185,7 +24185,6 @@ "x-go-name": "CardType" }, "description": { - "description": "Project description", "type": "string", "x-go-name": "Description" }, @@ -25332,13 +25331,11 @@ "x-go-name": "Color" }, "sorting": { - "description": "Sorting order", "type": "integer", "format": "int64", "x-go-name": "Sorting" }, "title": { - "description": "Column title", "type": "string", "x-go-name": "Title" } @@ -25356,7 +25353,6 @@ "x-go-name": "CardType" }, "description": { - "description": "Project description", "type": "string", "x-go-name": "Description" }, @@ -25366,7 +25362,6 @@ "x-go-name": "State" }, "title": { - "description": "Project title", "type": "string", "x-go-name": "Title" } @@ -28081,65 +28076,54 @@ "x-go-name": "CardType" }, "closed_date": { - "description": "Closed time", "type": "string", "format": "date-time", "x-go-name": "ClosedDate" }, "created": { - "description": "Created time", "type": "string", "format": "date-time", "x-go-name": "Created" }, "creator_id": { - "description": "Creator ID", "type": "integer", "format": "int64", "x-go-name": "CreatorID" }, "description": { - "description": "Project description", "type": "string", "x-go-name": "Description" }, "id": { - "description": "Unique identifier of the project", "type": "integer", "format": "int64", "x-go-name": "ID" }, "is_closed": { - "description": "Whether the project is closed", "type": "boolean", "x-go-name": "IsClosed" }, "num_closed_issues": { - "description": "Number of closed issues", "type": "integer", "format": "int64", "x-go-name": "NumClosedIssues" }, "num_issues": { - "description": "Total number of issues", "type": "integer", "format": "int64", "x-go-name": "NumIssues" }, "num_open_issues": { - "description": "Number of open issues", "type": "integer", "format": "int64", "x-go-name": "NumOpenIssues" }, "owner_id": { - "description": "Owner ID (for organization or user projects)", "type": "integer", "format": "int64", "x-go-name": "OwnerID" }, "repo_id": { - "description": "Repository ID (for repository projects)", "type": "integer", "format": "int64", "x-go-name": "RepoID" @@ -28151,7 +28135,6 @@ "x-go-name": "TemplateType" }, "title": { - "description": "Project title", "type": "string", "x-go-name": "Title" }, @@ -28162,13 +28145,11 @@ "x-go-name": "Type" }, "updated": { - "description": "Updated time", "type": "string", "format": "date-time", "x-go-name": "Updated" }, "url": { - "description": "Project URL", "type": "string", "x-go-name": "URL" } @@ -28180,58 +28161,48 @@ "type": "object", "properties": { "color": { - "description": "Column color (hex format)", "type": "string", "x-go-name": "Color" }, "created": { - "description": "Created time", "type": "string", "format": "date-time", "x-go-name": "Created" }, "creator_id": { - "description": "Creator ID", "type": "integer", "format": "int64", "x-go-name": "CreatorID" }, "default": { - "description": "Whether this is the default column", "type": "boolean", "x-go-name": "Default" }, "id": { - "description": "Unique identifier of the column", "type": "integer", "format": "int64", "x-go-name": "ID" }, "num_issues": { - "description": "Number of issues in this column", "type": "integer", "format": "int64", "x-go-name": "NumIssues" }, "project_id": { - "description": "Project ID", "type": "integer", "format": "int64", "x-go-name": "ProjectID" }, "sorting": { - "description": "Sorting order", "type": "integer", "format": "int64", "x-go-name": "Sorting" }, "title": { - "description": "Column title", "type": "string", "x-go-name": "Title" }, "updated": { - "description": "Updated time", "type": "string", "format": "date-time", "x-go-name": "Updated"