// Copyright 2025 The go-github AUTHORS. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package github import ( "context" "encoding/json" "fmt" "strconv" ) // ProjectsService handles communication with the project V2 // methods of the GitHub API. // // GitHub API docs: https://docs.github.com/rest/projects?apiVersion=2022-11-28 type ProjectsService service // ProjectV2ItemContentType represents the type of content in a ProjectV2Item. type ProjectV2ItemContentType string // This is the set of possible content types for a ProjectV2Item. const ( ProjectV2ItemContentTypeDraftIssue ProjectV2ItemContentType = "DraftIssue" ProjectV2ItemContentTypeIssue ProjectV2ItemContentType = "Issue" ProjectV2ItemContentTypePullRequest ProjectV2ItemContentType = "PullRequest" ) // ProjectV2StatusUpdate represents a status update for a project. type ProjectV2StatusUpdate struct { ID *int64 `json:"id,omitempty"` NodeID *string `json:"node_id,omitempty"` ProjectNodeID *string `json:"project_node_id,omitempty"` Creator *User `json:"creator,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` // Status can be one of: "INACTIVE", "ON_TRACK", "AT_RISK", "OFF_TRACK", "COMPLETE". Status *string `json:"status,omitempty"` StartDate *string `json:"start_date,omitempty"` TargetDate *string `json:"target_date,omitempty"` Body *string `json:"body,omitempty"` } // ProjectV2DraftIssue represents a draft issue in a project. type ProjectV2DraftIssue struct { ID *int64 `json:"id,omitempty"` NodeID *string `json:"node_id,omitempty"` Title *string `json:"title,omitempty"` Body *string `json:"body,omitempty"` User *User `json:"user,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` } // ProjectV2 represents a v2 project. type ProjectV2 struct { ID *int64 `json:"id,omitempty"` NodeID *string `json:"node_id,omitempty"` Owner *User `json:"owner,omitempty"` Creator *User `json:"creator,omitempty"` Title *string `json:"title,omitempty"` Description *string `json:"description,omitempty"` Public *bool `json:"public,omitempty"` ClosedAt *Timestamp `json:"closed_at,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` DeletedAt *Timestamp `json:"deleted_at,omitempty"` Number *int `json:"number,omitempty"` ShortDescription *string `json:"short_description,omitempty"` DeletedBy *User `json:"deleted_by,omitempty"` State *string `json:"state,omitempty"` LatestStatusUpdate *ProjectV2StatusUpdate `json:"latest_status_update,omitempty"` IsTemplate *bool `json:"is_template,omitempty"` // Fields migrated from the Project (classic) struct: URL *string `json:"url,omitempty"` HTMLURL *string `json:"html_url,omitempty"` ColumnsURL *string `json:"columns_url,omitempty"` OwnerURL *string `json:"owner_url,omitempty"` Name *string `json:"name,omitempty"` Body *string `json:"body,omitempty"` OrganizationPermission *string `json:"organization_permission,omitempty"` Private *bool `json:"private,omitempty"` } func (p ProjectV2) String() string { return Stringify(p) } // ListProjectsPaginationOptions specifies optional parameters to list projects for user / organization. // // Note: Pagination is powered by before/after cursor-style pagination. After the initial call, // inspect the returned *Response. Use resp.After as the opts.After value to request // the next page, and resp.Before as the opts.Before value to request the previous // page. Set either Before or After for a request; if both are // supplied GitHub API will return an error. PerPage controls the number of items // per page (max 100 per GitHub API docs). type ListProjectsPaginationOptions struct { // A cursor, as given in the Link header. If specified, the query only searches for events before this cursor. Before string `url:"before,omitempty"` // A cursor, as given in the Link header. If specified, the query only searches for events after this cursor. After string `url:"after,omitempty"` // For paginated result sets, the number of results to include per page. PerPage int `url:"per_page,omitempty"` } // ListProjectsOptions specifies optional parameters to list projects for user / organization. type ListProjectsOptions struct { ListProjectsPaginationOptions // Q is an optional query string to limit results to projects of the specified type. Query string `url:"q,omitempty"` } // ProjectV2TextContent represents text content in a project field option or iteration. // It includes both HTML and raw text representations. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28 type ProjectV2TextContent struct { HTML *string `json:"html,omitempty"` Raw *string `json:"raw,omitempty"` } // ProjectV2FieldOption represents an option for a project field of type single_select or multi_select. // It defines the available choices that can be selected for dropdown-style fields. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28 type ProjectV2FieldOption struct { ID *string `json:"id,omitempty"` // The unique identifier for this option. Color *string `json:"color,omitempty"` // The color associated with this option (e.g., "blue", "red"). Description *ProjectV2TextContent `json:"description,omitempty"` // An optional description for this option. Name *ProjectV2TextContent `json:"name,omitempty"` // The display name of the option. } // ProjectV2FieldIteration represents an iteration within a project field of type iteration. // It defines a specific time-bound period that can be associated with project items. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28 type ProjectV2FieldIteration struct { ID *string `json:"id,omitempty"` // The unique identifier for the iteration. Title *ProjectV2TextContent `json:"title,omitempty"` // The title of the iteration. StartDate *string `json:"start_date,omitempty"` // The start date of the iteration in ISO 8601 format. Duration *int `json:"duration,omitempty"` // The duration of the iteration in seconds. } // ProjectV2FieldConfiguration represents the configuration for a project field of type iteration. // It defines settings such as duration and start day for iterations within the project. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28 type ProjectV2FieldConfiguration struct { Duration *int `json:"duration,omitempty"` // The duration of the iteration field in seconds. StartDay *int `json:"start_day,omitempty"` // The start day for the iteration. Iterations []*ProjectV2FieldIteration `json:"iterations,omitempty"` // The list of iterations associated with the configuration. } // ProjectV2ItemContent is a union type that holds the content of a ProjectV2Item. // The actual type depends on the ContentType field of the parent ProjectV2Item. // Only one of the fields will be populated after unmarshaling. type ProjectV2ItemContent struct { Issue *Issue `json:"-"` PullRequest *PullRequest `json:"-"` DraftIssue *ProjectV2DraftIssue `json:"-"` } // MarshalJSON implements custom marshaling for ProjectV2ItemContent. func (c ProjectV2ItemContent) MarshalJSON() ([]byte, error) { if c.Issue != nil { return json.Marshal(c.Issue) } if c.PullRequest != nil { return json.Marshal(c.PullRequest) } if c.DraftIssue != nil { return json.Marshal(c.DraftIssue) } return []byte("null"), nil } // ProjectV2Item represents a full project item with field values. // This type is used by Get, List, and Update operations which return field values. // The Content field is automatically unmarshaled into the appropriate type based on ContentType. type ProjectV2Item struct { ArchivedAt *Timestamp `json:"archived_at,omitempty"` Content *ProjectV2ItemContent `json:"content,omitempty"` ContentType *ProjectV2ItemContentType `json:"content_type,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` Creator *User `json:"creator,omitempty"` Fields []*ProjectV2ItemFieldValue `json:"fields,omitempty"` ID *int64 `json:"id,omitempty"` ItemURL *string `json:"item_url,omitempty"` NodeID *string `json:"node_id,omitempty"` ProjectURL *string `json:"project_url,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` // ProjectNodeID and ContentNodeID are used in ProjectsV2Item Webhook payloads. // They may not be populated in all API responses, but are included here for completeness. // See: https://docs.github.com/en/webhooks/webhook-events-and-payloads#projects_v2_item ProjectNodeID *string `json:"project_node_id,omitempty"` ContentNodeID *string `json:"content_node_id,omitempty"` } // UnmarshalJSON implements custom unmarshaling for ProjectV2Item. // It uses the ContentType field to determine how to unmarshal the Content field. func (p *ProjectV2Item) UnmarshalJSON(data []byte) error { type contentAlias ProjectV2Item aux := &struct { Content json.RawMessage `json:"content,omitempty"` *contentAlias }{ contentAlias: (*contentAlias)(p), } if err := json.Unmarshal(data, aux); err != nil { return err } // Now unmarshal the content based on ContentType if len(aux.Content) > 0 && string(aux.Content) != "null" && p.ContentType != nil { p.Content = &ProjectV2ItemContent{} switch *p.ContentType { case ProjectV2ItemContentTypeIssue: p.Content.Issue = &Issue{} return json.Unmarshal(aux.Content, p.Content.Issue) case ProjectV2ItemContentTypePullRequest: p.Content.PullRequest = &PullRequest{} return json.Unmarshal(aux.Content, p.Content.PullRequest) case ProjectV2ItemContentTypeDraftIssue: p.Content.DraftIssue = &ProjectV2DraftIssue{} return json.Unmarshal(aux.Content, p.Content.DraftIssue) } } return nil } // ProjectV2Field represents a field in a GitHub Projects V2 project. // Fields define the structure and data types for project items. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28 type ProjectV2Field struct { ID *int64 `json:"id,omitempty"` NodeID *string `json:"node_id,omitempty"` Name *string `json:"name,omitempty"` DataType *string `json:"data_type,omitempty"` ProjectURL *string `json:"project_url,omitempty"` Options []*ProjectV2FieldOption `json:"options,omitempty"` Configuration *ProjectV2FieldConfiguration `json:"configuration,omitempty"` CreatedAt *Timestamp `json:"created_at,omitempty"` UpdatedAt *Timestamp `json:"updated_at,omitempty"` } // ProjectV2ItemFieldValue represents a field value of a project item. type ProjectV2ItemFieldValue struct { ID *int64 `json:"id,omitempty"` Name *string `json:"name,omitempty"` DataType *string `json:"data_type,omitempty"` // Value set for the field. The type depends on the field type: // - text: string // - number: float64 // - date: string (ISO 8601 date format, e.g. "2023-06-23") or null // - single_select: object with "id", "name", "color", "description" fields or null // - iteration: object with "id", "title", "start_date", "duration" fields or null // - title: object with "text" field (read-only, reflects the item's title) or null // - assignees: array of user objects with "login", "id", etc. or null // - labels: array of label objects with "id", "name", "color", etc. or null // - linked_pull_requests: array of pull request objects or null // - milestone: milestone object with "id", "title", "description", etc. or null // - repository: repository object with "id", "name", "full_name", etc. or null // - reviewers: array of user objects or null // - status: object with "id", "name", "color", "description" fields (same structure as single_select) or null Value any `json:"value,omitempty"` } // ListOrganizationProjects lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/projects?apiVersion=2022-11-28#list-projects-for-organization // //meta:operation GET /orgs/{org}/projectsV2 func (s *ProjectsService) ListOrganizationProjects(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2", org) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var projects []*ProjectV2 resp, err := s.client.Do(req, &projects) if err != nil { return nil, resp, err } return projects, resp, nil } // GetOrganizationProject gets a Projects V2 project for an organization by ID. // // GitHub API docs: https://docs.github.com/rest/projects/projects?apiVersion=2022-11-28#get-project-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number} func (s *ProjectsService) GetOrganizationProject(ctx context.Context, org string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectNumber) req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var project *ProjectV2 resp, err := s.client.Do(req, &project) if err != nil { return nil, resp, err } return project, resp, nil } // ListUserProjects lists Projects V2 for a user. // // GitHub API docs: https://docs.github.com/rest/projects/projects?apiVersion=2022-11-28#list-projects-for-user // //meta:operation GET /users/{username}/projectsV2 func (s *ProjectsService) ListUserProjects(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2", username) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var projects []*ProjectV2 resp, err := s.client.Do(req, &projects) if err != nil { return nil, resp, err } return projects, resp, nil } // GetUserProject gets a Projects V2 project for a user by ID. // // GitHub API docs: https://docs.github.com/rest/projects/projects?apiVersion=2022-11-28#get-project-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number} func (s *ProjectsService) GetUserProject(ctx context.Context, username string, projectNumber int) (*ProjectV2, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber) req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var project *ProjectV2 resp, err := s.client.Do(req, &project) if err != nil { return nil, resp, err } return project, resp, nil } // ListOrganizationProjectFields lists Projects V2 for an organization. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28#list-project-fields-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields func (s *ProjectsService) ListOrganizationProjectFields(ctx context.Context, org string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var fields []*ProjectV2Field resp, err := s.client.Do(req, &fields) if err != nil { return nil, resp, err } return fields, resp, nil } // ListUserProjectFields lists Projects V2 for a user. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28#list-project-fields-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields func (s *ProjectsService) ListUserProjectFields(ctx context.Context, user string, projectNumber int, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields", user, projectNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var fields []*ProjectV2Field resp, err := s.client.Do(req, &fields) if err != nil { return nil, resp, err } return fields, resp, nil } // GetOrganizationProjectField gets a single project field from an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28#get-project-field-for-organization // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id} func (s *ProjectsService) GetOrganizationProjectField(ctx context.Context, org string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields/%v", org, projectNumber, fieldID) req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var field *ProjectV2Field resp, err := s.client.Do(req, &field) if err != nil { return nil, resp, err } return field, resp, nil } // GetUserProjectField gets a single project field from a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28#get-project-field-for-user // //meta:operation GET /users/{username}/projectsV2/{project_number}/fields/{field_id} func (s *ProjectsService) GetUserProjectField(ctx context.Context, user string, projectNumber int, fieldID int64) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields/%v", user, projectNumber, fieldID) req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var field *ProjectV2Field resp, err := s.client.Do(req, &field) if err != nil { return nil, resp, err } return field, resp, nil } // ListProjectItemsOptions specifies optional parameters when listing project items. // Note: Pagination uses before/after cursor-style pagination similar to ListProjectsOptions. // "Fields" can be used to restrict which field values are returned (by their numeric IDs). type ListProjectItemsOptions struct { // Embed ListProjectsOptions to reuse pagination and query parameters. ListProjectsOptions // Fields restricts which field values are returned by numeric field IDs. Fields []int64 `url:"fields,omitempty,comma"` } // GetProjectItemOptions specifies optional parameters when getting a project item. type GetProjectItemOptions struct { // Fields restricts which field values are returned by numeric field IDs. Fields []int64 `url:"fields,omitempty,comma"` } // AddProjectItemRequest represents the payload to add an item (issue or pull request) // to a project. The Type must be either "Issue" or "PullRequest" (as per API docs). // The item is identified either by ID, the numerical ID of that issue or pull request, // or by Owner, Repo, and Number. type AddProjectItemRequest struct { Type ProjectV2ItemContentType `json:"type"` ID *int64 `json:"id,omitempty"` Owner *string `json:"owner,omitempty"` Repo *string `json:"repo,omitempty"` Number *int `json:"number,omitempty"` } // UpdateProjectV2Field represents a field update for a project item. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#update-project-item-for-organization type UpdateProjectV2Field struct { // ID is the field ID to update. ID int64 `json:"id"` // Value is the new value to set for the field. The type depends on the field type. // For text fields: string // For number fields: float64 or int // For single_select fields: string (option ID) // For date fields: string (ISO 8601 date) // For iteration fields: string (iteration ID) // Note: Some field types (title, assignees, labels, etc.) are read-only or managed through other API endpoints. Value any `json:"value"` } // UpdateProjectItemRequest represents fields that can be modified for a project item. type UpdateProjectItemRequest struct { // Fields contains field updates to apply to the project item. // Each entry specifies a field ID and its new value. Fields []*UpdateProjectV2Field `json:"fields"` } // ListOrganizationProjectItems lists items for an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#list-items-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items func (s *ProjectsService) ListOrganizationProjectItems(ctx context.Context, org string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var items []*ProjectV2Item resp, err := s.client.Do(req, &items) if err != nil { return nil, resp, err } return items, resp, nil } // AddOrganizationProjectItem adds an issue or pull request item to an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#add-item-to-organization-owned-project // //meta:operation POST /orgs/{org}/projectsV2/{project_number}/items func (s *ProjectsService) AddOrganizationProjectItem(ctx context.Context, org string, projectNumber int, body AddProjectItemRequest) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items", org, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // GetOrganizationProjectItem gets a single item from an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#get-an-item-for-an-organization-owned-project // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/items/{item_id} func (s *ProjectsService) GetOrganizationProjectItem(ctx context.Context, org string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // UpdateOrganizationProjectItem updates an item in an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#update-project-item-for-organization // //meta:operation PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id} func (s *ProjectsService) UpdateOrganizationProjectItem(ctx context.Context, org string, projectNumber int, itemID int64, body UpdateProjectItemRequest) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest(ctx, "PATCH", u, body) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // DeleteOrganizationProjectItem deletes an item from an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#delete-project-item-for-organization // //meta:operation DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id} func (s *ProjectsService) DeleteOrganizationProjectItem(ctx context.Context, org string, projectNumber int, itemID int64) (*Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/items/%v", org, projectNumber, itemID) req, err := s.client.NewRequest(ctx, "DELETE", u, nil) if err != nil { return nil, err } return s.client.Do(req, nil) } // ListUserProjectItems lists items for a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#list-items-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items func (s *ProjectsService) ListUserProjectItems(ctx context.Context, username string, projectNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var items []*ProjectV2Item resp, err := s.client.Do(req, &items) if err != nil { return nil, resp, err } return items, resp, nil } // AddUserProjectItem adds an issue or pull request item to a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#add-item-to-user-owned-project // //meta:operation POST /users/{username}/projectsV2/{project_number}/items func (s *ProjectsService) AddUserProjectItem(ctx context.Context, username string, projectNumber int, body AddProjectItemRequest) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items", username, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // GetUserProjectItem gets a single item from a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#get-an-item-for-a-user-owned-project // //meta:operation GET /users/{username}/projectsV2/{project_number}/items/{item_id} func (s *ProjectsService) GetUserProjectItem(ctx context.Context, username string, projectNumber int, itemID int64, opts *GetProjectItemOptions) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // UpdateUserProjectItem updates an item in a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#update-project-item-for-user // //meta:operation PATCH /users/{username}/projectsV2/{project_number}/items/{item_id} func (s *ProjectsService) UpdateUserProjectItem(ctx context.Context, username string, projectNumber int, itemID int64, body UpdateProjectItemRequest) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest(ctx, "PATCH", u, body) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // DeleteUserProjectItem deletes an item from a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#delete-project-item-for-user // //meta:operation DELETE /users/{username}/projectsV2/{project_number}/items/{item_id} func (s *ProjectsService) DeleteUserProjectItem(ctx context.Context, username string, projectNumber int, itemID int64) (*Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/items/%v", username, projectNumber, itemID) req, err := s.client.NewRequest(ctx, "DELETE", u, nil) if err != nil { return nil, err } return s.client.Do(req, nil) } // CreateProjectV2DraftItemRequest specifies the parameters to create a draft item in a project. type CreateProjectV2DraftItemRequest struct { // Title is the title of the draft issue item to create. Title string `json:"title"` // Body is the body content of the draft issue item to create. Body *string `json:"body,omitempty"` } // CreateOrganizationProjectDraftItem creates a draft item in an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/drafts?apiVersion=2022-11-28#create-draft-item-for-organization-owned-project // //meta:operation POST /orgs/{org}/projectsV2/{project_number}/drafts func (s *ProjectsService) CreateOrganizationProjectDraftItem(ctx context.Context, org string, projectNumber int, body CreateProjectV2DraftItemRequest) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/drafts", org, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // CreateUserProjectDraftItem creates a draft item in a user owned project. // // Note: this endpoint identifies the user by their unique numeric ID (user_id) // in the path, not by username, mirroring the GitHub REST API. // // GitHub API docs: https://docs.github.com/rest/projects/drafts?apiVersion=2022-11-28#create-draft-item-for-user-owned-project // //meta:operation POST /user/{user_id}/projectsV2/{project_number}/drafts func (s *ProjectsService) CreateUserProjectDraftItem(ctx context.Context, userID int64, projectNumber int, body CreateProjectV2DraftItemRequest) (*ProjectV2Item, *Response, error) { u := fmt.Sprintf("user/%v/projectsV2/%v/drafts", userID, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var item *ProjectV2Item resp, err := s.client.Do(req, &item) if err != nil { return nil, resp, err } return item, resp, nil } // ProjectV2FieldSingleSelectOption represents an option to create for a single_select project field. type ProjectV2FieldSingleSelectOption struct { // Name is the display name of the option. Name string `json:"name"` // Color is the color associated with the option. // One of: BLUE, GRAY, GREEN, ORANGE, PINK, PURPLE, RED, YELLOW. Color *string `json:"color,omitempty"` // Description is an optional description of the option. Description *string `json:"description,omitempty"` } // ProjectV2FieldIterationConfiguration represents the configuration to create an iteration project field. type ProjectV2FieldIterationConfiguration struct { // StartDate is the start date of the first iteration in ISO 8601 (YYYY-MM-DD) format. StartDate *string `json:"start_date,omitempty"` // Duration is the default duration for iterations in days. Duration *int `json:"duration,omitempty"` // Iterations holds zero or more iterations for the field. Iterations []*ProjectV2FieldIterationConfigurationIteration `json:"iterations,omitempty"` } // ProjectV2FieldIterationConfigurationIteration represents a single iteration within a ProjectV2FieldIterationConfiguration. type ProjectV2FieldIterationConfigurationIteration struct { // Title is the title of the iteration. Title *string `json:"title,omitempty"` // StartDate is the start date of the iteration in ISO 8601 (YYYY-MM-DD) format. StartDate *string `json:"start_date,omitempty"` // Duration is the duration of the iteration in days. Duration *int `json:"duration,omitempty"` } // AddProjectV2FieldRequest specifies the parameters to add a field to a project. // // The GitHub API models the body as a one-of: set IssueFieldID to link a // built-in issue field, or set Name and DataType to create a new field. When // DataType is "single_select", SingleSelectOptions is required; when DataType // is "iteration", IterationConfiguration is required. type AddProjectV2FieldRequest struct { // Name is the name of the field. Name *string `json:"name,omitempty"` // DataType is the field's data type. // One of: text, number, date, single_select, iteration. DataType *string `json:"data_type,omitempty"` // SingleSelectOptions holds the options for a single_select field. (Required for single_select.) SingleSelectOptions []*ProjectV2FieldSingleSelectOption `json:"single_select_options,omitempty"` // IterationConfiguration holds the configuration for an iteration field. (Required for iteration.) IterationConfiguration *ProjectV2FieldIterationConfiguration `json:"iteration_configuration,omitempty"` // IssueFieldID links an existing built-in issue field to the project. (Organization owned projects only.) IssueFieldID *int64 `json:"issue_field_id,omitempty"` } // AddOrganizationProjectField adds a field to an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28#add-a-field-to-an-organization-owned-project // //meta:operation POST /orgs/{org}/projectsV2/{project_number}/fields func (s *ProjectsService) AddOrganizationProjectField(ctx context.Context, org string, projectNumber int, body AddProjectV2FieldRequest) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var field *ProjectV2Field resp, err := s.client.Do(req, &field) if err != nil { return nil, resp, err } return field, resp, nil } // AddUserProjectField adds a field to a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/fields?apiVersion=2022-11-28#add-field-to-user-owned-project // //meta:operation POST /users/{username}/projectsV2/{project_number}/fields func (s *ProjectsService) AddUserProjectField(ctx context.Context, username string, projectNumber int, body AddProjectV2FieldRequest) (*ProjectV2Field, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/fields", username, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var field *ProjectV2Field resp, err := s.client.Do(req, &field) if err != nil { return nil, resp, err } return field, resp, nil } // ProjectV2View represents a view in a project. type ProjectV2View struct { ID int64 `json:"id"` Number int `json:"number"` Name string `json:"name"` Layout string `json:"layout"` NodeID string `json:"node_id"` ProjectURL string `json:"project_url"` HTMLURL string `json:"html_url"` Creator User `json:"creator"` Filter *string `json:"filter,omitempty"` VisibleFields []int64 `json:"visible_fields"` SortBy []*ProjectV2ViewSortBy `json:"sort_by"` GroupBy []int64 `json:"group_by"` VerticalGroupBy []int64 `json:"vertical_group_by"` CreatedAt Timestamp `json:"created_at"` UpdatedAt Timestamp `json:"updated_at"` } // ProjectV2ViewSortBy represents a single sort criterion of a project view. // // On the wire it is encoded as a [field_id, direction] tuple rather than an // object, so it has custom JSON (un)marshaling. type ProjectV2ViewSortBy struct { // FieldID is the ID of the field to sort by. FieldID *int64 `json:"-"` // Direction is the sort direction, one of "asc" or "desc". Direction *string `json:"-"` } // UnmarshalJSON implements custom unmarshaling for the [field_id, direction] // tuple. The field_id may be encoded as either a number or a string. func (s *ProjectV2ViewSortBy) UnmarshalJSON(data []byte) error { var tuple []json.RawMessage if err := json.Unmarshal(data, &tuple); err != nil { return err } if len(tuple) != 2 { return fmt.Errorf("expected a [field_id, direction] tuple, got %v elements", len(tuple)) } var fieldID int64 if err := json.Unmarshal(tuple[0], &fieldID); err != nil { // The OpenAPI schema allows the field_id to be a string as well. var str string if err2 := json.Unmarshal(tuple[0], &str); err2 != nil { return fmt.Errorf("invalid field_id: %w", err2) } fieldID, err = strconv.ParseInt(str, 10, 64) if err != nil { return fmt.Errorf("invalid field_id %q: %w", str, err) } } var direction string if err := json.Unmarshal(tuple[1], &direction); err != nil { return fmt.Errorf("invalid direction: %w", err) } s.FieldID = &fieldID s.Direction = &direction return nil } // MarshalJSON implements custom marshaling to the [field_id, direction] tuple. func (s ProjectV2ViewSortBy) MarshalJSON() ([]byte, error) { return json.Marshal([]any{s.FieldID, s.Direction}) } // CreateProjectV2ViewRequest specifies the parameters to create a project view. type CreateProjectV2ViewRequest struct { // Name is the view's display name. Name string `json:"name"` // Layout is the view's layout. One of: table, board, roadmap. Layout string `json:"layout"` // Filter is an optional query string to filter the items shown in the view. Filter *string `json:"filter,omitempty"` // VisibleFields lists the field IDs to display. Not applicable to the roadmap layout. VisibleFields []int64 `json:"visible_fields,omitempty"` } // CreateOrganizationProjectView creates a view for an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/views?apiVersion=2022-11-28#create-a-view-for-an-organization-owned-project // //meta:operation POST /orgs/{org}/projectsV2/{project_number}/views func (s *ProjectsService) CreateOrganizationProjectView(ctx context.Context, org string, projectNumber int, body CreateProjectV2ViewRequest) (*ProjectV2View, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/views", org, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var view *ProjectV2View resp, err := s.client.Do(req, &view) if err != nil { return nil, resp, err } return view, resp, nil } // CreateUserProjectView creates a view for a user owned project. // // Note: this endpoint identifies the user by their unique numeric ID (user_id) // in the path, not by username, mirroring the GitHub REST API. // // GitHub API docs: https://docs.github.com/rest/projects/views?apiVersion=2022-11-28#create-a-view-for-a-user-owned-project // //meta:operation POST /users/{user_id}/projectsV2/{project_number}/views func (s *ProjectsService) CreateUserProjectView(ctx context.Context, userID int64, projectNumber int, body CreateProjectV2ViewRequest) (*ProjectV2View, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/views", userID, projectNumber) req, err := s.client.NewRequest(ctx, "POST", u, body) if err != nil { return nil, nil, err } var view *ProjectV2View resp, err := s.client.Do(req, &view) if err != nil { return nil, resp, err } return view, resp, nil } // ListOrganizationProjectViewItems lists the items of a view in an organization owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#list-items-for-an-organization-project-view // //meta:operation GET /orgs/{org}/projectsV2/{project_number}/views/{view_number}/items func (s *ProjectsService) ListOrganizationProjectViewItems(ctx context.Context, org string, projectNumber, viewNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("orgs/%v/projectsV2/%v/views/%v/items", org, projectNumber, viewNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var items []*ProjectV2Item resp, err := s.client.Do(req, &items) if err != nil { return nil, resp, err } return items, resp, nil } // ListUserProjectViewItems lists the items of a view in a user owned project. // // GitHub API docs: https://docs.github.com/rest/projects/items?apiVersion=2022-11-28#list-items-for-a-user-project-view // //meta:operation GET /users/{username}/projectsV2/{project_number}/views/{view_number}/items func (s *ProjectsService) ListUserProjectViewItems(ctx context.Context, username string, projectNumber, viewNumber int, opts *ListProjectItemsOptions) ([]*ProjectV2Item, *Response, error) { u := fmt.Sprintf("users/%v/projectsV2/%v/views/%v/items", username, projectNumber, viewNumber) u, err := addOptions(u, opts) if err != nil { return nil, nil, err } req, err := s.client.NewRequest(ctx, "GET", u, nil) if err != nil { return nil, nil, err } var items []*ProjectV2Item resp, err := s.client.Do(req, &items) if err != nil { return nil, resp, err } return items, resp, nil }