2014-06-30 04:30:41 +08:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-11-06 17:37:14 +08:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-28 02:20:29 +08:00
// SPDX-License-Identifier: MIT
2014-06-30 04:30:41 +08:00
2014-06-27 21:33:49 +08:00
package org
import (
2023-04-14 03:06:10 +08:00
"fmt"
2019-06-13 03:41:28 +08:00
"net/http"
2021-11-17 02:18:25 +08:00
"net/url"
2014-08-26 18:11:15 +08:00
"path"
2022-01-05 11:37:00 +08:00
"strconv"
2017-11-26 16:01:48 +08:00
"strings"
2014-08-26 18:11:15 +08:00
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/models"
2022-04-08 02:59:56 +08:00
"code.gitea.io/gitea/models/db"
2022-10-19 20:40:28 +08:00
org_model "code.gitea.io/gitea/models/organization"
2021-11-28 19:58:28 +08:00
"code.gitea.io/gitea/models/perm"
2021-12-10 09:27:50 +08:00
repo_model "code.gitea.io/gitea/models/repo"
2021-11-10 03:57:58 +08:00
unit_model "code.gitea.io/gitea/models/unit"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2016-11-11 00:24:48 +08:00
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
2022-10-19 20:40:28 +08:00
"code.gitea.io/gitea/modules/setting"
2021-01-26 23:36:53 +08:00
"code.gitea.io/gitea/modules/web"
2017-12-07 15:00:09 +08:00
"code.gitea.io/gitea/routers/utils"
2022-12-29 10:57:15 +08:00
"code.gitea.io/gitea/services/convert"
2021-04-07 03:44:05 +08:00
"code.gitea.io/gitea/services/forms"
2022-08-25 10:31:57 +08:00
org_service "code.gitea.io/gitea/services/org"
2014-06-27 21:33:49 +08:00
)
2014-06-30 04:30:41 +08:00
const (
2016-11-18 11:03:03 +08:00
// tplTeams template path for teams list page
tplTeams base . TplName = "org/team/teams"
// tplTeamNew template path for create new team page
tplTeamNew base . TplName = "org/team/new"
// tplTeamMembers template path for showing team members page
tplTeamMembers base . TplName = "org/team/members"
// tplTeamRepositories template path for showing team repositories page
tplTeamRepositories base . TplName = "org/team/repositories"
2022-10-19 20:40:28 +08:00
// tplTeamInvite template path for team invites page
tplTeamInvite base . TplName = "org/team/invite"
2014-06-30 04:30:41 +08:00
)
2016-11-18 11:03:03 +08:00
// Teams render teams list page
2016-03-12 00:56:52 +08:00
func Teams ( ctx * context . Context ) {
2014-08-16 16:21:17 +08:00
org := ctx . Org . Organization
ctx . Data [ "Title" ] = org . FullName
ctx . Data [ "PageIsOrgTeams" ] = true
2014-06-30 04:30:41 +08:00
2021-11-19 19:41:40 +08:00
for _ , t := range ctx . Org . Teams {
2022-12-03 10:48:26 +08:00
if err := t . LoadMembers ( ctx ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "GetMembers" , err )
2014-06-30 04:30:41 +08:00
return
}
}
2021-11-19 19:41:40 +08:00
ctx . Data [ "Teams" ] = ctx . Org . Teams
2014-06-30 04:30:41 +08:00
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeams )
2014-06-27 21:33:49 +08:00
}
2016-11-18 11:03:03 +08:00
// TeamsAction response for join, leave, remove, add operations to team
2016-03-12 00:56:52 +08:00
func TeamsAction ( ctx * context . Context ) {
2021-08-11 08:31:13 +08:00
page := ctx . FormString ( "page" )
2014-08-16 16:21:17 +08:00
var err error
switch ctx . Params ( ":action" ) {
case "join" :
2014-08-24 21:09:05 +08:00
if ! ctx . Org . IsOwner {
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusNotFound )
2014-08-24 21:09:05 +08:00
return
}
2022-03-29 14:29:02 +08:00
err = models . AddTeamMember ( ctx . Org . Team , ctx . Doer . ID )
2014-08-16 16:21:17 +08:00
case "leave" :
2022-03-29 14:29:02 +08:00
err = models . RemoveTeamMember ( ctx . Org . Team , ctx . Doer . ID )
2021-08-27 10:57:40 +08:00
if err != nil {
2022-10-19 20:40:28 +08:00
if org_model . IsErrLastOrgOwner ( err ) {
2021-08-27 10:57:40 +08:00
ctx . Flash . Error ( ctx . Tr ( "form.last_org_owner" ) )
} else {
log . Error ( "Action(%s): %v" , ctx . Params ( ":action" ) , err )
2023-07-05 02:36:08 +08:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-08-27 10:57:40 +08:00
"ok" : false ,
"err" : err . Error ( ) ,
} )
return
}
}
2023-08-12 15:02:22 +08:00
checkIsOrgMemberAndRedirect ( ctx , ctx . Org . OrgLink + "/teams/" )
2021-08-27 10:57:40 +08:00
return
2014-08-24 21:09:05 +08:00
case "remove" :
if ! ctx . Org . IsOwner {
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusNotFound )
2014-08-24 21:09:05 +08:00
return
}
2022-10-19 20:40:28 +08:00
uid := ctx . FormInt64 ( "uid" )
if uid == 0 {
ctx . Redirect ( ctx . Org . OrgLink + "/teams" )
return
}
2022-03-29 14:29:02 +08:00
err = models . RemoveTeamMember ( ctx . Org . Team , uid )
2021-08-27 10:57:40 +08:00
if err != nil {
2022-10-19 20:40:28 +08:00
if org_model . IsErrLastOrgOwner ( err ) {
2021-08-27 10:57:40 +08:00
ctx . Flash . Error ( ctx . Tr ( "form.last_org_owner" ) )
} else {
log . Error ( "Action(%s): %v" , ctx . Params ( ":action" ) , err )
2023-07-05 02:36:08 +08:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2021-08-27 10:57:40 +08:00
"ok" : false ,
"err" : err . Error ( ) ,
} )
return
}
}
2023-08-12 15:02:22 +08:00
checkIsOrgMemberAndRedirect ( ctx , ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) )
2021-08-27 10:57:40 +08:00
return
2014-08-24 21:09:05 +08:00
case "add" :
if ! ctx . Org . IsOwner {
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusNotFound )
2014-08-24 21:09:05 +08:00
return
}
2021-08-11 08:31:13 +08:00
uname := utils . RemoveUsernameParameterSuffix ( strings . ToLower ( ctx . FormString ( "uname" ) ) )
2021-11-24 17:49:20 +08:00
var u * user_model . User
2022-05-20 22:08:52 +08:00
u , err = user_model . GetUserByName ( ctx , uname )
2014-08-24 21:09:05 +08:00
if err != nil {
2021-11-24 17:49:20 +08:00
if user_model . IsErrUserNotExist ( err ) {
2022-10-19 20:40:28 +08:00
if setting . MailService != nil && user_model . ValidateEmail ( uname ) == nil {
2022-10-20 11:23:31 +08:00
if err := org_service . CreateTeamInvite ( ctx , ctx . Doer , ctx . Org . Team , uname ) ; err != nil {
2022-10-19 20:40:28 +08:00
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 {
ctx . Flash . Error ( ctx . Tr ( "form.user_not_exist" ) )
}
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) )
2014-08-24 21:09:05 +08:00
} else {
2022-10-19 20:40:28 +08:00
ctx . ServerError ( "GetUserByName" , err )
2014-08-24 21:09:05 +08:00
}
return
}
2017-05-26 21:12:02 +08:00
if u . IsOrganization ( ) {
ctx . Flash . Error ( ctx . Tr ( "form.cannot_add_org_to_team" ) )
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) )
2017-05-26 21:12:02 +08:00
return
}
2018-08-20 02:49:19 +08:00
if ctx . Org . Team . IsMember ( u . ID ) {
ctx . Flash . Error ( ctx . Tr ( "org.teams.add_duplicate_users" ) )
} else {
2022-03-29 14:29:02 +08:00
err = models . AddTeamMember ( ctx . Org . Team , u . ID )
2018-08-20 02:49:19 +08:00
}
2022-10-19 20:40:28 +08:00
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
}
2014-08-24 21:09:05 +08:00
page = "team"
2014-07-03 04:42:16 +08:00
}
2014-08-16 16:21:17 +08:00
if err != nil {
2022-10-19 20:40:28 +08:00
if org_model . IsErrLastOrgOwner ( err ) {
2014-08-24 21:09:05 +08:00
ctx . Flash . Error ( ctx . Tr ( "form.last_org_owner" ) )
} else {
2019-04-02 15:48:31 +08:00
log . Error ( "Action(%s): %v" , ctx . Params ( ":action" ) , err )
2023-07-05 02:36:08 +08:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2014-08-24 21:09:05 +08:00
"ok" : false ,
"err" : err . Error ( ) ,
} )
return
}
}
switch page {
case "team" :
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) )
2017-03-09 19:18:49 +08:00
case "home" :
2021-11-19 19:41:40 +08:00
ctx . Redirect ( ctx . Org . Organization . AsUser ( ) . HomeLink ( ) )
2014-08-24 21:09:05 +08:00
default :
ctx . Redirect ( ctx . Org . OrgLink + "/teams" )
2014-07-03 04:42:16 +08:00
}
2014-08-16 16:21:17 +08:00
}
2014-07-03 04:42:16 +08:00
2023-08-12 15:02:22 +08:00
func checkIsOrgMemberAndRedirect ( ctx * context . Context , defaultRedirect string ) {
if isOrgMember , err := org_model . IsOrganizationMember ( ctx , ctx . Org . Organization . ID , ctx . Doer . ID ) ; err != nil {
ctx . ServerError ( "IsOrganizationMember" , err )
return
} else if ! isOrgMember {
if ctx . Org . Organization . Visibility . IsPrivate ( ) {
defaultRedirect = setting . AppSubURL + "/"
} else {
defaultRedirect = ctx . Org . Organization . HomeLink ( )
}
}
ctx . JSONRedirect ( defaultRedirect )
}
2016-11-18 11:03:03 +08:00
// TeamsRepoAction operate team's repository
2016-03-12 00:56:52 +08:00
func TeamsRepoAction ( ctx * context . Context ) {
2014-08-26 18:11:15 +08:00
if ! ctx . Org . IsOwner {
2021-04-05 23:30:52 +08:00
ctx . Error ( http . StatusNotFound )
2014-08-26 18:11:15 +08:00
return
}
var err error
2019-11-17 01:36:13 +08:00
action := ctx . Params ( ":action" )
switch action {
2014-08-26 18:11:15 +08:00
case "add" :
2021-08-11 08:31:13 +08:00
repoName := path . Base ( ctx . FormString ( "repo_name" ) )
2021-12-10 09:27:50 +08:00
var repo * repo_model . Repository
repo , err = repo_model . GetRepositoryByName ( ctx . Org . Organization . ID , repoName )
2014-08-26 18:11:15 +08:00
if err != nil {
2021-12-10 09:27:50 +08:00
if repo_model . IsErrRepoNotExist ( err ) {
2014-10-10 17:06:12 +08:00
ctx . Flash . Error ( ctx . Tr ( "org.teams.add_nonexistent_repo" ) )
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) + "/repositories" )
2014-10-10 17:06:12 +08:00
return
}
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "GetRepositoryByName" , err )
2014-08-26 18:11:15 +08:00
return
}
2022-08-25 10:31:57 +08:00
err = org_service . TeamAddRepository ( ctx . Org . Team , repo )
2014-08-26 18:11:15 +08:00
case "remove" :
2022-03-29 14:29:02 +08:00
err = models . RemoveRepository ( ctx . Org . Team , ctx . FormInt64 ( "repoid" ) )
2019-11-09 08:39:37 +08:00
case "addall" :
2022-03-29 14:29:02 +08:00
err = models . AddAllRepositories ( ctx . Org . Team )
2019-11-09 08:39:37 +08:00
case "removeall" :
2022-03-29 14:29:02 +08:00
err = models . RemoveAllRepositories ( ctx . Org . Team )
2014-08-26 18:11:15 +08:00
}
if err != nil {
2019-04-02 15:48:31 +08:00
log . Error ( "Action(%s): '%s' %v" , ctx . Params ( ":action" ) , ctx . Org . Team . Name , err )
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "TeamsRepoAction" , err )
2014-08-26 18:11:15 +08:00
return
}
2019-11-09 08:39:37 +08:00
2019-11-17 01:36:13 +08:00
if action == "addall" || action == "removeall" {
2023-07-26 14:04:01 +08:00
ctx . JSONRedirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) + "/repositories" )
2019-11-17 01:36:13 +08:00
return
}
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( ctx . Org . Team . LowerName ) + "/repositories" )
2014-08-26 18:11:15 +08:00
}
2016-11-18 11:03:03 +08:00
// NewTeam render create new team page
2016-03-12 00:56:52 +08:00
func NewTeam ( ctx * context . Context ) {
2014-08-16 16:21:17 +08:00
ctx . Data [ "Title" ] = ctx . Org . Organization . FullName
ctx . Data [ "PageIsOrgTeams" ] = true
ctx . Data [ "PageIsOrgTeamsNew" ] = true
2022-10-19 20:40:28 +08:00
ctx . Data [ "Team" ] = & org_model . Team { }
2021-11-10 03:57:58 +08:00
ctx . Data [ "Units" ] = unit_model . Units
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeamNew )
2014-07-03 04:42:16 +08:00
}
2023-04-14 03:06:10 +08:00
func getUnitPerms ( forms url . Values , teamPermission perm . AccessMode ) map [ unit_model . Type ] perm . AccessMode {
2022-01-05 11:37:00 +08:00
unitPerms := make ( map [ unit_model . Type ] perm . AccessMode )
2023-04-14 03:06:10 +08:00
for _ , ut := range unit_model . AllRepoUnitTypes {
// Default accessmode is none
unitPerms [ ut ] = perm . AccessModeNone
v , ok := forms [ fmt . Sprintf ( "unit_%d" , ut ) ]
if ok {
vv , _ := strconv . Atoi ( v [ 0 ] )
if teamPermission >= perm . AccessModeAdmin {
unitPerms [ ut ] = teamPermission
// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
if ut == unit_model . TypeExternalTracker || ut == unit_model . TypeExternalWiki {
unitPerms [ ut ] = perm . AccessModeRead
}
} else {
unitPerms [ ut ] = perm . AccessMode ( vv )
if unitPerms [ ut ] >= perm . AccessModeAdmin {
unitPerms [ ut ] = perm . AccessModeWrite
}
2022-01-05 11:37:00 +08:00
}
}
}
return unitPerms
}
2016-11-18 11:03:03 +08:00
// NewTeamPost response for create new team
2021-01-26 23:36:53 +08:00
func NewTeamPost ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . CreateTeamForm )
2022-01-05 11:37:00 +08:00
includesAllRepositories := form . RepoAccess == "all"
p := perm . ParseAccessMode ( form . Permission )
2023-04-14 03:06:10 +08:00
unitPerms := getUnitPerms ( ctx . Req . Form , p )
2022-01-05 11:37:00 +08:00
if p < perm . AccessModeAdmin {
// if p is less than admin accessmode, then it should be general accessmode,
// so we should calculate the minial accessmode from units accessmodes.
p = unit_model . MinUnitAccessMode ( unitPerms )
}
2014-07-03 04:42:16 +08:00
2022-10-19 20:40:28 +08:00
t := & org_model . Team {
2019-11-06 17:37:14 +08:00
OrgID : ctx . Org . Organization . ID ,
Name : form . TeamName ,
Description : form . Description ,
2022-01-05 11:37:00 +08:00
AccessMode : p ,
2019-11-06 17:37:14 +08:00
IncludesAllRepositories : includesAllRepositories ,
2019-11-20 19:27:49 +08:00
CanCreateOrgRepo : form . CanCreateOrgRepo ,
2014-07-03 04:42:16 +08:00
}
2018-08-22 01:02:32 +08:00
2023-04-14 03:06:10 +08:00
units := make ( [ ] * org_model . TeamUnit , 0 , len ( unitPerms ) )
for tp , perm := range unitPerms {
units = append ( units , & org_model . TeamUnit {
OrgID : ctx . Org . Organization . ID ,
Type : tp ,
AccessMode : perm ,
} )
2017-07-17 10:04:43 +08:00
}
2023-04-14 03:06:10 +08:00
t . Units = units
2017-07-17 10:04:43 +08:00
2022-01-05 11:37:00 +08:00
ctx . Data [ "Title" ] = ctx . Org . Organization . FullName
ctx . Data [ "PageIsOrgTeams" ] = true
ctx . Data [ "PageIsOrgTeamsNew" ] = true
ctx . Data [ "Units" ] = unit_model . Units
2016-01-30 06:06:14 +08:00
ctx . Data [ "Team" ] = t
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeamNew )
2016-01-30 06:06:14 +08:00
return
}
2022-01-05 11:37:00 +08:00
if t . AccessMode < perm . AccessModeAdmin && len ( unitPerms ) == 0 {
2017-07-17 10:04:43 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.team_no_units_error" ) , tplTeamNew , & form )
return
}
2014-08-16 16:21:17 +08:00
if err := models . NewTeam ( t ) ; err != nil {
2016-01-30 06:06:14 +08:00
ctx . Data [ "Err_TeamName" ] = true
switch {
2022-10-19 20:40:28 +08:00
case org_model . IsErrTeamAlreadyExist ( err ) :
2016-11-18 11:03:03 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.team_name_been_taken" ) , tplTeamNew , & form )
2014-08-16 16:21:17 +08:00
default :
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "NewTeam" , err )
2014-07-03 04:42:16 +08:00
}
return
}
2016-01-30 06:06:14 +08:00
log . Trace ( "Team created: %s/%s" , ctx . Org . Organization . Name , t . Name )
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( t . LowerName ) )
2014-06-27 21:33:49 +08:00
}
2014-06-27 22:04:04 +08:00
2016-11-18 11:03:03 +08:00
// TeamMembers render team members page
2016-03-12 00:56:52 +08:00
func TeamMembers ( ctx * context . Context ) {
2014-08-24 21:09:05 +08:00
ctx . Data [ "Title" ] = ctx . Org . Team . Name
ctx . Data [ "PageIsOrgTeams" ] = true
2018-12-09 14:42:11 +08:00
ctx . Data [ "PageIsOrgTeamMembers" ] = true
2022-12-03 10:48:26 +08:00
if err := ctx . Org . Team . LoadMembers ( ctx ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "GetMembers" , err )
2014-08-24 21:09:05 +08:00
return
}
2022-02-24 06:07:05 +08:00
ctx . Data [ "Units" ] = unit_model . Units
2022-10-19 20:40:28 +08:00
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
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeamMembers )
2014-08-24 21:09:05 +08:00
}
2016-11-18 11:03:03 +08:00
// TeamRepositories show the repositories of team
2016-03-12 00:56:52 +08:00
func TeamRepositories ( ctx * context . Context ) {
2014-08-26 18:11:15 +08:00
ctx . Data [ "Title" ] = ctx . Org . Team . Name
ctx . Data [ "PageIsOrgTeams" ] = true
2018-12-09 14:42:11 +08:00
ctx . Data [ "PageIsOrgTeamRepos" ] = true
2022-12-03 10:48:26 +08:00
if err := ctx . Org . Team . LoadRepositories ( ctx ) ; err != nil {
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "GetRepositories" , err )
2014-08-26 18:11:15 +08:00
return
}
2022-02-24 06:07:05 +08:00
ctx . Data [ "Units" ] = unit_model . Units
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeamRepositories )
2014-08-26 18:11:15 +08:00
}
2022-04-08 02:59:56 +08:00
// SearchTeam api for searching teams
func SearchTeam ( ctx * context . Context ) {
listOptions := db . ListOptions {
Page : ctx . FormInt ( "page" ) ,
PageSize : convert . ToCorrectPageSize ( ctx . FormInt ( "limit" ) ) ,
}
2022-10-19 20:40:28 +08:00
opts := & org_model . SearchTeamOptions {
2022-08-22 00:24:05 +08:00
// 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
2022-04-08 02:59:56 +08:00
Keyword : ctx . FormTrim ( "q" ) ,
OrgID : ctx . Org . Organization . ID ,
IncludeDesc : ctx . FormString ( "include_desc" ) == "" || ctx . FormBool ( "include_desc" ) ,
ListOptions : listOptions ,
}
2022-10-19 20:40:28 +08:00
teams , maxResults , err := org_model . SearchTeam ( opts )
2022-04-08 02:59:56 +08:00
if err != nil {
log . Error ( "SearchTeam failed: %v" , err )
2023-07-05 02:36:08 +08:00
ctx . JSON ( http . StatusInternalServerError , map [ string ] any {
2022-04-08 02:59:56 +08:00
"ok" : false ,
"error" : "SearchTeam internal failure" ,
} )
return
}
Add context cache as a request level cache (#22294)
To avoid duplicated load of the same data in an HTTP request, we can set
a context cache to do that. i.e. Some pages may load a user from a
database with the same id in different areas on the same page. But the
code is hidden in two different deep logic. How should we share the
user? As a result of this PR, now if both entry functions accept
`context.Context` as the first parameter and we just need to refactor
`GetUserByID` to reuse the user from the context cache. Then it will not
be loaded twice on an HTTP request.
But of course, sometimes we would like to reload an object from the
database, that's why `RemoveContextData` is also exposed.
The core context cache is here. It defines a new context
```go
type cacheContext struct {
ctx context.Context
data map[any]map[any]any
lock sync.RWMutex
}
var cacheContextKey = struct{}{}
func WithCacheContext(ctx context.Context) context.Context {
return context.WithValue(ctx, cacheContextKey, &cacheContext{
ctx: ctx,
data: make(map[any]map[any]any),
})
}
```
Then you can use the below 4 methods to read/write/del the data within
the same context.
```go
func GetContextData(ctx context.Context, tp, key any) any
func SetContextData(ctx context.Context, tp, key, value any)
func RemoveContextData(ctx context.Context, tp, key any)
func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error)
```
Then let's take a look at how `system.GetString` implement it.
```go
func GetSetting(ctx context.Context, key string) (string, error) {
return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) {
return cache.GetString(genSettingCacheKey(key), func() (string, error) {
res, err := GetSettingNoCache(ctx, key)
if err != nil {
return "", err
}
return res.SettingValue, nil
})
})
}
```
First, it will check if context data include the setting object with the
key. If not, it will query from the global cache which may be memory or
a Redis cache. If not, it will get the object from the database. In the
end, if the object gets from the global cache or database, it will be
set into the context cache.
An object stored in the context cache will only be destroyed after the
context disappeared.
2023-02-15 21:37:34 +08:00
apiTeams , err := convert . ToTeams ( ctx , teams , false )
2022-05-14 01:27:58 +08:00
if err != nil {
log . Error ( "convert ToTeams failed: %v" , err )
2023-07-05 02:36:08 +08:00
ctx . JSON ( http . StatusInternalServerError , map [ string ] any {
2022-05-14 01:27:58 +08:00
"ok" : false ,
"error" : "SearchTeam failed to get units" ,
} )
return
2022-04-08 02:59:56 +08:00
}
ctx . SetTotalCountHeader ( maxResults )
2023-07-05 02:36:08 +08:00
ctx . JSON ( http . StatusOK , map [ string ] any {
2022-04-08 02:59:56 +08:00
"ok" : true ,
"data" : apiTeams ,
} )
}
2016-11-18 11:03:03 +08:00
// EditTeam render team edit page
2016-03-12 00:56:52 +08:00
func EditTeam ( ctx * context . Context ) {
2014-08-24 21:09:05 +08:00
ctx . Data [ "Title" ] = ctx . Org . Organization . FullName
ctx . Data [ "PageIsOrgTeams" ] = true
2023-04-14 03:06:10 +08:00
if err := ctx . Org . Team . LoadUnits ( ctx ) ; err != nil {
ctx . ServerError ( "LoadUnits" , err )
return
}
ctx . Data [ "Team" ] = ctx . Org . Team
2021-11-10 03:57:58 +08:00
ctx . Data [ "Units" ] = unit_model . Units
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeamNew )
2014-06-27 22:04:04 +08:00
}
2014-07-07 18:13:42 +08:00
2016-11-18 11:03:03 +08:00
// EditTeamPost response for modify team information
2021-01-26 23:36:53 +08:00
func EditTeamPost ( ctx * context . Context ) {
2021-04-07 03:44:05 +08:00
form := web . GetForm ( ctx ) . ( * forms . CreateTeamForm )
2014-08-24 21:09:05 +08:00
t := ctx . Org . Team
2023-04-14 03:06:10 +08:00
newAccessMode := perm . ParseAccessMode ( form . Permission )
unitPerms := getUnitPerms ( ctx . Req . Form , newAccessMode )
if newAccessMode < perm . AccessModeAdmin {
// if newAccessMode is less than admin accessmode, then it should be general accessmode,
// so we should calculate the minial accessmode from units accessmodes.
newAccessMode = unit_model . MinUnitAccessMode ( unitPerms )
}
2022-01-05 11:37:00 +08:00
isAuthChanged := false
isIncludeAllChanged := false
includesAllRepositories := form . RepoAccess == "all"
2014-08-24 21:09:05 +08:00
ctx . Data [ "Title" ] = ctx . Org . Organization . FullName
2014-08-23 20:24:02 +08:00
ctx . Data [ "PageIsOrgTeams" ] = true
2016-01-30 06:06:14 +08:00
ctx . Data [ "Team" ] = t
2021-11-10 03:57:58 +08:00
ctx . Data [ "Units" ] = unit_model . Units
2014-08-24 21:09:05 +08:00
if ! t . IsOwnerTeam ( ) {
t . Name = form . TeamName
2022-01-05 11:37:00 +08:00
if t . AccessMode != newAccessMode {
2014-08-24 21:09:05 +08:00
isAuthChanged = true
2022-01-05 11:37:00 +08:00
t . AccessMode = newAccessMode
2014-08-24 21:09:05 +08:00
}
2019-11-06 17:37:14 +08:00
if t . IncludesAllRepositories != includesAllRepositories {
isIncludeAllChanged = true
t . IncludesAllRepositories = includesAllRepositories
}
2022-08-18 16:58:21 +08:00
t . CanCreateOrgRepo = form . CanCreateOrgRepo
} else {
t . CanCreateOrgRepo = true
2014-08-24 21:09:05 +08:00
}
2022-08-18 16:58:21 +08:00
2014-08-24 21:09:05 +08:00
t . Description = form . Description
2023-04-14 03:06:10 +08:00
units := make ( [ ] * org_model . TeamUnit , 0 , len ( unitPerms ) )
for tp , perm := range unitPerms {
units = append ( units , & org_model . TeamUnit {
OrgID : t . OrgID ,
TeamID : t . ID ,
Type : tp ,
AccessMode : perm ,
} )
2017-07-17 10:04:43 +08:00
}
2023-04-14 03:06:10 +08:00
t . Units = units
2017-07-17 10:04:43 +08:00
if ctx . HasError ( ) {
2021-04-05 23:30:52 +08:00
ctx . HTML ( http . StatusOK , tplTeamNew )
2017-07-17 10:04:43 +08:00
return
}
2022-01-05 11:37:00 +08:00
if t . AccessMode < perm . AccessModeAdmin && len ( unitPerms ) == 0 {
2017-07-17 10:04:43 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.team_no_units_error" ) , tplTeamNew , & form )
return
}
2019-11-06 17:37:14 +08:00
if err := models . UpdateTeam ( t , isAuthChanged , isIncludeAllChanged ) ; err != nil {
2016-01-30 06:06:14 +08:00
ctx . Data [ "Err_TeamName" ] = true
switch {
2022-10-19 20:40:28 +08:00
case org_model . IsErrTeamAlreadyExist ( err ) :
2016-11-18 11:03:03 +08:00
ctx . RenderWithErr ( ctx . Tr ( "form.team_name_been_taken" ) , tplTeamNew , & form )
2016-01-30 06:06:14 +08:00
default :
2018-01-11 05:34:17 +08:00
ctx . ServerError ( "UpdateTeam" , err )
2014-08-24 21:09:05 +08:00
}
return
}
2021-11-17 02:18:25 +08:00
ctx . Redirect ( ctx . Org . OrgLink + "/teams/" + url . PathEscape ( t . LowerName ) )
2014-08-24 21:09:05 +08:00
}
2016-11-18 11:03:03 +08:00
// DeleteTeam response for the delete team request
2016-03-12 00:56:52 +08:00
func DeleteTeam ( ctx * context . Context ) {
2014-08-24 21:09:05 +08:00
if err := models . DeleteTeam ( ctx . Org . Team ) ; err != nil {
2015-11-22 14:32:09 +08:00
ctx . Flash . Error ( "DeleteTeam: " + err . Error ( ) )
} else {
ctx . Flash . Success ( ctx . Tr ( "org.teams.delete_team_success" ) )
2014-08-24 21:09:05 +08:00
}
2015-11-22 14:32:09 +08:00
2023-07-26 14:04:01 +08:00
ctx . JSONRedirect ( ctx . Org . OrgLink + "/teams" )
2014-07-07 18:13:42 +08:00
}
2022-10-19 20:40:28 +08:00
// 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
}
2022-12-03 10:48:26 +08:00
inviter , err := user_model . GetUserByID ( ctx , invite . InviterID )
2022-10-19 20:40:28 +08:00
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
}
2022-12-03 10:48:26 +08:00
org , err := user_model . GetUserByID ( ctx , team . OrgID )
2022-10-19 20:40:28 +08:00
if err != nil {
return nil , nil , nil , nil , err
}
return invite , org_model . OrgFromUser ( org ) , team , inviter , nil
}