summaryrefslogtreecommitdiffstats
path: root/routers/web/org/teams.go
diff options
context:
space:
mode:
authorKN4CK3R <admin@oldschoolhack.me>2022-10-19 14:40:28 +0200
committerGitHub <noreply@github.com>2022-10-19 14:40:28 +0200
commitc3b2e44392e7f6c9a77a46664788c0bb9a6f33cb (patch)
tree6fe65807cd30770951ede4396a149ce03fb56b96 /routers/web/org/teams.go
parent7d1aed83f4d0cdf096ec8758ff8a85ddccf1328d (diff)
downloadgitea-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/org/teams.go')
-rw-r--r--routers/web/org/teams.go165
1 files changed, 142 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
+}