You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

307 lines
7.2 KiB

  1. // Copyright 2020 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package models
  5. import (
  6. "errors"
  7. "fmt"
  8. "code.gitea.io/gitea/modules/setting"
  9. "code.gitea.io/gitea/modules/timeutil"
  10. "code.gitea.io/gitea/modules/util"
  11. "xorm.io/builder"
  12. )
  13. type (
  14. // ProjectsConfig is used to identify the type of board that is being created
  15. ProjectsConfig struct {
  16. BoardType ProjectBoardType
  17. Translation string
  18. }
  19. // ProjectType is used to identify the type of project in question and ownership
  20. ProjectType uint8
  21. )
  22. const (
  23. // ProjectTypeIndividual is a type of project board that is owned by an individual
  24. ProjectTypeIndividual ProjectType = iota + 1
  25. // ProjectTypeRepository is a project that is tied to a repository
  26. ProjectTypeRepository
  27. // ProjectTypeOrganization is a project that is tied to an organisation
  28. ProjectTypeOrganization
  29. )
  30. // Project represents a project board
  31. type Project struct {
  32. ID int64 `xorm:"pk autoincr"`
  33. Title string `xorm:"INDEX NOT NULL"`
  34. Description string `xorm:"TEXT"`
  35. RepoID int64 `xorm:"INDEX"`
  36. CreatorID int64 `xorm:"NOT NULL"`
  37. IsClosed bool `xorm:"INDEX"`
  38. BoardType ProjectBoardType
  39. Type ProjectType
  40. RenderedContent string `xorm:"-"`
  41. CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
  42. UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
  43. ClosedDateUnix timeutil.TimeStamp
  44. }
  45. // GetProjectsConfig retrieves the types of configurations projects could have
  46. func GetProjectsConfig() []ProjectsConfig {
  47. return []ProjectsConfig{
  48. {ProjectBoardTypeNone, "repo.projects.type.none"},
  49. {ProjectBoardTypeBasicKanban, "repo.projects.type.basic_kanban"},
  50. {ProjectBoardTypeBugTriage, "repo.projects.type.bug_triage"},
  51. }
  52. }
  53. // IsProjectTypeValid checks if a project type is valid
  54. func IsProjectTypeValid(p ProjectType) bool {
  55. switch p {
  56. case ProjectTypeRepository:
  57. return true
  58. default:
  59. return false
  60. }
  61. }
  62. // ProjectSearchOptions are options for GetProjects
  63. type ProjectSearchOptions struct {
  64. RepoID int64
  65. Page int
  66. IsClosed util.OptionalBool
  67. SortType string
  68. Type ProjectType
  69. }
  70. // GetProjects returns a list of all projects that have been created in the repository
  71. func GetProjects(opts ProjectSearchOptions) ([]*Project, int64, error) {
  72. return getProjects(x, opts)
  73. }
  74. func getProjects(e Engine, opts ProjectSearchOptions) ([]*Project, int64, error) {
  75. projects := make([]*Project, 0, setting.UI.IssuePagingNum)
  76. var cond builder.Cond = builder.Eq{"repo_id": opts.RepoID}
  77. switch opts.IsClosed {
  78. case util.OptionalBoolTrue:
  79. cond = cond.And(builder.Eq{"is_closed": true})
  80. case util.OptionalBoolFalse:
  81. cond = cond.And(builder.Eq{"is_closed": false})
  82. }
  83. if opts.Type > 0 {
  84. cond = cond.And(builder.Eq{"type": opts.Type})
  85. }
  86. count, err := e.Where(cond).Count(new(Project))
  87. if err != nil {
  88. return nil, 0, fmt.Errorf("Count: %v", err)
  89. }
  90. e = e.Where(cond)
  91. if opts.Page > 0 {
  92. e = e.Limit(setting.UI.IssuePagingNum, (opts.Page-1)*setting.UI.IssuePagingNum)
  93. }
  94. switch opts.SortType {
  95. case "oldest":
  96. e.Desc("created_unix")
  97. case "recentupdate":
  98. e.Desc("updated_unix")
  99. case "leastupdate":
  100. e.Asc("updated_unix")
  101. default:
  102. e.Asc("created_unix")
  103. }
  104. return projects, count, e.Find(&projects)
  105. }
  106. // NewProject creates a new Project
  107. func NewProject(p *Project) error {
  108. if !IsProjectBoardTypeValid(p.BoardType) {
  109. p.BoardType = ProjectBoardTypeNone
  110. }
  111. if !IsProjectTypeValid(p.Type) {
  112. return errors.New("project type is not valid")
  113. }
  114. sess := x.NewSession()
  115. defer sess.Close()
  116. if err := sess.Begin(); err != nil {
  117. return err
  118. }
  119. if _, err := sess.Insert(p); err != nil {
  120. return err
  121. }
  122. if _, err := sess.Exec("UPDATE `repository` SET num_projects = num_projects + 1 WHERE id = ?", p.RepoID); err != nil {
  123. return err
  124. }
  125. if err := createBoardsForProjectsType(sess, p); err != nil {
  126. return err
  127. }
  128. return sess.Commit()
  129. }
  130. // GetProjectByID returns the projects in a repository
  131. func GetProjectByID(id int64) (*Project, error) {
  132. return getProjectByID(x, id)
  133. }
  134. func getProjectByID(e Engine, id int64) (*Project, error) {
  135. p := new(Project)
  136. has, err := e.ID(id).Get(p)
  137. if err != nil {
  138. return nil, err
  139. } else if !has {
  140. return nil, ErrProjectNotExist{ID: id}
  141. }
  142. return p, nil
  143. }
  144. // UpdateProject updates project properties
  145. func UpdateProject(p *Project) error {
  146. return updateProject(x, p)
  147. }
  148. func updateProject(e Engine, p *Project) error {
  149. _, err := e.ID(p.ID).Cols(
  150. "title",
  151. "description",
  152. ).Update(p)
  153. return err
  154. }
  155. func updateRepositoryProjectCount(e Engine, repoID int64) error {
  156. if _, err := e.Exec(builder.Update(
  157. builder.Eq{
  158. "`num_projects`": builder.Select("count(*)").From("`project`").
  159. Where(builder.Eq{"`project`.`repo_id`": repoID}.
  160. And(builder.Eq{"`project`.`type`": ProjectTypeRepository})),
  161. }).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
  162. return err
  163. }
  164. if _, err := e.Exec(builder.Update(
  165. builder.Eq{
  166. "`num_closed_projects`": builder.Select("count(*)").From("`project`").
  167. Where(builder.Eq{"`project`.`repo_id`": repoID}.
  168. And(builder.Eq{"`project`.`type`": ProjectTypeRepository}).
  169. And(builder.Eq{"`project`.`is_closed`": true})),
  170. }).From("`repository`").Where(builder.Eq{"id": repoID})); err != nil {
  171. return err
  172. }
  173. return nil
  174. }
  175. // ChangeProjectStatusByRepoIDAndID toggles a project between opened and closed
  176. func ChangeProjectStatusByRepoIDAndID(repoID, projectID int64, isClosed bool) error {
  177. sess := x.NewSession()
  178. defer sess.Close()
  179. if err := sess.Begin(); err != nil {
  180. return err
  181. }
  182. p := new(Project)
  183. has, err := sess.ID(projectID).Where("repo_id = ?", repoID).Get(p)
  184. if err != nil {
  185. return err
  186. } else if !has {
  187. return ErrProjectNotExist{ID: projectID, RepoID: repoID}
  188. }
  189. if err := changeProjectStatus(sess, p, isClosed); err != nil {
  190. return err
  191. }
  192. return sess.Commit()
  193. }
  194. // ChangeProjectStatus toggle a project between opened and closed
  195. func ChangeProjectStatus(p *Project, isClosed bool) error {
  196. sess := x.NewSession()
  197. defer sess.Close()
  198. if err := sess.Begin(); err != nil {
  199. return err
  200. }
  201. if err := changeProjectStatus(sess, p, isClosed); err != nil {
  202. return err
  203. }
  204. return sess.Commit()
  205. }
  206. func changeProjectStatus(e Engine, p *Project, isClosed bool) error {
  207. p.IsClosed = isClosed
  208. p.ClosedDateUnix = timeutil.TimeStampNow()
  209. count, err := e.ID(p.ID).Where("repo_id = ? AND is_closed = ?", p.RepoID, !isClosed).Cols("is_closed", "closed_date_unix").Update(p)
  210. if err != nil {
  211. return err
  212. }
  213. if count < 1 {
  214. return nil
  215. }
  216. return updateRepositoryProjectCount(e, p.RepoID)
  217. }
  218. // DeleteProjectByID deletes a project from a repository.
  219. func DeleteProjectByID(id int64) error {
  220. sess := x.NewSession()
  221. defer sess.Close()
  222. if err := sess.Begin(); err != nil {
  223. return err
  224. }
  225. if err := deleteProjectByID(sess, id); err != nil {
  226. return err
  227. }
  228. return sess.Commit()
  229. }
  230. func deleteProjectByID(e Engine, id int64) error {
  231. p, err := getProjectByID(e, id)
  232. if err != nil {
  233. if IsErrProjectNotExist(err) {
  234. return nil
  235. }
  236. return err
  237. }
  238. if err := deleteProjectIssuesByProjectID(e, id); err != nil {
  239. return err
  240. }
  241. if err := deleteProjectBoardByProjectID(e, id); err != nil {
  242. return err
  243. }
  244. if _, err = e.ID(p.ID).Delete(new(Project)); err != nil {
  245. return err
  246. }
  247. return updateRepositoryProjectCount(e, p.RepoID)
  248. }