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

team.go 16 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  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. CanCreateOrgRepo: form.CanCreateOrgRepo,
  126. Authorize: models.ParseAccessMode(form.Permission),
  127. }
  128. unitTypes := models.FindUnitTypes(form.Units...)
  129. if team.Authorize < models.AccessModeOwner {
  130. var units = make([]*models.TeamUnit, 0, len(form.Units))
  131. for _, tp := range unitTypes {
  132. units = append(units, &models.TeamUnit{
  133. OrgID: ctx.Org.Organization.ID,
  134. Type: tp,
  135. })
  136. }
  137. team.Units = units
  138. }
  139. if err := models.NewTeam(team); err != nil {
  140. if models.IsErrTeamAlreadyExist(err) {
  141. ctx.Error(422, "", err)
  142. } else {
  143. ctx.Error(500, "NewTeam", err)
  144. }
  145. return
  146. }
  147. ctx.JSON(201, convert.ToTeam(team))
  148. }
  149. // EditTeam api for edit a team
  150. func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
  151. // swagger:operation PATCH /teams/{id} organization orgEditTeam
  152. // ---
  153. // summary: Edit a team
  154. // consumes:
  155. // - application/json
  156. // produces:
  157. // - application/json
  158. // parameters:
  159. // - name: id
  160. // in: path
  161. // description: id of the team to edit
  162. // type: integer
  163. // required: true
  164. // - name: body
  165. // in: body
  166. // schema:
  167. // "$ref": "#/definitions/EditTeamOption"
  168. // responses:
  169. // "200":
  170. // "$ref": "#/responses/Team"
  171. team := ctx.Org.Team
  172. team.Description = form.Description
  173. unitTypes := models.FindUnitTypes(form.Units...)
  174. team.CanCreateOrgRepo = form.CanCreateOrgRepo
  175. isAuthChanged := false
  176. isIncludeAllChanged := false
  177. if !team.IsOwnerTeam() {
  178. // Validate permission level.
  179. auth := models.ParseAccessMode(form.Permission)
  180. team.Name = form.Name
  181. if team.Authorize != auth {
  182. isAuthChanged = true
  183. team.Authorize = auth
  184. }
  185. if team.IncludesAllRepositories != form.IncludesAllRepositories {
  186. isIncludeAllChanged = true
  187. team.IncludesAllRepositories = form.IncludesAllRepositories
  188. }
  189. }
  190. if team.Authorize < models.AccessModeOwner {
  191. var units = make([]*models.TeamUnit, 0, len(form.Units))
  192. for _, tp := range unitTypes {
  193. units = append(units, &models.TeamUnit{
  194. OrgID: ctx.Org.Team.OrgID,
  195. Type: tp,
  196. })
  197. }
  198. team.Units = units
  199. }
  200. if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {
  201. ctx.Error(500, "EditTeam", err)
  202. return
  203. }
  204. ctx.JSON(200, convert.ToTeam(team))
  205. }
  206. // DeleteTeam api for delete a team
  207. func DeleteTeam(ctx *context.APIContext) {
  208. // swagger:operation DELETE /teams/{id} organization orgDeleteTeam
  209. // ---
  210. // summary: Delete a team
  211. // parameters:
  212. // - name: id
  213. // in: path
  214. // description: id of the team to delete
  215. // type: integer
  216. // format: int64
  217. // required: true
  218. // responses:
  219. // "204":
  220. // description: team deleted
  221. if err := models.DeleteTeam(ctx.Org.Team); err != nil {
  222. ctx.Error(500, "DeleteTeam", err)
  223. return
  224. }
  225. ctx.Status(204)
  226. }
  227. // GetTeamMembers api for get a team's members
  228. func GetTeamMembers(ctx *context.APIContext) {
  229. // swagger:operation GET /teams/{id}/members organization orgListTeamMembers
  230. // ---
  231. // summary: List a team's members
  232. // produces:
  233. // - application/json
  234. // parameters:
  235. // - name: id
  236. // in: path
  237. // description: id of the team
  238. // type: integer
  239. // format: int64
  240. // required: true
  241. // responses:
  242. // "200":
  243. // "$ref": "#/responses/UserList"
  244. isMember, err := models.IsOrganizationMember(ctx.Org.Team.OrgID, ctx.User.ID)
  245. if err != nil {
  246. ctx.Error(500, "IsOrganizationMember", err)
  247. return
  248. } else if !isMember {
  249. ctx.NotFound()
  250. return
  251. }
  252. team := ctx.Org.Team
  253. if err := team.GetMembers(); err != nil {
  254. ctx.Error(500, "GetTeamMembers", err)
  255. return
  256. }
  257. members := make([]*api.User, len(team.Members))
  258. for i, member := range team.Members {
  259. members[i] = convert.ToUser(member, ctx.IsSigned, ctx.User.IsAdmin)
  260. }
  261. ctx.JSON(200, members)
  262. }
  263. // GetTeamMember api for get a particular member of team
  264. func GetTeamMember(ctx *context.APIContext) {
  265. // swagger:operation GET /teams/{id}/members/{username} organization orgListTeamMember
  266. // ---
  267. // summary: List a particular member of team
  268. // produces:
  269. // - application/json
  270. // parameters:
  271. // - name: id
  272. // in: path
  273. // description: id of the team
  274. // type: integer
  275. // format: int64
  276. // required: true
  277. // - name: username
  278. // in: path
  279. // description: username of the member to list
  280. // type: string
  281. // required: true
  282. // responses:
  283. // "200":
  284. // "$ref": "#/responses/User"
  285. u := user.GetUserByParams(ctx)
  286. if ctx.Written() {
  287. return
  288. }
  289. teamID := ctx.ParamsInt64("teamid")
  290. isTeamMember, err := models.IsUserInTeams(u.ID, []int64{teamID})
  291. if err != nil {
  292. ctx.Error(500, "IsUserInTeams", err)
  293. return
  294. } else if !isTeamMember {
  295. ctx.NotFound()
  296. return
  297. }
  298. ctx.JSON(200, convert.ToUser(u, ctx.IsSigned, ctx.User.IsAdmin))
  299. }
  300. // AddTeamMember api for add a member to a team
  301. func AddTeamMember(ctx *context.APIContext) {
  302. // swagger:operation PUT /teams/{id}/members/{username} organization orgAddTeamMember
  303. // ---
  304. // summary: Add a team member
  305. // produces:
  306. // - application/json
  307. // parameters:
  308. // - name: id
  309. // in: path
  310. // description: id of the team
  311. // type: integer
  312. // format: int64
  313. // required: true
  314. // - name: username
  315. // in: path
  316. // description: username of the user to add
  317. // type: string
  318. // required: true
  319. // responses:
  320. // "204":
  321. // "$ref": "#/responses/empty"
  322. u := user.GetUserByParams(ctx)
  323. if ctx.Written() {
  324. return
  325. }
  326. if err := ctx.Org.Team.AddMember(u.ID); err != nil {
  327. ctx.Error(500, "AddMember", err)
  328. return
  329. }
  330. ctx.Status(204)
  331. }
  332. // RemoveTeamMember api for remove one member from a team
  333. func RemoveTeamMember(ctx *context.APIContext) {
  334. // swagger:operation DELETE /teams/{id}/members/{username} organization orgRemoveTeamMember
  335. // ---
  336. // summary: Remove a team member
  337. // produces:
  338. // - application/json
  339. // parameters:
  340. // - name: id
  341. // in: path
  342. // description: id of the team
  343. // type: integer
  344. // format: int64
  345. // required: true
  346. // - name: username
  347. // in: path
  348. // description: username of the user to remove
  349. // type: string
  350. // required: true
  351. // responses:
  352. // "204":
  353. // "$ref": "#/responses/empty"
  354. u := user.GetUserByParams(ctx)
  355. if ctx.Written() {
  356. return
  357. }
  358. if err := ctx.Org.Team.RemoveMember(u.ID); err != nil {
  359. ctx.Error(500, "RemoveMember", err)
  360. return
  361. }
  362. ctx.Status(204)
  363. }
  364. // GetTeamRepos api for get a team's repos
  365. func GetTeamRepos(ctx *context.APIContext) {
  366. // swagger:operation GET /teams/{id}/repos organization orgListTeamRepos
  367. // ---
  368. // summary: List a team's repos
  369. // produces:
  370. // - application/json
  371. // parameters:
  372. // - name: id
  373. // in: path
  374. // description: id of the team
  375. // type: integer
  376. // format: int64
  377. // required: true
  378. // responses:
  379. // "200":
  380. // "$ref": "#/responses/RepositoryList"
  381. team := ctx.Org.Team
  382. if err := team.GetRepositories(); err != nil {
  383. ctx.Error(500, "GetTeamRepos", err)
  384. }
  385. repos := make([]*api.Repository, len(team.Repos))
  386. for i, repo := range team.Repos {
  387. access, err := models.AccessLevel(ctx.User, repo)
  388. if err != nil {
  389. ctx.Error(500, "GetTeamRepos", err)
  390. return
  391. }
  392. repos[i] = repo.APIFormat(access)
  393. }
  394. ctx.JSON(200, repos)
  395. }
  396. // getRepositoryByParams get repository by a team's organization ID and repo name
  397. func getRepositoryByParams(ctx *context.APIContext) *models.Repository {
  398. repo, err := models.GetRepositoryByName(ctx.Org.Team.OrgID, ctx.Params(":reponame"))
  399. if err != nil {
  400. if models.IsErrRepoNotExist(err) {
  401. ctx.NotFound()
  402. } else {
  403. ctx.Error(500, "GetRepositoryByName", err)
  404. }
  405. return nil
  406. }
  407. return repo
  408. }
  409. // AddTeamRepository api for adding a repository to a team
  410. func AddTeamRepository(ctx *context.APIContext) {
  411. // swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
  412. // ---
  413. // summary: Add a repository to a team
  414. // produces:
  415. // - application/json
  416. // parameters:
  417. // - name: id
  418. // in: path
  419. // description: id of the team
  420. // type: integer
  421. // format: int64
  422. // required: true
  423. // - name: org
  424. // in: path
  425. // description: organization that owns the repo to add
  426. // type: string
  427. // required: true
  428. // - name: repo
  429. // in: path
  430. // description: name of the repo to add
  431. // type: string
  432. // required: true
  433. // responses:
  434. // "204":
  435. // "$ref": "#/responses/empty"
  436. repo := getRepositoryByParams(ctx)
  437. if ctx.Written() {
  438. return
  439. }
  440. if access, err := models.AccessLevel(ctx.User, repo); err != nil {
  441. ctx.Error(500, "AccessLevel", err)
  442. return
  443. } else if access < models.AccessModeAdmin {
  444. ctx.Error(403, "", "Must have admin-level access to the repository")
  445. return
  446. }
  447. if err := ctx.Org.Team.AddRepository(repo); err != nil {
  448. ctx.Error(500, "AddRepository", err)
  449. return
  450. }
  451. ctx.Status(204)
  452. }
  453. // RemoveTeamRepository api for removing a repository from a team
  454. func RemoveTeamRepository(ctx *context.APIContext) {
  455. // swagger:operation DELETE /teams/{id}/repos/{org}/{repo} organization orgRemoveTeamRepository
  456. // ---
  457. // summary: Remove a repository from a team
  458. // description: This does not delete the repository, it only removes the
  459. // repository from the team.
  460. // produces:
  461. // - application/json
  462. // parameters:
  463. // - name: id
  464. // in: path
  465. // description: id of the team
  466. // type: integer
  467. // format: int64
  468. // required: true
  469. // - name: org
  470. // in: path
  471. // description: organization that owns the repo to remove
  472. // type: string
  473. // required: true
  474. // - name: repo
  475. // in: path
  476. // description: name of the repo to remove
  477. // type: string
  478. // required: true
  479. // responses:
  480. // "204":
  481. // "$ref": "#/responses/empty"
  482. repo := getRepositoryByParams(ctx)
  483. if ctx.Written() {
  484. return
  485. }
  486. if access, err := models.AccessLevel(ctx.User, repo); err != nil {
  487. ctx.Error(500, "AccessLevel", err)
  488. return
  489. } else if access < models.AccessModeAdmin {
  490. ctx.Error(403, "", "Must have admin-level access to the repository")
  491. return
  492. }
  493. if err := ctx.Org.Team.RemoveRepository(repo.ID); err != nil {
  494. ctx.Error(500, "RemoveRepository", err)
  495. return
  496. }
  497. ctx.Status(204)
  498. }
  499. // SearchTeam api for searching teams
  500. func SearchTeam(ctx *context.APIContext) {
  501. // swagger:operation GET /orgs/{org}/teams/search organization teamSearch
  502. // ---
  503. // summary: Search for teams within an organization
  504. // produces:
  505. // - application/json
  506. // parameters:
  507. // - name: org
  508. // in: path
  509. // description: name of the organization
  510. // type: string
  511. // required: true
  512. // - name: q
  513. // in: query
  514. // description: keywords to search
  515. // type: string
  516. // - name: include_desc
  517. // in: query
  518. // description: include search within team description (defaults to true)
  519. // type: boolean
  520. // - name: limit
  521. // in: query
  522. // description: limit size of results
  523. // type: integer
  524. // - name: page
  525. // in: query
  526. // description: page number of results to return (1-based)
  527. // type: integer
  528. // responses:
  529. // "200":
  530. // description: "SearchResults of a successful search"
  531. // schema:
  532. // type: object
  533. // properties:
  534. // ok:
  535. // type: boolean
  536. // data:
  537. // type: array
  538. // items:
  539. // "$ref": "#/definitions/Team"
  540. opts := &models.SearchTeamOptions{
  541. UserID: ctx.User.ID,
  542. Keyword: strings.TrimSpace(ctx.Query("q")),
  543. OrgID: ctx.Org.Organization.ID,
  544. IncludeDesc: (ctx.Query("include_desc") == "" || ctx.QueryBool("include_desc")),
  545. PageSize: ctx.QueryInt("limit"),
  546. Page: ctx.QueryInt("page"),
  547. }
  548. teams, _, err := models.SearchTeam(opts)
  549. if err != nil {
  550. log.Error("SearchTeam failed: %v", err)
  551. ctx.JSON(500, map[string]interface{}{
  552. "ok": false,
  553. "error": "SearchTeam internal failure",
  554. })
  555. return
  556. }
  557. apiTeams := make([]*api.Team, len(teams))
  558. for i := range teams {
  559. if err := teams[i].GetUnits(); err != nil {
  560. log.Error("Team GetUnits failed: %v", err)
  561. ctx.JSON(500, map[string]interface{}{
  562. "ok": false,
  563. "error": "SearchTeam failed to get units",
  564. })
  565. return
  566. }
  567. apiTeams[i] = convert.ToTeam(teams[i])
  568. }
  569. ctx.JSON(200, map[string]interface{}{
  570. "ok": true,
  571. "data": apiTeams,
  572. })
  573. }