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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2019 The Gitea Authors. All rights reserved.
  3. // SPDX-License-Identifier: MIT
  4. package org
  5. import (
  6. "errors"
  7. "net/http"
  8. "code.gitea.io/gitea/models"
  9. activities_model "code.gitea.io/gitea/models/activities"
  10. "code.gitea.io/gitea/models/organization"
  11. "code.gitea.io/gitea/models/perm"
  12. access_model "code.gitea.io/gitea/models/perm/access"
  13. repo_model "code.gitea.io/gitea/models/repo"
  14. unit_model "code.gitea.io/gitea/models/unit"
  15. "code.gitea.io/gitea/modules/context"
  16. "code.gitea.io/gitea/modules/log"
  17. api "code.gitea.io/gitea/modules/structs"
  18. "code.gitea.io/gitea/modules/web"
  19. "code.gitea.io/gitea/routers/api/v1/user"
  20. "code.gitea.io/gitea/routers/api/v1/utils"
  21. "code.gitea.io/gitea/services/convert"
  22. org_service "code.gitea.io/gitea/services/org"
  23. repo_service "code.gitea.io/gitea/services/repository"
  24. )
  25. // ListTeams list all the teams of an organization
  26. func ListTeams(ctx *context.APIContext) {
  27. // swagger:operation GET /orgs/{org}/teams organization orgListTeams
  28. // ---
  29. // summary: List an organization's teams
  30. // produces:
  31. // - application/json
  32. // parameters:
  33. // - name: org
  34. // in: path
  35. // description: name of the organization
  36. // type: string
  37. // required: true
  38. // - name: page
  39. // in: query
  40. // description: page number of results to return (1-based)
  41. // type: integer
  42. // - name: limit
  43. // in: query
  44. // description: page size of results
  45. // type: integer
  46. // responses:
  47. // "200":
  48. // "$ref": "#/responses/TeamList"
  49. // "404":
  50. // "$ref": "#/responses/notFound"
  51. teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
  52. ListOptions: utils.GetListOptions(ctx),
  53. OrgID: ctx.Org.Organization.ID,
  54. })
  55. if err != nil {
  56. ctx.Error(http.StatusInternalServerError, "LoadTeams", err)
  57. return
  58. }
  59. apiTeams, err := convert.ToTeams(ctx, teams, false)
  60. if err != nil {
  61. ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
  62. return
  63. }
  64. ctx.SetTotalCountHeader(count)
  65. ctx.JSON(http.StatusOK, apiTeams)
  66. }
  67. // ListUserTeams list all the teams a user belongs to
  68. func ListUserTeams(ctx *context.APIContext) {
  69. // swagger:operation GET /user/teams user userListTeams
  70. // ---
  71. // summary: List all the teams a user belongs to
  72. // produces:
  73. // - application/json
  74. // parameters:
  75. // - name: page
  76. // in: query
  77. // description: page number of results to return (1-based)
  78. // type: integer
  79. // - name: limit
  80. // in: query
  81. // description: page size of results
  82. // type: integer
  83. // responses:
  84. // "200":
  85. // "$ref": "#/responses/TeamList"
  86. teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
  87. ListOptions: utils.GetListOptions(ctx),
  88. UserID: ctx.Doer.ID,
  89. })
  90. if err != nil {
  91. ctx.Error(http.StatusInternalServerError, "GetUserTeams", err)
  92. return
  93. }
  94. apiTeams, err := convert.ToTeams(ctx, teams, true)
  95. if err != nil {
  96. ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
  97. return
  98. }
  99. ctx.SetTotalCountHeader(count)
  100. ctx.JSON(http.StatusOK, apiTeams)
  101. }
  102. // GetTeam api for get a team
  103. func GetTeam(ctx *context.APIContext) {
  104. // swagger:operation GET /teams/{id} organization orgGetTeam
  105. // ---
  106. // summary: Get a team
  107. // produces:
  108. // - application/json
  109. // parameters:
  110. // - name: id
  111. // in: path
  112. // description: id of the team to get
  113. // type: integer
  114. // format: int64
  115. // required: true
  116. // responses:
  117. // "200":
  118. // "$ref": "#/responses/Team"
  119. // "404":
  120. // "$ref": "#/responses/notFound"
  121. apiTeam, err := convert.ToTeam(ctx, ctx.Org.Team, true)
  122. if err != nil {
  123. ctx.InternalServerError(err)
  124. return
  125. }
  126. ctx.JSON(http.StatusOK, apiTeam)
  127. }
  128. func attachTeamUnits(team *organization.Team, units []string) {
  129. unitTypes, _ := unit_model.FindUnitTypes(units...)
  130. team.Units = make([]*organization.TeamUnit, 0, len(units))
  131. for _, tp := range unitTypes {
  132. team.Units = append(team.Units, &organization.TeamUnit{
  133. OrgID: team.OrgID,
  134. Type: tp,
  135. AccessMode: team.AccessMode,
  136. })
  137. }
  138. }
  139. func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode {
  140. res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap))
  141. for unitKey, p := range unitsMap {
  142. res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p)
  143. }
  144. return res
  145. }
  146. func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) {
  147. team.Units = make([]*organization.TeamUnit, 0, len(unitsMap))
  148. for unitKey, p := range unitsMap {
  149. team.Units = append(team.Units, &organization.TeamUnit{
  150. OrgID: team.OrgID,
  151. Type: unit_model.TypeFromKey(unitKey),
  152. AccessMode: perm.ParseAccessMode(p),
  153. })
  154. }
  155. }
  156. func attachAdminTeamUnits(team *organization.Team) {
  157. team.Units = make([]*organization.TeamUnit, 0, len(unit_model.AllRepoUnitTypes))
  158. for _, ut := range unit_model.AllRepoUnitTypes {
  159. up := perm.AccessModeAdmin
  160. if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
  161. up = perm.AccessModeRead
  162. }
  163. team.Units = append(team.Units, &organization.TeamUnit{
  164. OrgID: team.OrgID,
  165. Type: ut,
  166. AccessMode: up,
  167. })
  168. }
  169. }
  170. // CreateTeam api for create a team
  171. func CreateTeam(ctx *context.APIContext) {
  172. // swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
  173. // ---
  174. // summary: Create a team
  175. // consumes:
  176. // - application/json
  177. // produces:
  178. // - application/json
  179. // parameters:
  180. // - name: org
  181. // in: path
  182. // description: name of the organization
  183. // type: string
  184. // required: true
  185. // - name: body
  186. // in: body
  187. // schema:
  188. // "$ref": "#/definitions/CreateTeamOption"
  189. // responses:
  190. // "201":
  191. // "$ref": "#/responses/Team"
  192. // "404":
  193. // "$ref": "#/responses/notFound"
  194. // "422":
  195. // "$ref": "#/responses/validationError"
  196. form := web.GetForm(ctx).(*api.CreateTeamOption)
  197. p := perm.ParseAccessMode(form.Permission)
  198. if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
  199. p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
  200. }
  201. team := &organization.Team{
  202. OrgID: ctx.Org.Organization.ID,
  203. Name: form.Name,
  204. Description: form.Description,
  205. IncludesAllRepositories: form.IncludesAllRepositories,
  206. CanCreateOrgRepo: form.CanCreateOrgRepo,
  207. AccessMode: p,
  208. }
  209. if team.AccessMode < perm.AccessModeAdmin {
  210. if len(form.UnitsMap) > 0 {
  211. attachTeamUnitsMap(team, form.UnitsMap)
  212. } else if len(form.Units) > 0 {
  213. attachTeamUnits(team, form.Units)
  214. } else {
  215. ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
  216. return
  217. }
  218. } else {
  219. attachAdminTeamUnits(team)
  220. }
  221. if err := models.NewTeam(ctx, team); err != nil {
  222. if organization.IsErrTeamAlreadyExist(err) {
  223. ctx.Error(http.StatusUnprocessableEntity, "", err)
  224. } else {
  225. ctx.Error(http.StatusInternalServerError, "NewTeam", err)
  226. }
  227. return
  228. }
  229. apiTeam, err := convert.ToTeam(ctx, team, true)
  230. if err != nil {
  231. ctx.InternalServerError(err)
  232. return
  233. }
  234. ctx.JSON(http.StatusCreated, apiTeam)
  235. }
  236. // EditTeam api for edit a team
  237. func EditTeam(ctx *context.APIContext) {
  238. // swagger:operation PATCH /teams/{id} organization orgEditTeam
  239. // ---
  240. // summary: Edit a team
  241. // consumes:
  242. // - application/json
  243. // produces:
  244. // - application/json
  245. // parameters:
  246. // - name: id
  247. // in: path
  248. // description: id of the team to edit
  249. // type: integer
  250. // required: true
  251. // - name: body
  252. // in: body
  253. // schema:
  254. // "$ref": "#/definitions/EditTeamOption"
  255. // responses:
  256. // "200":
  257. // "$ref": "#/responses/Team"
  258. // "404":
  259. // "$ref": "#/responses/notFound"
  260. form := web.GetForm(ctx).(*api.EditTeamOption)
  261. team := ctx.Org.Team
  262. if err := team.LoadUnits(ctx); err != nil {
  263. ctx.InternalServerError(err)
  264. return
  265. }
  266. if form.CanCreateOrgRepo != nil {
  267. team.CanCreateOrgRepo = team.IsOwnerTeam() || *form.CanCreateOrgRepo
  268. }
  269. if len(form.Name) > 0 {
  270. team.Name = form.Name
  271. }
  272. if form.Description != nil {
  273. team.Description = *form.Description
  274. }
  275. isAuthChanged := false
  276. isIncludeAllChanged := false
  277. if !team.IsOwnerTeam() && len(form.Permission) != 0 {
  278. // Validate permission level.
  279. p := perm.ParseAccessMode(form.Permission)
  280. if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
  281. p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
  282. }
  283. if team.AccessMode != p {
  284. isAuthChanged = true
  285. team.AccessMode = p
  286. }
  287. if form.IncludesAllRepositories != nil {
  288. isIncludeAllChanged = true
  289. team.IncludesAllRepositories = *form.IncludesAllRepositories
  290. }
  291. }
  292. if team.AccessMode < perm.AccessModeAdmin {
  293. if len(form.UnitsMap) > 0 {
  294. attachTeamUnitsMap(team, form.UnitsMap)
  295. } else if len(form.Units) > 0 {
  296. attachTeamUnits(team, form.Units)
  297. }
  298. } else {
  299. attachAdminTeamUnits(team)
  300. }
  301. if err := models.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
  302. ctx.Error(http.StatusInternalServerError, "EditTeam", err)
  303. return
  304. }
  305. apiTeam, err := convert.ToTeam(ctx, team)
  306. if err != nil {
  307. ctx.InternalServerError(err)
  308. return
  309. }
  310. ctx.JSON(http.StatusOK, apiTeam)
  311. }
  312. // DeleteTeam api for delete a team
  313. func DeleteTeam(ctx *context.APIContext) {
  314. // swagger:operation DELETE /teams/{id} organization orgDeleteTeam
  315. // ---
  316. // summary: Delete a team
  317. // parameters:
  318. // - name: id
  319. // in: path
  320. // description: id of the team to delete
  321. // type: integer
  322. // format: int64
  323. // required: true
  324. // responses:
  325. // "204":
  326. // description: team deleted
  327. // "404":
  328. // "$ref": "#/responses/notFound"
  329. if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
  330. ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
  331. return
  332. }
  333. ctx.Status(http.StatusNoContent)
  334. }
  335. // GetTeamMembers api for get a team's members
  336. func GetTeamMembers(ctx *context.APIContext) {
  337. // swagger:operation GET /teams/{id}/members organization orgListTeamMembers
  338. // ---
  339. // summary: List a team's members
  340. // produces:
  341. // - application/json
  342. // parameters:
  343. // - name: id
  344. // in: path
  345. // description: id of the team
  346. // type: integer
  347. // format: int64
  348. // required: true
  349. // - name: page
  350. // in: query
  351. // description: page number of results to return (1-based)
  352. // type: integer
  353. // - name: limit
  354. // in: query
  355. // description: page size of results
  356. // type: integer
  357. // responses:
  358. // "200":
  359. // "$ref": "#/responses/UserList"
  360. // "404":
  361. // "$ref": "#/responses/notFound"
  362. isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID)
  363. if err != nil {
  364. ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
  365. return
  366. } else if !isMember && !ctx.Doer.IsAdmin {
  367. ctx.NotFound()
  368. return
  369. }
  370. teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
  371. ListOptions: utils.GetListOptions(ctx),
  372. TeamID: ctx.Org.Team.ID,
  373. })
  374. if err != nil {
  375. ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err)
  376. return
  377. }
  378. members := make([]*api.User, len(teamMembers))
  379. for i, member := range teamMembers {
  380. members[i] = convert.ToUser(ctx, member, ctx.Doer)
  381. }
  382. ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers))
  383. ctx.JSON(http.StatusOK, members)
  384. }
  385. // GetTeamMember api for get a particular member of team
  386. func GetTeamMember(ctx *context.APIContext) {
  387. // swagger:operation GET /teams/{id}/members/{username} organization orgListTeamMember
  388. // ---
  389. // summary: List a particular member of team
  390. // produces:
  391. // - application/json
  392. // parameters:
  393. // - name: id
  394. // in: path
  395. // description: id of the team
  396. // type: integer
  397. // format: int64
  398. // required: true
  399. // - name: username
  400. // in: path
  401. // description: username of the member to list
  402. // type: string
  403. // required: true
  404. // responses:
  405. // "200":
  406. // "$ref": "#/responses/User"
  407. // "404":
  408. // "$ref": "#/responses/notFound"
  409. u := user.GetUserByParams(ctx)
  410. if ctx.Written() {
  411. return
  412. }
  413. teamID := ctx.ParamsInt64("teamid")
  414. isTeamMember, err := organization.IsUserInTeams(ctx, u.ID, []int64{teamID})
  415. if err != nil {
  416. ctx.Error(http.StatusInternalServerError, "IsUserInTeams", err)
  417. return
  418. } else if !isTeamMember {
  419. ctx.NotFound()
  420. return
  421. }
  422. ctx.JSON(http.StatusOK, convert.ToUser(ctx, u, ctx.Doer))
  423. }
  424. // AddTeamMember api for add a member to a team
  425. func AddTeamMember(ctx *context.APIContext) {
  426. // swagger:operation PUT /teams/{id}/members/{username} organization orgAddTeamMember
  427. // ---
  428. // summary: Add a team member
  429. // produces:
  430. // - application/json
  431. // parameters:
  432. // - name: id
  433. // in: path
  434. // description: id of the team
  435. // type: integer
  436. // format: int64
  437. // required: true
  438. // - name: username
  439. // in: path
  440. // description: username of the user to add
  441. // type: string
  442. // required: true
  443. // responses:
  444. // "204":
  445. // "$ref": "#/responses/empty"
  446. // "404":
  447. // "$ref": "#/responses/notFound"
  448. u := user.GetUserByParams(ctx)
  449. if ctx.Written() {
  450. return
  451. }
  452. if err := models.AddTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
  453. ctx.Error(http.StatusInternalServerError, "AddMember", err)
  454. return
  455. }
  456. ctx.Status(http.StatusNoContent)
  457. }
  458. // RemoveTeamMember api for remove one member from a team
  459. func RemoveTeamMember(ctx *context.APIContext) {
  460. // swagger:operation DELETE /teams/{id}/members/{username} organization orgRemoveTeamMember
  461. // ---
  462. // summary: Remove a team member
  463. // produces:
  464. // - application/json
  465. // parameters:
  466. // - name: id
  467. // in: path
  468. // description: id of the team
  469. // type: integer
  470. // format: int64
  471. // required: true
  472. // - name: username
  473. // in: path
  474. // description: username of the user to remove
  475. // type: string
  476. // required: true
  477. // responses:
  478. // "204":
  479. // "$ref": "#/responses/empty"
  480. // "404":
  481. // "$ref": "#/responses/notFound"
  482. u := user.GetUserByParams(ctx)
  483. if ctx.Written() {
  484. return
  485. }
  486. if err := models.RemoveTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
  487. ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
  488. return
  489. }
  490. ctx.Status(http.StatusNoContent)
  491. }
  492. // GetTeamRepos api for get a team's repos
  493. func GetTeamRepos(ctx *context.APIContext) {
  494. // swagger:operation GET /teams/{id}/repos organization orgListTeamRepos
  495. // ---
  496. // summary: List a team's repos
  497. // produces:
  498. // - application/json
  499. // parameters:
  500. // - name: id
  501. // in: path
  502. // description: id of the team
  503. // type: integer
  504. // format: int64
  505. // required: true
  506. // - name: page
  507. // in: query
  508. // description: page number of results to return (1-based)
  509. // type: integer
  510. // - name: limit
  511. // in: query
  512. // description: page size of results
  513. // type: integer
  514. // responses:
  515. // "200":
  516. // "$ref": "#/responses/RepositoryList"
  517. // "404":
  518. // "$ref": "#/responses/notFound"
  519. team := ctx.Org.Team
  520. teamRepos, err := organization.GetTeamRepositories(ctx, &organization.SearchTeamRepoOptions{
  521. ListOptions: utils.GetListOptions(ctx),
  522. TeamID: team.ID,
  523. })
  524. if err != nil {
  525. ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
  526. return
  527. }
  528. repos := make([]*api.Repository, len(teamRepos))
  529. for i, repo := range teamRepos {
  530. permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  531. if err != nil {
  532. ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
  533. return
  534. }
  535. repos[i] = convert.ToRepo(ctx, repo, permission)
  536. }
  537. ctx.SetTotalCountHeader(int64(team.NumRepos))
  538. ctx.JSON(http.StatusOK, repos)
  539. }
  540. // GetTeamRepo api for get a particular repo of team
  541. func GetTeamRepo(ctx *context.APIContext) {
  542. // swagger:operation GET /teams/{id}/repos/{org}/{repo} organization orgListTeamRepo
  543. // ---
  544. // summary: List a particular repo of team
  545. // produces:
  546. // - application/json
  547. // parameters:
  548. // - name: id
  549. // in: path
  550. // description: id of the team
  551. // type: integer
  552. // format: int64
  553. // required: true
  554. // - name: org
  555. // in: path
  556. // description: organization that owns the repo to list
  557. // type: string
  558. // required: true
  559. // - name: repo
  560. // in: path
  561. // description: name of the repo to list
  562. // type: string
  563. // required: true
  564. // responses:
  565. // "200":
  566. // "$ref": "#/responses/Repository"
  567. // "404":
  568. // "$ref": "#/responses/notFound"
  569. repo := getRepositoryByParams(ctx)
  570. if ctx.Written() {
  571. return
  572. }
  573. if !organization.HasTeamRepo(ctx, ctx.Org.Team.OrgID, ctx.Org.Team.ID, repo.ID) {
  574. ctx.NotFound()
  575. return
  576. }
  577. permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
  578. if err != nil {
  579. ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
  580. return
  581. }
  582. ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
  583. }
  584. // getRepositoryByParams get repository by a team's organization ID and repo name
  585. func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
  586. repo, err := repo_model.GetRepositoryByName(ctx.Org.Team.OrgID, ctx.Params(":reponame"))
  587. if err != nil {
  588. if repo_model.IsErrRepoNotExist(err) {
  589. ctx.NotFound()
  590. } else {
  591. ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
  592. }
  593. return nil
  594. }
  595. return repo
  596. }
  597. // AddTeamRepository api for adding a repository to a team
  598. func AddTeamRepository(ctx *context.APIContext) {
  599. // swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
  600. // ---
  601. // summary: Add a repository to a team
  602. // produces:
  603. // - application/json
  604. // parameters:
  605. // - name: id
  606. // in: path
  607. // description: id of the team
  608. // type: integer
  609. // format: int64
  610. // required: true
  611. // - name: org
  612. // in: path
  613. // description: organization that owns the repo to add
  614. // type: string
  615. // required: true
  616. // - name: repo
  617. // in: path
  618. // description: name of the repo to add
  619. // type: string
  620. // required: true
  621. // responses:
  622. // "204":
  623. // "$ref": "#/responses/empty"
  624. // "403":
  625. // "$ref": "#/responses/forbidden"
  626. // "404":
  627. // "$ref": "#/responses/notFound"
  628. repo := getRepositoryByParams(ctx)
  629. if ctx.Written() {
  630. return
  631. }
  632. if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
  633. ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
  634. return
  635. } else if access < perm.AccessModeAdmin {
  636. ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
  637. return
  638. }
  639. if err := org_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
  640. ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
  641. return
  642. }
  643. ctx.Status(http.StatusNoContent)
  644. }
  645. // RemoveTeamRepository api for removing a repository from a team
  646. func RemoveTeamRepository(ctx *context.APIContext) {
  647. // swagger:operation DELETE /teams/{id}/repos/{org}/{repo} organization orgRemoveTeamRepository
  648. // ---
  649. // summary: Remove a repository from a team
  650. // description: This does not delete the repository, it only removes the
  651. // repository from the team.
  652. // produces:
  653. // - application/json
  654. // parameters:
  655. // - name: id
  656. // in: path
  657. // description: id of the team
  658. // type: integer
  659. // format: int64
  660. // required: true
  661. // - name: org
  662. // in: path
  663. // description: organization that owns the repo to remove
  664. // type: string
  665. // required: true
  666. // - name: repo
  667. // in: path
  668. // description: name of the repo to remove
  669. // type: string
  670. // required: true
  671. // responses:
  672. // "204":
  673. // "$ref": "#/responses/empty"
  674. // "403":
  675. // "$ref": "#/responses/forbidden"
  676. // "404":
  677. // "$ref": "#/responses/notFound"
  678. repo := getRepositoryByParams(ctx)
  679. if ctx.Written() {
  680. return
  681. }
  682. if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
  683. ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
  684. return
  685. } else if access < perm.AccessModeAdmin {
  686. ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
  687. return
  688. }
  689. if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, repo.ID); err != nil {
  690. ctx.Error(http.StatusInternalServerError, "RemoveRepository", err)
  691. return
  692. }
  693. ctx.Status(http.StatusNoContent)
  694. }
  695. // SearchTeam api for searching teams
  696. func SearchTeam(ctx *context.APIContext) {
  697. // swagger:operation GET /orgs/{org}/teams/search organization teamSearch
  698. // ---
  699. // summary: Search for teams within an organization
  700. // produces:
  701. // - application/json
  702. // parameters:
  703. // - name: org
  704. // in: path
  705. // description: name of the organization
  706. // type: string
  707. // required: true
  708. // - name: q
  709. // in: query
  710. // description: keywords to search
  711. // type: string
  712. // - name: include_desc
  713. // in: query
  714. // description: include search within team description (defaults to true)
  715. // type: boolean
  716. // - name: page
  717. // in: query
  718. // description: page number of results to return (1-based)
  719. // type: integer
  720. // - name: limit
  721. // in: query
  722. // description: page size of results
  723. // type: integer
  724. // responses:
  725. // "200":
  726. // description: "SearchResults of a successful search"
  727. // schema:
  728. // type: object
  729. // properties:
  730. // ok:
  731. // type: boolean
  732. // data:
  733. // type: array
  734. // items:
  735. // "$ref": "#/definitions/Team"
  736. // "404":
  737. // "$ref": "#/responses/notFound"
  738. listOptions := utils.GetListOptions(ctx)
  739. opts := &organization.SearchTeamOptions{
  740. Keyword: ctx.FormTrim("q"),
  741. OrgID: ctx.Org.Organization.ID,
  742. IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
  743. ListOptions: listOptions,
  744. }
  745. // Only admin is allowd to search for all teams
  746. if !ctx.Doer.IsAdmin {
  747. opts.UserID = ctx.Doer.ID
  748. }
  749. teams, maxResults, err := organization.SearchTeam(ctx, opts)
  750. if err != nil {
  751. log.Error("SearchTeam failed: %v", err)
  752. ctx.JSON(http.StatusInternalServerError, map[string]any{
  753. "ok": false,
  754. "error": "SearchTeam internal failure",
  755. })
  756. return
  757. }
  758. apiTeams, err := convert.ToTeams(ctx, teams, false)
  759. if err != nil {
  760. ctx.InternalServerError(err)
  761. return
  762. }
  763. ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
  764. ctx.SetTotalCountHeader(maxResults)
  765. ctx.JSON(http.StatusOK, map[string]any{
  766. "ok": true,
  767. "data": apiTeams,
  768. })
  769. }
  770. func ListTeamActivityFeeds(ctx *context.APIContext) {
  771. // swagger:operation GET /teams/{id}/activities/feeds organization orgListTeamActivityFeeds
  772. // ---
  773. // summary: List a team's activity feeds
  774. // produces:
  775. // - application/json
  776. // parameters:
  777. // - name: id
  778. // in: path
  779. // description: id of the team
  780. // type: integer
  781. // format: int64
  782. // required: true
  783. // - name: date
  784. // in: query
  785. // description: the date of the activities to be found
  786. // type: string
  787. // format: date
  788. // - name: page
  789. // in: query
  790. // description: page number of results to return (1-based)
  791. // type: integer
  792. // - name: limit
  793. // in: query
  794. // description: page size of results
  795. // type: integer
  796. // responses:
  797. // "200":
  798. // "$ref": "#/responses/ActivityFeedsList"
  799. // "404":
  800. // "$ref": "#/responses/notFound"
  801. listOptions := utils.GetListOptions(ctx)
  802. opts := activities_model.GetFeedsOptions{
  803. RequestedTeam: ctx.Org.Team,
  804. Actor: ctx.Doer,
  805. IncludePrivate: true,
  806. Date: ctx.FormString("date"),
  807. ListOptions: listOptions,
  808. }
  809. feeds, count, err := activities_model.GetFeeds(ctx, opts)
  810. if err != nil {
  811. ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
  812. return
  813. }
  814. ctx.SetTotalCountHeader(count)
  815. ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
  816. }