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.

teams.go 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. // Copyright 2014 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. "fmt"
  7. "net/http"
  8. "net/url"
  9. "path"
  10. "strconv"
  11. "strings"
  12. "code.gitea.io/gitea/models"
  13. "code.gitea.io/gitea/models/db"
  14. org_model "code.gitea.io/gitea/models/organization"
  15. "code.gitea.io/gitea/models/perm"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. unit_model "code.gitea.io/gitea/models/unit"
  18. user_model "code.gitea.io/gitea/models/user"
  19. "code.gitea.io/gitea/modules/base"
  20. "code.gitea.io/gitea/modules/context"
  21. "code.gitea.io/gitea/modules/log"
  22. "code.gitea.io/gitea/modules/setting"
  23. "code.gitea.io/gitea/modules/web"
  24. "code.gitea.io/gitea/routers/utils"
  25. shared_user "code.gitea.io/gitea/routers/web/shared/user"
  26. "code.gitea.io/gitea/services/convert"
  27. "code.gitea.io/gitea/services/forms"
  28. org_service "code.gitea.io/gitea/services/org"
  29. repo_service "code.gitea.io/gitea/services/repository"
  30. )
  31. const (
  32. // tplTeams template path for teams list page
  33. tplTeams base.TplName = "org/team/teams"
  34. // tplTeamNew template path for create new team page
  35. tplTeamNew base.TplName = "org/team/new"
  36. // tplTeamMembers template path for showing team members page
  37. tplTeamMembers base.TplName = "org/team/members"
  38. // tplTeamRepositories template path for showing team repositories page
  39. tplTeamRepositories base.TplName = "org/team/repositories"
  40. // tplTeamInvite template path for team invites page
  41. tplTeamInvite base.TplName = "org/team/invite"
  42. )
  43. // Teams render teams list page
  44. func Teams(ctx *context.Context) {
  45. org := ctx.Org.Organization
  46. ctx.Data["Title"] = org.FullName
  47. ctx.Data["PageIsOrgTeams"] = true
  48. for _, t := range ctx.Org.Teams {
  49. if err := t.LoadMembers(ctx); err != nil {
  50. ctx.ServerError("GetMembers", err)
  51. return
  52. }
  53. }
  54. ctx.Data["Teams"] = ctx.Org.Teams
  55. err := shared_user.LoadHeaderCount(ctx)
  56. if err != nil {
  57. ctx.ServerError("LoadHeaderCount", err)
  58. return
  59. }
  60. ctx.HTML(http.StatusOK, tplTeams)
  61. }
  62. // TeamsAction response for join, leave, remove, add operations to team
  63. func TeamsAction(ctx *context.Context) {
  64. page := ctx.FormString("page")
  65. var err error
  66. switch ctx.Params(":action") {
  67. case "join":
  68. if !ctx.Org.IsOwner {
  69. ctx.Error(http.StatusNotFound)
  70. return
  71. }
  72. err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID)
  73. case "leave":
  74. err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer.ID)
  75. if err != nil {
  76. if org_model.IsErrLastOrgOwner(err) {
  77. ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
  78. } else {
  79. log.Error("Action(%s): %v", ctx.Params(":action"), err)
  80. ctx.JSON(http.StatusOK, map[string]any{
  81. "ok": false,
  82. "err": err.Error(),
  83. })
  84. return
  85. }
  86. }
  87. checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/")
  88. return
  89. case "remove":
  90. if !ctx.Org.IsOwner {
  91. ctx.Error(http.StatusNotFound)
  92. return
  93. }
  94. uid := ctx.FormInt64("uid")
  95. if uid == 0 {
  96. ctx.Redirect(ctx.Org.OrgLink + "/teams")
  97. return
  98. }
  99. err = models.RemoveTeamMember(ctx, ctx.Org.Team, uid)
  100. if err != nil {
  101. if org_model.IsErrLastOrgOwner(err) {
  102. ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
  103. } else {
  104. log.Error("Action(%s): %v", ctx.Params(":action"), err)
  105. ctx.JSON(http.StatusOK, map[string]any{
  106. "ok": false,
  107. "err": err.Error(),
  108. })
  109. return
  110. }
  111. }
  112. checkIsOrgMemberAndRedirect(ctx, ctx.Org.OrgLink+"/teams/"+url.PathEscape(ctx.Org.Team.LowerName))
  113. return
  114. case "add":
  115. if !ctx.Org.IsOwner {
  116. ctx.Error(http.StatusNotFound)
  117. return
  118. }
  119. uname := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("uname")))
  120. var u *user_model.User
  121. u, err = user_model.GetUserByName(ctx, uname)
  122. if err != nil {
  123. if user_model.IsErrUserNotExist(err) {
  124. if setting.MailService != nil && user_model.ValidateEmail(uname) == nil {
  125. if err := org_service.CreateTeamInvite(ctx, ctx.Doer, ctx.Org.Team, uname); err != nil {
  126. if org_model.IsErrTeamInviteAlreadyExist(err) {
  127. ctx.Flash.Error(ctx.Tr("form.duplicate_invite_to_team"))
  128. } else if org_model.IsErrUserEmailAlreadyAdded(err) {
  129. ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
  130. } else {
  131. ctx.ServerError("CreateTeamInvite", err)
  132. return
  133. }
  134. }
  135. } else {
  136. ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
  137. }
  138. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName))
  139. } else {
  140. ctx.ServerError("GetUserByName", err)
  141. }
  142. return
  143. }
  144. if u.IsOrganization() {
  145. ctx.Flash.Error(ctx.Tr("form.cannot_add_org_to_team"))
  146. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName))
  147. return
  148. }
  149. if ctx.Org.Team.IsMember(u.ID) {
  150. ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
  151. } else {
  152. err = models.AddTeamMember(ctx, ctx.Org.Team, u.ID)
  153. }
  154. page = "team"
  155. case "remove_invite":
  156. if !ctx.Org.IsOwner {
  157. ctx.Error(http.StatusNotFound)
  158. return
  159. }
  160. iid := ctx.FormInt64("iid")
  161. if iid == 0 {
  162. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName))
  163. return
  164. }
  165. if err := org_model.RemoveInviteByID(ctx, iid, ctx.Org.Team.ID); err != nil {
  166. log.Error("Action(%s): %v", ctx.Params(":action"), err)
  167. ctx.ServerError("RemoveInviteByID", err)
  168. return
  169. }
  170. page = "team"
  171. }
  172. if err != nil {
  173. if org_model.IsErrLastOrgOwner(err) {
  174. ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
  175. } else {
  176. log.Error("Action(%s): %v", ctx.Params(":action"), err)
  177. ctx.JSON(http.StatusOK, map[string]any{
  178. "ok": false,
  179. "err": err.Error(),
  180. })
  181. return
  182. }
  183. }
  184. switch page {
  185. case "team":
  186. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName))
  187. case "home":
  188. ctx.Redirect(ctx.Org.Organization.AsUser().HomeLink())
  189. default:
  190. ctx.Redirect(ctx.Org.OrgLink + "/teams")
  191. }
  192. }
  193. func checkIsOrgMemberAndRedirect(ctx *context.Context, defaultRedirect string) {
  194. if isOrgMember, err := org_model.IsOrganizationMember(ctx, ctx.Org.Organization.ID, ctx.Doer.ID); err != nil {
  195. ctx.ServerError("IsOrganizationMember", err)
  196. return
  197. } else if !isOrgMember {
  198. if ctx.Org.Organization.Visibility.IsPrivate() {
  199. defaultRedirect = setting.AppSubURL + "/"
  200. } else {
  201. defaultRedirect = ctx.Org.Organization.HomeLink()
  202. }
  203. }
  204. ctx.JSONRedirect(defaultRedirect)
  205. }
  206. // TeamsRepoAction operate team's repository
  207. func TeamsRepoAction(ctx *context.Context) {
  208. if !ctx.Org.IsOwner {
  209. ctx.Error(http.StatusNotFound)
  210. return
  211. }
  212. var err error
  213. action := ctx.Params(":action")
  214. switch action {
  215. case "add":
  216. repoName := path.Base(ctx.FormString("repo_name"))
  217. var repo *repo_model.Repository
  218. repo, err = repo_model.GetRepositoryByName(ctx.Org.Organization.ID, repoName)
  219. if err != nil {
  220. if repo_model.IsErrRepoNotExist(err) {
  221. ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo"))
  222. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories")
  223. return
  224. }
  225. ctx.ServerError("GetRepositoryByName", err)
  226. return
  227. }
  228. err = org_service.TeamAddRepository(ctx, ctx.Org.Team, repo)
  229. case "remove":
  230. err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, ctx.FormInt64("repoid"))
  231. case "addall":
  232. err = models.AddAllRepositories(ctx, ctx.Org.Team)
  233. case "removeall":
  234. err = models.RemoveAllRepositories(ctx, ctx.Org.Team)
  235. }
  236. if err != nil {
  237. log.Error("Action(%s): '%s' %v", ctx.Params(":action"), ctx.Org.Team.Name, err)
  238. ctx.ServerError("TeamsRepoAction", err)
  239. return
  240. }
  241. if action == "addall" || action == "removeall" {
  242. ctx.JSONRedirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories")
  243. return
  244. }
  245. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories")
  246. }
  247. // NewTeam render create new team page
  248. func NewTeam(ctx *context.Context) {
  249. ctx.Data["Title"] = ctx.Org.Organization.FullName
  250. ctx.Data["PageIsOrgTeams"] = true
  251. ctx.Data["PageIsOrgTeamsNew"] = true
  252. ctx.Data["Team"] = &org_model.Team{}
  253. ctx.Data["Units"] = unit_model.Units
  254. ctx.HTML(http.StatusOK, tplTeamNew)
  255. }
  256. func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode {
  257. unitPerms := make(map[unit_model.Type]perm.AccessMode)
  258. for _, ut := range unit_model.AllRepoUnitTypes {
  259. // Default accessmode is none
  260. unitPerms[ut] = perm.AccessModeNone
  261. v, ok := forms[fmt.Sprintf("unit_%d", ut)]
  262. if ok {
  263. vv, _ := strconv.Atoi(v[0])
  264. if teamPermission >= perm.AccessModeAdmin {
  265. unitPerms[ut] = teamPermission
  266. // Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
  267. if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
  268. unitPerms[ut] = perm.AccessModeRead
  269. }
  270. } else {
  271. unitPerms[ut] = perm.AccessMode(vv)
  272. if unitPerms[ut] >= perm.AccessModeAdmin {
  273. unitPerms[ut] = perm.AccessModeWrite
  274. }
  275. }
  276. }
  277. }
  278. return unitPerms
  279. }
  280. // NewTeamPost response for create new team
  281. func NewTeamPost(ctx *context.Context) {
  282. form := web.GetForm(ctx).(*forms.CreateTeamForm)
  283. includesAllRepositories := form.RepoAccess == "all"
  284. p := perm.ParseAccessMode(form.Permission)
  285. unitPerms := getUnitPerms(ctx.Req.Form, p)
  286. if p < perm.AccessModeAdmin {
  287. // if p is less than admin accessmode, then it should be general accessmode,
  288. // so we should calculate the minial accessmode from units accessmodes.
  289. p = unit_model.MinUnitAccessMode(unitPerms)
  290. }
  291. t := &org_model.Team{
  292. OrgID: ctx.Org.Organization.ID,
  293. Name: form.TeamName,
  294. Description: form.Description,
  295. AccessMode: p,
  296. IncludesAllRepositories: includesAllRepositories,
  297. CanCreateOrgRepo: form.CanCreateOrgRepo,
  298. }
  299. units := make([]*org_model.TeamUnit, 0, len(unitPerms))
  300. for tp, perm := range unitPerms {
  301. units = append(units, &org_model.TeamUnit{
  302. OrgID: ctx.Org.Organization.ID,
  303. Type: tp,
  304. AccessMode: perm,
  305. })
  306. }
  307. t.Units = units
  308. ctx.Data["Title"] = ctx.Org.Organization.FullName
  309. ctx.Data["PageIsOrgTeams"] = true
  310. ctx.Data["PageIsOrgTeamsNew"] = true
  311. ctx.Data["Units"] = unit_model.Units
  312. ctx.Data["Team"] = t
  313. if ctx.HasError() {
  314. ctx.HTML(http.StatusOK, tplTeamNew)
  315. return
  316. }
  317. if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 {
  318. ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form)
  319. return
  320. }
  321. if err := models.NewTeam(ctx, t); err != nil {
  322. ctx.Data["Err_TeamName"] = true
  323. switch {
  324. case org_model.IsErrTeamAlreadyExist(err):
  325. ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form)
  326. default:
  327. ctx.ServerError("NewTeam", err)
  328. }
  329. return
  330. }
  331. log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name)
  332. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName))
  333. }
  334. // TeamMembers render team members page
  335. func TeamMembers(ctx *context.Context) {
  336. ctx.Data["Title"] = ctx.Org.Team.Name
  337. ctx.Data["PageIsOrgTeams"] = true
  338. ctx.Data["PageIsOrgTeamMembers"] = true
  339. if err := shared_user.LoadHeaderCount(ctx); err != nil {
  340. ctx.ServerError("LoadHeaderCount", err)
  341. return
  342. }
  343. if err := ctx.Org.Team.LoadMembers(ctx); err != nil {
  344. ctx.ServerError("GetMembers", err)
  345. return
  346. }
  347. ctx.Data["Units"] = unit_model.Units
  348. invites, err := org_model.GetInvitesByTeamID(ctx, ctx.Org.Team.ID)
  349. if err != nil {
  350. ctx.ServerError("GetInvitesByTeamID", err)
  351. return
  352. }
  353. ctx.Data["Invites"] = invites
  354. ctx.Data["IsEmailInviteEnabled"] = setting.MailService != nil
  355. ctx.HTML(http.StatusOK, tplTeamMembers)
  356. }
  357. // TeamRepositories show the repositories of team
  358. func TeamRepositories(ctx *context.Context) {
  359. ctx.Data["Title"] = ctx.Org.Team.Name
  360. ctx.Data["PageIsOrgTeams"] = true
  361. ctx.Data["PageIsOrgTeamRepos"] = true
  362. if err := shared_user.LoadHeaderCount(ctx); err != nil {
  363. ctx.ServerError("LoadHeaderCount", err)
  364. return
  365. }
  366. if err := ctx.Org.Team.LoadRepositories(ctx); err != nil {
  367. ctx.ServerError("GetRepositories", err)
  368. return
  369. }
  370. ctx.Data["Units"] = unit_model.Units
  371. ctx.HTML(http.StatusOK, tplTeamRepositories)
  372. }
  373. // SearchTeam api for searching teams
  374. func SearchTeam(ctx *context.Context) {
  375. listOptions := db.ListOptions{
  376. Page: ctx.FormInt("page"),
  377. PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
  378. }
  379. opts := &org_model.SearchTeamOptions{
  380. // UserID is not set because the router already requires the doer to be an org admin. Thus, we don't need to restrict to teams that the user belongs in
  381. Keyword: ctx.FormTrim("q"),
  382. OrgID: ctx.Org.Organization.ID,
  383. IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
  384. ListOptions: listOptions,
  385. }
  386. teams, maxResults, err := org_model.SearchTeam(ctx, opts)
  387. if err != nil {
  388. log.Error("SearchTeam failed: %v", err)
  389. ctx.JSON(http.StatusInternalServerError, map[string]any{
  390. "ok": false,
  391. "error": "SearchTeam internal failure",
  392. })
  393. return
  394. }
  395. apiTeams, err := convert.ToTeams(ctx, teams, false)
  396. if err != nil {
  397. log.Error("convert ToTeams failed: %v", err)
  398. ctx.JSON(http.StatusInternalServerError, map[string]any{
  399. "ok": false,
  400. "error": "SearchTeam failed to get units",
  401. })
  402. return
  403. }
  404. ctx.SetTotalCountHeader(maxResults)
  405. ctx.JSON(http.StatusOK, map[string]any{
  406. "ok": true,
  407. "data": apiTeams,
  408. })
  409. }
  410. // EditTeam render team edit page
  411. func EditTeam(ctx *context.Context) {
  412. ctx.Data["Title"] = ctx.Org.Organization.FullName
  413. ctx.Data["PageIsOrgTeams"] = true
  414. if err := ctx.Org.Team.LoadUnits(ctx); err != nil {
  415. ctx.ServerError("LoadUnits", err)
  416. return
  417. }
  418. ctx.Data["Team"] = ctx.Org.Team
  419. ctx.Data["Units"] = unit_model.Units
  420. ctx.HTML(http.StatusOK, tplTeamNew)
  421. }
  422. // EditTeamPost response for modify team information
  423. func EditTeamPost(ctx *context.Context) {
  424. form := web.GetForm(ctx).(*forms.CreateTeamForm)
  425. t := ctx.Org.Team
  426. newAccessMode := perm.ParseAccessMode(form.Permission)
  427. unitPerms := getUnitPerms(ctx.Req.Form, newAccessMode)
  428. if newAccessMode < perm.AccessModeAdmin {
  429. // if newAccessMode is less than admin accessmode, then it should be general accessmode,
  430. // so we should calculate the minial accessmode from units accessmodes.
  431. newAccessMode = unit_model.MinUnitAccessMode(unitPerms)
  432. }
  433. isAuthChanged := false
  434. isIncludeAllChanged := false
  435. includesAllRepositories := form.RepoAccess == "all"
  436. ctx.Data["Title"] = ctx.Org.Organization.FullName
  437. ctx.Data["PageIsOrgTeams"] = true
  438. ctx.Data["Team"] = t
  439. ctx.Data["Units"] = unit_model.Units
  440. if !t.IsOwnerTeam() {
  441. t.Name = form.TeamName
  442. if t.AccessMode != newAccessMode {
  443. isAuthChanged = true
  444. t.AccessMode = newAccessMode
  445. }
  446. if t.IncludesAllRepositories != includesAllRepositories {
  447. isIncludeAllChanged = true
  448. t.IncludesAllRepositories = includesAllRepositories
  449. }
  450. t.CanCreateOrgRepo = form.CanCreateOrgRepo
  451. } else {
  452. t.CanCreateOrgRepo = true
  453. }
  454. t.Description = form.Description
  455. units := make([]*org_model.TeamUnit, 0, len(unitPerms))
  456. for tp, perm := range unitPerms {
  457. units = append(units, &org_model.TeamUnit{
  458. OrgID: t.OrgID,
  459. TeamID: t.ID,
  460. Type: tp,
  461. AccessMode: perm,
  462. })
  463. }
  464. t.Units = units
  465. if ctx.HasError() {
  466. ctx.HTML(http.StatusOK, tplTeamNew)
  467. return
  468. }
  469. if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 {
  470. ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form)
  471. return
  472. }
  473. if err := models.UpdateTeam(ctx, t, isAuthChanged, isIncludeAllChanged); err != nil {
  474. ctx.Data["Err_TeamName"] = true
  475. switch {
  476. case org_model.IsErrTeamAlreadyExist(err):
  477. ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form)
  478. default:
  479. ctx.ServerError("UpdateTeam", err)
  480. }
  481. return
  482. }
  483. ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName))
  484. }
  485. // DeleteTeam response for the delete team request
  486. func DeleteTeam(ctx *context.Context) {
  487. if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
  488. ctx.Flash.Error("DeleteTeam: " + err.Error())
  489. } else {
  490. ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success"))
  491. }
  492. ctx.JSONRedirect(ctx.Org.OrgLink + "/teams")
  493. }
  494. // TeamInvite renders the team invite page
  495. func TeamInvite(ctx *context.Context) {
  496. invite, org, team, inviter, err := getTeamInviteFromContext(ctx)
  497. if err != nil {
  498. if org_model.IsErrTeamInviteNotFound(err) {
  499. ctx.NotFound("ErrTeamInviteNotFound", err)
  500. } else {
  501. ctx.ServerError("getTeamInviteFromContext", err)
  502. }
  503. return
  504. }
  505. ctx.Data["Title"] = ctx.Tr("org.teams.invite_team_member", team.Name)
  506. ctx.Data["Invite"] = invite
  507. ctx.Data["Organization"] = org
  508. ctx.Data["Team"] = team
  509. ctx.Data["Inviter"] = inviter
  510. ctx.HTML(http.StatusOK, tplTeamInvite)
  511. }
  512. // TeamInvitePost handles the team invitation
  513. func TeamInvitePost(ctx *context.Context) {
  514. invite, org, team, _, err := getTeamInviteFromContext(ctx)
  515. if err != nil {
  516. if org_model.IsErrTeamInviteNotFound(err) {
  517. ctx.NotFound("ErrTeamInviteNotFound", err)
  518. } else {
  519. ctx.ServerError("getTeamInviteFromContext", err)
  520. }
  521. return
  522. }
  523. if err := models.AddTeamMember(ctx, team, ctx.Doer.ID); err != nil {
  524. ctx.ServerError("AddTeamMember", err)
  525. return
  526. }
  527. if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil {
  528. log.Error("RemoveInviteByID: %v", err)
  529. }
  530. ctx.Redirect(org.OrganisationLink() + "/teams/" + url.PathEscape(team.LowerName))
  531. }
  532. func getTeamInviteFromContext(ctx *context.Context) (*org_model.TeamInvite, *org_model.Organization, *org_model.Team, *user_model.User, error) {
  533. invite, err := org_model.GetInviteByToken(ctx, ctx.Params("token"))
  534. if err != nil {
  535. return nil, nil, nil, nil, err
  536. }
  537. inviter, err := user_model.GetUserByID(ctx, invite.InviterID)
  538. if err != nil {
  539. return nil, nil, nil, nil, err
  540. }
  541. team, err := org_model.GetTeamByID(ctx, invite.TeamID)
  542. if err != nil {
  543. return nil, nil, nil, nil, err
  544. }
  545. org, err := user_model.GetUserByID(ctx, team.OrgID)
  546. if err != nil {
  547. return nil, nil, nil, nil, err
  548. }
  549. return invite, org_model.OrgFromUser(org), team, inviter, nil
  550. }