Add team member invite by email (#20307)
Allows to add (not registered) team members by email. related #5353 Invite by mail:  Pending invitations:  Email:  Join form:  Co-authored-by: Jack Hay <jjphay@gmail.com>
This commit is contained in:
parent
7d1aed83f4
commit
c3b2e44392
18 changed files with 615 additions and 43 deletions
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
"code.gitea.io/gitea/models"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/organization"
|
||||
org_model "code.gitea.io/gitea/models/organization"
|
||||
"code.gitea.io/gitea/models/perm"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
unit_model "code.gitea.io/gitea/models/unit"
|
||||
|
@ -23,9 +23,11 @@ import (
|
|||
"code.gitea.io/gitea/modules/context"
|
||||
"code.gitea.io/gitea/modules/convert"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/utils"
|
||||
"code.gitea.io/gitea/services/forms"
|
||||
"code.gitea.io/gitea/services/mailer"
|
||||
org_service "code.gitea.io/gitea/services/org"
|
||||
)
|
||||
|
||||
|
@ -38,6 +40,8 @@ const (
|
|||
tplTeamMembers base.TplName = "org/team/members"
|
||||
// tplTeamRepositories template path for showing team repositories page
|
||||
tplTeamRepositories base.TplName = "org/team/repositories"
|
||||
// tplTeamInvite template path for team invites page
|
||||
tplTeamInvite base.TplName = "org/team/invite"
|
||||
)
|
||||
|
||||
// Teams render teams list page
|
||||
|
@ -59,12 +63,6 @@ func Teams(ctx *context.Context) {
|
|||
|
||||
// TeamsAction response for join, leave, remove, add operations to team
|
||||
func TeamsAction(ctx *context.Context) {
|
||||
uid := ctx.FormInt64("uid")
|
||||
if uid == 0 {
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/teams")
|
||||
return
|
||||
}
|
||||
|
||||
page := ctx.FormString("page")
|
||||
var err error
|
||||
switch ctx.Params(":action") {
|
||||
|
@ -77,7 +75,7 @@ func TeamsAction(ctx *context.Context) {
|
|||
case "leave":
|
||||
err = models.RemoveTeamMember(ctx.Org.Team, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
if organization.IsErrLastOrgOwner(err) {
|
||||
if org_model.IsErrLastOrgOwner(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||
} else {
|
||||
log.Error("Action(%s): %v", ctx.Params(":action"), err)
|
||||
|
@ -98,9 +96,16 @@ func TeamsAction(ctx *context.Context) {
|
|||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
uid := ctx.FormInt64("uid")
|
||||
if uid == 0 {
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/teams")
|
||||
return
|
||||
}
|
||||
|
||||
err = models.RemoveTeamMember(ctx.Org.Team, uid)
|
||||
if err != nil {
|
||||
if organization.IsErrLastOrgOwner(err) {
|
||||
if org_model.IsErrLastOrgOwner(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||
} else {
|
||||
log.Error("Action(%s): %v", ctx.Params(":action"), err)
|
||||
|
@ -126,10 +131,27 @@ func TeamsAction(ctx *context.Context) {
|
|||
u, err = user_model.GetUserByName(ctx, uname)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
||||
if setting.MailService != nil && user_model.ValidateEmail(uname) == nil {
|
||||
invite, err := org_model.CreateTeamInvite(ctx, ctx.Doer, ctx.Org.Team, uname)
|
||||
if err != nil {
|
||||
if org_model.IsErrTeamInviteAlreadyExist(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.duplicate_invite_to_team"))
|
||||
} else if org_model.IsErrUserEmailAlreadyAdded(err) {
|
||||
ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
|
||||
} else {
|
||||
ctx.ServerError("CreateTeamInvite", err)
|
||||
return
|
||||
}
|
||||
} else if err := mailer.MailTeamInvite(ctx, ctx.Doer, ctx.Org.Team, invite); err != nil {
|
||||
ctx.ServerError("MailTeamInvite", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
ctx.Flash.Error(ctx.Tr("form.user_not_exist"))
|
||||
}
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName))
|
||||
} else {
|
||||
ctx.ServerError(" GetUserByName", err)
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -146,11 +168,30 @@ func TeamsAction(ctx *context.Context) {
|
|||
err = models.AddTeamMember(ctx.Org.Team, u.ID)
|
||||
}
|
||||
|
||||
page = "team"
|
||||
case "remove_invite":
|
||||
if !ctx.Org.IsOwner {
|
||||
ctx.Error(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
iid := ctx.FormInt64("iid")
|
||||
if iid == 0 {
|
||||
ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName))
|
||||
return
|
||||
}
|
||||
|
||||
if err := org_model.RemoveInviteByID(ctx, iid, ctx.Org.Team.ID); err != nil {
|
||||
log.Error("Action(%s): %v", ctx.Params(":action"), err)
|
||||
ctx.ServerError("RemoveInviteByID", err)
|
||||
return
|
||||
}
|
||||
|
||||
page = "team"
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if organization.IsErrLastOrgOwner(err) {
|
||||
if org_model.IsErrLastOrgOwner(err) {
|
||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||
} else {
|
||||
log.Error("Action(%s): %v", ctx.Params(":action"), err)
|
||||
|
@ -224,7 +265,7 @@ func NewTeam(ctx *context.Context) {
|
|||
ctx.Data["Title"] = ctx.Org.Organization.FullName
|
||||
ctx.Data["PageIsOrgTeams"] = true
|
||||
ctx.Data["PageIsOrgTeamsNew"] = true
|
||||
ctx.Data["Team"] = &organization.Team{}
|
||||
ctx.Data["Team"] = &org_model.Team{}
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
ctx.HTML(http.StatusOK, tplTeamNew)
|
||||
}
|
||||
|
@ -255,7 +296,7 @@ func NewTeamPost(ctx *context.Context) {
|
|||
p = unit_model.MinUnitAccessMode(unitPerms)
|
||||
}
|
||||
|
||||
t := &organization.Team{
|
||||
t := &org_model.Team{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Name: form.TeamName,
|
||||
Description: form.Description,
|
||||
|
@ -265,9 +306,9 @@ func NewTeamPost(ctx *context.Context) {
|
|||
}
|
||||
|
||||
if t.AccessMode < perm.AccessModeAdmin {
|
||||
units := make([]*organization.TeamUnit, 0, len(unitPerms))
|
||||
units := make([]*org_model.TeamUnit, 0, len(unitPerms))
|
||||
for tp, perm := range unitPerms {
|
||||
units = append(units, &organization.TeamUnit{
|
||||
units = append(units, &org_model.TeamUnit{
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
Type: tp,
|
||||
AccessMode: perm,
|
||||
|
@ -295,7 +336,7 @@ func NewTeamPost(ctx *context.Context) {
|
|||
if err := models.NewTeam(t); err != nil {
|
||||
ctx.Data["Err_TeamName"] = true
|
||||
switch {
|
||||
case organization.IsErrTeamAlreadyExist(err):
|
||||
case org_model.IsErrTeamAlreadyExist(err):
|
||||
ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form)
|
||||
default:
|
||||
ctx.ServerError("NewTeam", err)
|
||||
|
@ -316,6 +357,15 @@ func TeamMembers(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
ctx.Data["Units"] = unit_model.Units
|
||||
|
||||
invites, err := org_model.GetInvitesByTeamID(ctx, ctx.Org.Team.ID)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetInvitesByTeamID", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Invites"] = invites
|
||||
ctx.Data["IsEmailInviteEnabled"] = setting.MailService != nil
|
||||
|
||||
ctx.HTML(http.StatusOK, tplTeamMembers)
|
||||
}
|
||||
|
||||
|
@ -339,7 +389,7 @@ func SearchTeam(ctx *context.Context) {
|
|||
PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")),
|
||||
}
|
||||
|
||||
opts := &organization.SearchTeamOptions{
|
||||
opts := &org_model.SearchTeamOptions{
|
||||
// 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
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
OrgID: ctx.Org.Organization.ID,
|
||||
|
@ -347,7 +397,7 @@ func SearchTeam(ctx *context.Context) {
|
|||
ListOptions: listOptions,
|
||||
}
|
||||
|
||||
teams, maxResults, err := organization.SearchTeam(opts)
|
||||
teams, maxResults, err := org_model.SearchTeam(opts)
|
||||
if err != nil {
|
||||
log.Error("SearchTeam failed: %v", err)
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
|
@ -424,16 +474,16 @@ func EditTeamPost(ctx *context.Context) {
|
|||
|
||||
t.Description = form.Description
|
||||
if t.AccessMode < perm.AccessModeAdmin {
|
||||
units := make([]organization.TeamUnit, 0, len(unitPerms))
|
||||
units := make([]org_model.TeamUnit, 0, len(unitPerms))
|
||||
for tp, perm := range unitPerms {
|
||||
units = append(units, organization.TeamUnit{
|
||||
units = append(units, org_model.TeamUnit{
|
||||
OrgID: t.OrgID,
|
||||
TeamID: t.ID,
|
||||
Type: tp,
|
||||
AccessMode: perm,
|
||||
})
|
||||
}
|
||||
if err := organization.UpdateTeamUnits(t, units); err != nil {
|
||||
if err := org_model.UpdateTeamUnits(t, units); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "UpdateTeamUnits", err.Error())
|
||||
return
|
||||
}
|
||||
|
@ -452,7 +502,7 @@ func EditTeamPost(ctx *context.Context) {
|
|||
if err := models.UpdateTeam(t, isAuthChanged, isIncludeAllChanged); err != nil {
|
||||
ctx.Data["Err_TeamName"] = true
|
||||
switch {
|
||||
case organization.IsErrTeamAlreadyExist(err):
|
||||
case org_model.IsErrTeamAlreadyExist(err):
|
||||
ctx.RenderWithErr(ctx.Tr("form.team_name_been_taken"), tplTeamNew, &form)
|
||||
default:
|
||||
ctx.ServerError("UpdateTeam", err)
|
||||
|
@ -474,3 +524,72 @@ func DeleteTeam(ctx *context.Context) {
|
|||
"redirect": ctx.Org.OrgLink + "/teams",
|
||||
})
|
||||
}
|
||||
|
||||
// TeamInvite renders the team invite page
|
||||
func TeamInvite(ctx *context.Context) {
|
||||
invite, org, team, inviter, err := getTeamInviteFromContext(ctx)
|
||||
if err != nil {
|
||||
if org_model.IsErrTeamInviteNotFound(err) {
|
||||
ctx.NotFound("ErrTeamInviteNotFound", err)
|
||||
} else {
|
||||
ctx.ServerError("getTeamInviteFromContext", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = ctx.Tr("org.teams.invite_team_member", team.Name)
|
||||
ctx.Data["Invite"] = invite
|
||||
ctx.Data["Organization"] = org
|
||||
ctx.Data["Team"] = team
|
||||
ctx.Data["Inviter"] = inviter
|
||||
|
||||
ctx.HTML(http.StatusOK, tplTeamInvite)
|
||||
}
|
||||
|
||||
// TeamInvitePost handles the team invitation
|
||||
func TeamInvitePost(ctx *context.Context) {
|
||||
invite, org, team, _, err := getTeamInviteFromContext(ctx)
|
||||
if err != nil {
|
||||
if org_model.IsErrTeamInviteNotFound(err) {
|
||||
ctx.NotFound("ErrTeamInviteNotFound", err)
|
||||
} else {
|
||||
ctx.ServerError("getTeamInviteFromContext", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if err := models.AddTeamMember(team, ctx.Doer.ID); err != nil {
|
||||
ctx.ServerError("AddTeamMember", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := org_model.RemoveInviteByID(ctx, invite.ID, team.ID); err != nil {
|
||||
log.Error("RemoveInviteByID: %v", err)
|
||||
}
|
||||
|
||||
ctx.Redirect(org.OrganisationLink() + "/teams/" + url.PathEscape(team.LowerName))
|
||||
}
|
||||
|
||||
func getTeamInviteFromContext(ctx *context.Context) (*org_model.TeamInvite, *org_model.Organization, *org_model.Team, *user_model.User, error) {
|
||||
invite, err := org_model.GetInviteByToken(ctx, ctx.Params("token"))
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
inviter, err := user_model.GetUserByIDCtx(ctx, invite.InviterID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
team, err := org_model.GetTeamByID(ctx, invite.TeamID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
org, err := user_model.GetUserByIDCtx(ctx, team.OrgID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
return invite, org_model.OrgFromUser(org), team, inviter, nil
|
||||
}
|
||||
|
|
|
@ -651,6 +651,11 @@ func RegisterRoutes(m *web.Route) {
|
|||
m.Post("/create", bindIgnErr(forms.CreateOrgForm{}), org.CreatePost)
|
||||
})
|
||||
|
||||
m.Group("/invite/{token}", func() {
|
||||
m.Get("", org.TeamInvite)
|
||||
m.Post("", org.TeamInvitePost)
|
||||
})
|
||||
|
||||
m.Group("/{org}", func() {
|
||||
m.Get("/dashboard", user.Dashboard)
|
||||
m.Get("/dashboard/{team}", user.Dashboard)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue