code.gitea.io/gitea@v1.21.7/routers/web/org/teams.go (about)

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