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.

team.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package org
  6. import (
  7. "strings"
  8. "code.gitea.io/gitea/models"
  9. "code.gitea.io/gitea/modules/context"
  10. "code.gitea.io/gitea/modules/convert"
  11. "code.gitea.io/gitea/modules/log"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/routers/api/v1/user"
  14. )
  15. // ListTeams list all the teams of an organization
  16. func ListTeams(ctx *context.APIContext) {
  17. // swagger:operation GET /orgs/{org}/teams organization orgListTeams
  18. // ---
  19. // summary: List an organization's teams
  20. // produces:
  21. // - application/json
  22. // parameters:
  23. // - name: org
  24. // in: path
  25. // description: name of the organization
  26. // type: string
  27. // required: true
  28. // responses:
  29. // "200":
  30. // "$ref": "#/responses/TeamList"
  31. org := ctx.Org.Organization
  32. if err := org.GetTeams(); err != nil {
  33. ctx.Error(500, "GetTeams", err)
  34. return
  35. }
  36. apiTeams := make([]*api.Team, len(org.Teams))
  37. for i := range org.Teams {
  38. if err := org.Teams[i].GetUnits(); err != nil {
  39. ctx.Error(500, "GetUnits", err)
  40. return
  41. }
  42. apiTeams[i] = convert.ToTeam(org.Teams[i])
  43. }
  44. ctx.JSON(200, apiTeams)
  45. }
  46. // ListUserTeams list all the teams a user belongs to
  47. func ListUserTeams(ctx *context.APIContext) {
  48. // swagger:operation GET /user/teams user userListTeams
  49. // ---
  50. // summary: List all the teams a user belongs to
  51. // produces:
  52. // - application/json
  53. // responses:
  54. // "200":
  55. // "$ref": "#/responses/TeamList"
  56. teams, err := models.GetUserTeams(ctx.User.ID)
  57. if err != nil {
  58. ctx.Error(500, "GetUserTeams", err)
  59. return
  60. }
  61. cache := make(map[int64]*api.Organization)
  62. apiTeams := make([]*api.Team, len(teams))
  63. for i := range teams {
  64. apiOrg, ok := cache[teams[i].OrgID]
  65. if !ok {
  66. org, err := models.GetUserByID(teams[i].OrgID)
  67. if err != nil {
  68. ctx.Error(500, "GetUserByID", err)
  69. return
  70. }
  71. apiOrg = convert.ToOrganization(org)
  72. cache[teams[i].OrgID] = apiOrg
  73. }
  74. apiTeams[i] = convert.ToTeam(teams[i])
  75. apiTeams[i].Organization = apiOrg
  76. }
  77. ctx.JSON(200, apiTeams)
  78. }
  79. // GetTeam api for get a team
  80. func GetTeam(ctx *context.APIContext) {
  81. // swagger:operation GET /teams/{id} organization orgGetTeam
  82. // ---
  83. // summary: Get a team
  84. // produces:
  85. // - application/json
  86. // parameters:
  87. // - name: id
  88. // in: path
  89. // description: id of the team to get
  90. // type: integer
  91. // format: int64
  92. // required: true
  93. // responses:
  94. // "200":
  95. // "$ref": "#/responses/Team"
  96. ctx.JSON(200, convert.ToTeam(ctx.Org.Team))
  97. }
  98. // CreateTeam api for create a team
  99. func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
  100. // swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
  101. // ---
  102. // summary: Create a team
  103. // consumes:
  104. // - application/json
  105. // produces:
  106. // - application/json
  107. // parameters:
  108. // - name: org
  109. // in: path
  110. // description: name of the organization
  111. // type: string
  112. // required: true
  113. // - name: body
  114. // in: body
  115. // schema:
  116. // "$ref": "#/definitions/CreateTeamOption"
  117. // responses:
  118. // "201":
  119. // "$ref": "#/responses/Team"
  120. team := &models.Team{
  121. OrgID: ctx.Org.Organization.ID,
  122. Name: form.Name,
  123. Description: form.Description,
  124. IncludesAllRepositories: form.IncludesAllRepositories,
  125. Authorize: models.ParseAccessMode(form.Permission),
  126. }
  127. unitTypes := models.FindUnitTypes(form.Units...)
  128. if team.Authorize < models.AccessModeOwner {
  129. var units = make([]*models.TeamUnit, 0, len(form.Units))
  130. for _, tp := range unitTypes {
  131. units = append(units, &models.TeamUnit{
  132. OrgID: ctx.Org.Organization.ID,
  133. Type: tp,
  134. })
  135. }
  136. team.Units = units
  137. }
  138. if err := models.NewTeam(team); err != nil {
  139. if models.IsErrTeamAlreadyExist(err) {
  140. ctx.Error(422, "", err)
  141. } else {
  142. ctx.Error(500, "NewTeam", err)
  143. }
  144. return
  145. }
  146. ctx.JSON(201, convert.ToTeam(team))
  147. }
  148. // EditTeam api for edit a team
  149. func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
  150. // swagger:operation PATCH /teams/{id} organization orgEditTeam
  151. // ---
  152. // summary: Edit a team
  153. // consumes:
  154. // - application/json
  155. // produces:
  156. // - application/json
  157. // parameters:
  158. // - name: id
  159. // in: path
  160. // description: id of the team to edit
  161. // type: integer
  162. // required: true
  163. // - name: body
  164. // in: body
  165. // schema:
  166. // "$ref": "#/definitions/EditTeamOption"
  167. // responses:
  168. // "200":
  169. // "$ref": "#/responses/Team"
  170. team := ctx.Org.Team
  171. team.Description = form.Description
  172. unitTypes := models.FindUnitTypes(form.Units...)
  173. isAuthChanged := false
  174. isIncludeAllChanged := false
  175. if !team.IsOwnerTeam() {
  176. // Validate permission level.
  177. auth := models.ParseAccessMode(form.Permission)
  178. team.Name = form.Name
  179. if team.Authorize != auth {
  180. isAuthChanged = true
  181. team.Authorize = auth
  182. }
  183. if team.IncludesAllRepositories != form.IncludesAllRepositories {
  184. isIncludeAllChanged = true
  185. team.IncludesAllRepositories = form.IncludesAllRepositories
  186. }
  187. }
  188. if team.Authorize < models.AccessModeOwner {
  189. var units = make([]*models.TeamUnit, 0, len(form.Units))
  190. for _, tp := range unitTypes {
  191. units = append(units, &models.TeamUnit{
  192. OrgID: ctx.Org.Team.OrgID,
  193. Type: tp,
  194. })
  195. }
  196. team.Units = units
  197. }
  198. if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {
  199. ctx.Error(500, "EditTeam", err)
  200. return
  201. }
  202. ctx.JSON(200, convert.ToTeam(team))
  203. }
  204. // DeleteTeam api for delete a team
  205. func DeleteTeam(ctx *context.APIContext) {
  206. // swagger:operation DELETE /teams/{id} organization orgDeleteTeam
  207. // ---
  208. // summary: Delete a team
  209. // parameters:
  210. // - name: id
  211. // in: path
  212. // description: id of the team to delete
  213. // type: integer
  214. // format: int64
  215. // required: true
  216. // responses:
  217. // "204":
  218. // description: team deleted
  219. if err := models.DeleteTeam(ctx.Org.Team); err != nil {
  220. ctx.Error(500, "DeleteTeam", err)
  221. return
  222. }
  223. ctx.Status(204)
  224. }
  225. // GetTeamMembers api for get a team's members
  226. func GetTeamMembers(ctx *context.APIContext) {
  227. // swagger:operation GET /teams/{id}/members organization orgListTeamMembers
  228. // ---
  229. // summary: List a team's members
  230. // produces:
  231. // - application/json
  232. // parameters:
  233. // - name: id
  234. // in: path
  235. // description: id of the team
  236. // type: integer
  237. // format: int64
  238. // required: true
  239. // responses:
  240. // "200":
  241. // "$ref": "#/responses/UserList"
  242. isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID)
  243. if err != nil {
  244. ctx.Error(500, "IsOrganizationMember", err)
  245. return
  246. } else if !isMember {
  247. ctx.NotFound()
  248. return
  249. }
  250. team := ctx.Org.Team
  251. if err := team.GetMembers(); err != nil {
  252. ctx.Error(500, "GetTeamMembers", err)
  253. return
  254. }
  255. members := make([]*api.User, len(team.Members))
  256. for i, member := range team.Members {
  257. members[i] = convert.ToUser(member, ctx.IsSigned, ctx.User.IsAdmin)
  258. }
  259. ctx.JSON(200, members)
  260. }
  261. // GetTeamMember api for get a particular member of team
  262. func GetTeamMember(ctx *context.APIContext) {
  263. // swagger:operation GET /teams/{id}/members/{username} organization orgListTeamMember
  264. // ---
  265. // summary: List a particular member of team
  266. // produces:
  267. // - application/json
  268. // parameters:
  269. // - name: id
  270. // in: path
  271. // description: id of the team
  272. // type: integer
  273. // format: int64
  274. // required: true
  275. // - name: username
  276. // in: path
  277. // description: username of the member to list
  278. // type: string
  279. // required: true
  280. // responses:
  281. // "200":
  282. // "$ref": "#/responses/User"
  283. u := user.GetUserByParams(ctx)
  284. if ctx.Written() {
  285. return
  286. }
  287. teamID := ctx.ParamsInt64("teamid")
  288. isTeamMember, err := models.IsUserInTeams(u.ID, []int64{teamID})
  289. if err != nil {
  290. ctx.Error(500, "IsUserInTeams", err)
  291. return
  292. } else if !isTeamMember {
  293. ctx.NotFound()
  294. return
  295. }
  296. ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
  297. }
  298. // AddTeamMember api for add a member to a team
  299. func AddTeamMember(ctx *context.APIContext) {
  300. // swagger:operation PUT /teams/{id}/members/{username} organization orgAddTeamMember
  301. // ---
  302. // summary: Add a team member
  303. // produces:
  304. // - application/json
  305. // parameters:
  306. // - name: id
  307. // in: path
  308. // description: id of the team
  309. // type: integer
  310. // format: int64
  311. // required: true
  312. // - name: username
  313. // in: path
  314. // description: username of the user to add
  315. // type: string
  316. // required: true
  317. // responses:
  318. // "204":
  319. // "$ref": "#/responses/empty"
  320. u := user.GetUserByParams(ctx)
  321. if ctx.Written() {
  322. return
  323. }
  324. if err := ctx.Org.Team.AddMember(u.ID); err != nil {
  325. ctx.Error(500, "AddMember", err)
  326. return
  327. }
  328. ctx.Status(204)
  329. }
  330. // RemoveTeamMember api for remove one member from a team
  331. func RemoveTeamMember(ctx *context.APIContext) {
  332. // swagger:operation DELETE /teams/{id}/members/{username} organization orgRemoveTeamMember
  333. // ---
  334. // summary: Remove a team member
  335. // produces:
  336. // - application/json
  337. // parameters:
  338. // - name: id
  339. // in: path
  340. // description: id of the team
  341. // type: integer
  342. // format: int64
  343. // required: true
  344. // - name: username
  345. // in: path
  346. // description: username of the user to remove
  347. // type: string
  348. // required: true
  349. // responses:
  350. // "204":
  351. // "$ref": "#/responses/empty"
  352. u := user.GetUserByParams(ctx)
  353. if ctx.Written() {
  354. return
  355. }
  356. if err := ctx.Org.Team.RemoveMember(u.ID); err != nil {
  357. ctx.Error(500, "RemoveMember", err)
  358. return
  359. }
  360. ctx.Status(204)
  361. }
  362. // GetTeamRepos api for get a team's repos
  363. func GetTeamRepos(ctx *context.APIContext) {
  364. // swagger:operation GET /teams/{id}/repos organization orgListTeamRepos
  365. // ---
  366. // summary: List a team's repos
  367. // produces:
  368. // - application/json
  369. // parameters:
  370. // - name: id
  371. // in: path
  372. // description: id of the team
  373. // type: integer
  374. // format: int64
  375. // required: true
  376. // responses:
  377. // "200":
  378. // "$ref": "#/responses/RepositoryList"
  379. team := ctx.Org.Team
  380. if err := team.GetRepositories(); err != nil {
  381. ctx.Error(500, "GetTeamRepos", err)
  382. }
  383. repos := make([]*api.Repository, len(team.Repos))
  384. for i, repo := range team.Repos {
  385. access, err := models.AccessLevel(ctx.User, repo)
  386. if err != nil {
  387. ctx.Error(500, "GetTeamRepos", err)
  388. return
  389. }
  390. repos[i] = repo.APIFormat(access)
  391. }
  392. ctx.JSON(200, repos)
  393. }
  394. // getRepositoryByParams get repository by a team's organization ID and repo name
  395. func getRepositoryByParams(ctx *context.APIContext) *models.Repository {
  396. repo, err := models.GetRepositoryByName(ctx.Org.Team.OrgID, ctx.Params(":reponame"))
  397. if err != nil {
  398. if models.IsErrRepoNotExist(err) {
  399. ctx.NotFound()
  400. } else {
  401. ctx.Error(500, "GetRepositoryByName", err)
  402. }
  403. return nil
  404. }
  405. return repo
  406. }
  407. // AddTeamRepository api for adding a repository to a team
  408. func AddTeamRepository(ctx *context.APIContext) {
  409. // swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
  410. // ---
  411. // summary: Add a repository to a team
  412. // produces:
  413. // - application/json
  414. // parameters:
  415. // - name: id
  416. // in: path
  417. // description: id of the team
  418. // type: integer
  419. // format: int64
  420. // required: true
  421. // - name: org
  422. // in: path
  423. // description: organization that owns the repo to add
  424. // type: string
  425. // required: true
  426. // - name: repo
  427. // in: path
  428. // description: name of the repo to add
  429. // type: string
  430. // required: true
  431. // responses:
  432. // "204":
  433. // "$ref": "#/responses/empty"
  434. repo := getRepositoryByParams(ctx)
  435. if ctx.Written() {
  436. return
  437. }
  438. if access, err := models.AccessLevel(ctx.User, repo); err != nil {
  439. ctx.Error(500, "AccessLevel", err)
  440. return
  441. } else if access < models.AccessModeAdmin {
  442. ctx.Error(403, "", "Must have admin-level access to the repository")
  443. return
  444. }
  445. if err := ctx.Org.Team.AddRepository(repo); err != nil {
  446. ctx.Error(500, "AddRepository", err)
  447. return
  448. }
  449. ctx.Status(204)
  450. }
  451. // RemoveTeamRepository api for removing a repository from a team
  452. func RemoveTeamRepository(ctx *context.APIContext) {
  453. // swagger:operation DELETE /teams/{id}/repos/{org}/{repo} organization orgRemoveTeamRepository
  454. // ---
  455. // summary: Remove a repository from a team
  456. // description: This does not delete the repository, it only removes the
  457. // repository from the team.
  458. // produces:
  459. // - application/json
  460. // parameters:
  461. // - name: id
  462. // in: path
  463. // description: id of the team
  464. // type: integer
  465. // format: int64
  466. // required: true
  467. // - name: org
  468. // in: path
  469. // description: organization that owns the repo to remove
  470. // type: string
  471. // required: true
  472. // - name: repo
  473. // in: path
  474. // description: name of the repo to remove
  475. // type: string
  476. // required: true
  477. // responses:
  478. // "204":
  479. // "$ref": "#/responses/empty"
  480. repo := getRepositoryByParams(ctx)
  481. if ctx.Written() {
  482. return
  483. }
  484. if access, err := models.AccessLevel(ctx.User, repo); err != nil {
  485. ctx.Error(500, "AccessLevel", err)
  486. return
  487. } else if access < models.AccessModeAdmin {
  488. ctx.Error(403, "", "Must have admin-level access to the repository")
  489. return
  490. }
  491. if err := ctx.Org.Team.RemoveRepository(repo.ID); err != nil {
  492. ctx.Error(500, "RemoveRepository", err)
  493. return
  494. }
  495. ctx.Status(204)
  496. }
  497. // SearchTeam api for searching teams
  498. func SearchTeam(ctx *context.APIContext) {
  499. // swagger:operation GET /orgs/{org}/teams/search organization teamSearch
  500. // ---
  501. // summary: Search for teams within an organization
  502. // produces:
  503. // - application/json
  504. // parameters:
  505. // - name: org
  506. // in: path
  507. // description: name of the organization
  508. // type: string
  509. // required: true
  510. // - name: q
  511. // in: query
  512. // description: keywords to search
  513. // type: string
  514. // - name: include_desc
  515. // in: query
  516. // description: include search within team description (defaults to true)
  517. // type: boolean
  518. // - name: limit
  519. // in: query
  520. // description: limit size of results
  521. // type: integer
  522. // - name: page
  523. // in: query
  524. // description: page number of results to return (1-based)
  525. // type: integer
  526. // responses:
  527. // "200":
  528. // description: "SearchResults of a successful search"
  529. // schema:
  530. // type: object
  531. // properties:
  532. // ok:
  533. // type: boolean
  534. // data:
  535. // type: array
  536. // items:
  537. // "$ref": "#/definitions/Team"
  538. opts := &models.SearchTeamOptions{
  539. UserID: ctx.User.ID,
  540. Keyword: strings.TrimSpace(ctx.Query("q")),
  541. OrgID: ctx.Org.Organization.ID,
  542. IncludeDesc: (ctx.Query("include_desc") == "" || ctx.QueryBool("include_desc")),
  543. PageSize: ctx.QueryInt("limit"),
  544. Page: ctx.QueryInt("page"),
  545. }
  546. teams, _, err := models.SearchTeam(opts)
  547. if err != nil {
  548. log.Error("SearchTeam failed: %v", err)
  549. ctx.JSON(500, map[string]interface{}{
  550. "ok": false,
  551. "error": "SearchTeam internal failure",
  552. })
  553. return
  554. }
  555. apiTeams := make([]*api.Team, len(teams))
  556. for i := range teams {
  557. if err := teams[i].GetUnits(); err != nil {
  558. log.Error("Team GetUnits failed: %v", err)
  559. ctx.JSON(500, map[string]interface{}{
  560. "ok": false,
  561. "error": "SearchTeam failed to get units",
  562. })
  563. return
  564. }
  565. apiTeams[i] = convert.ToTeam(teams[i])
  566. }
  567. ctx.JSON(200, map[string]interface{}{
  568. "ok": true,
  569. "data": apiTeams,
  570. })
  571. }