diff options
author | KN4CK3R <admin@oldschoolhack.me> | 2022-10-19 14:40:28 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-19 14:40:28 +0200 |
commit | c3b2e44392e7f6c9a77a46664788c0bb9a6f33cb (patch) | |
tree | 6fe65807cd30770951ede4396a149ce03fb56b96 /routers/web | |
parent | 7d1aed83f4d0cdf096ec8758ff8a85ddccf1328d (diff) | |
download | gitea-c3b2e44392e7f6c9a77a46664788c0bb9a6f33cb.tar.gz gitea-c3b2e44392e7f6c9a77a46664788c0bb9a6f33cb.zip |
Add team member invite by email (#20307)
Allows to add (not registered) team members by email.
related #5353
Invite by mail:
![grafik](https://user-images.githubusercontent.com/1666336/178154779-adcc547f-c0b7-4a2a-a131-4e41a3d9d3ad.png)
Pending invitations:
![grafik](https://user-images.githubusercontent.com/1666336/178154882-9d739bb8-2b04-46c1-a025-c1f4be26af98.png)
Email:
![grafik](https://user-images.githubusercontent.com/1666336/178164716-f2f90893-7ba6-4a5e-a3db-42538a660258.png)
Join form:
![grafik](https://user-images.githubusercontent.com/1666336/178154840-aaab983a-d922-4414-b01a-9b1a19c5cef7.png)
Co-authored-by: Jack Hay <jjphay@gmail.com>
Diffstat (limited to 'routers/web')
-rw-r--r-- | routers/web/org/teams.go | 165 | ||||
-rw-r--r-- | routers/web/web.go | 5 |
2 files changed, 147 insertions, 23 deletions
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 13c88565c4..bcdbcbe079 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -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 } @@ -147,10 +169,29 @@ func TeamsAction(ctx *context.Context) { } 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 +} diff --git a/routers/web/web.go b/routers/web/web.go index 8859ec5850..62503b3141 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -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) |