code.gitea.io/gitea@v1.21.7/routers/api/v1/org/team.go (about)

     1  // Copyright 2016 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  	"errors"
     9  	"net/http"
    10  
    11  	"code.gitea.io/gitea/models"
    12  	activities_model "code.gitea.io/gitea/models/activities"
    13  	"code.gitea.io/gitea/models/organization"
    14  	"code.gitea.io/gitea/models/perm"
    15  	access_model "code.gitea.io/gitea/models/perm/access"
    16  	repo_model "code.gitea.io/gitea/models/repo"
    17  	unit_model "code.gitea.io/gitea/models/unit"
    18  	"code.gitea.io/gitea/modules/context"
    19  	"code.gitea.io/gitea/modules/log"
    20  	api "code.gitea.io/gitea/modules/structs"
    21  	"code.gitea.io/gitea/modules/web"
    22  	"code.gitea.io/gitea/routers/api/v1/user"
    23  	"code.gitea.io/gitea/routers/api/v1/utils"
    24  	"code.gitea.io/gitea/services/convert"
    25  	org_service "code.gitea.io/gitea/services/org"
    26  	repo_service "code.gitea.io/gitea/services/repository"
    27  )
    28  
    29  // ListTeams list all the teams of an organization
    30  func ListTeams(ctx *context.APIContext) {
    31  	// swagger:operation GET /orgs/{org}/teams organization orgListTeams
    32  	// ---
    33  	// summary: List an organization's teams
    34  	// produces:
    35  	// - application/json
    36  	// parameters:
    37  	// - name: org
    38  	//   in: path
    39  	//   description: name of the organization
    40  	//   type: string
    41  	//   required: true
    42  	// - name: page
    43  	//   in: query
    44  	//   description: page number of results to return (1-based)
    45  	//   type: integer
    46  	// - name: limit
    47  	//   in: query
    48  	//   description: page size of results
    49  	//   type: integer
    50  	// responses:
    51  	//   "200":
    52  	//     "$ref": "#/responses/TeamList"
    53  	//   "404":
    54  	//     "$ref": "#/responses/notFound"
    55  
    56  	teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
    57  		ListOptions: utils.GetListOptions(ctx),
    58  		OrgID:       ctx.Org.Organization.ID,
    59  	})
    60  	if err != nil {
    61  		ctx.Error(http.StatusInternalServerError, "LoadTeams", err)
    62  		return
    63  	}
    64  
    65  	apiTeams, err := convert.ToTeams(ctx, teams, false)
    66  	if err != nil {
    67  		ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
    68  		return
    69  	}
    70  
    71  	ctx.SetTotalCountHeader(count)
    72  	ctx.JSON(http.StatusOK, apiTeams)
    73  }
    74  
    75  // ListUserTeams list all the teams a user belongs to
    76  func ListUserTeams(ctx *context.APIContext) {
    77  	// swagger:operation GET /user/teams user userListTeams
    78  	// ---
    79  	// summary: List all the teams a user belongs to
    80  	// produces:
    81  	// - application/json
    82  	// parameters:
    83  	// - name: page
    84  	//   in: query
    85  	//   description: page number of results to return (1-based)
    86  	//   type: integer
    87  	// - name: limit
    88  	//   in: query
    89  	//   description: page size of results
    90  	//   type: integer
    91  	// responses:
    92  	//   "200":
    93  	//     "$ref": "#/responses/TeamList"
    94  
    95  	teams, count, err := organization.SearchTeam(ctx, &organization.SearchTeamOptions{
    96  		ListOptions: utils.GetListOptions(ctx),
    97  		UserID:      ctx.Doer.ID,
    98  	})
    99  	if err != nil {
   100  		ctx.Error(http.StatusInternalServerError, "GetUserTeams", err)
   101  		return
   102  	}
   103  
   104  	apiTeams, err := convert.ToTeams(ctx, teams, true)
   105  	if err != nil {
   106  		ctx.Error(http.StatusInternalServerError, "ConvertToTeams", err)
   107  		return
   108  	}
   109  
   110  	ctx.SetTotalCountHeader(count)
   111  	ctx.JSON(http.StatusOK, apiTeams)
   112  }
   113  
   114  // GetTeam api for get a team
   115  func GetTeam(ctx *context.APIContext) {
   116  	// swagger:operation GET /teams/{id} organization orgGetTeam
   117  	// ---
   118  	// summary: Get a team
   119  	// produces:
   120  	// - application/json
   121  	// parameters:
   122  	// - name: id
   123  	//   in: path
   124  	//   description: id of the team to get
   125  	//   type: integer
   126  	//   format: int64
   127  	//   required: true
   128  	// responses:
   129  	//   "200":
   130  	//     "$ref": "#/responses/Team"
   131  	//   "404":
   132  	//     "$ref": "#/responses/notFound"
   133  
   134  	apiTeam, err := convert.ToTeam(ctx, ctx.Org.Team, true)
   135  	if err != nil {
   136  		ctx.InternalServerError(err)
   137  		return
   138  	}
   139  
   140  	ctx.JSON(http.StatusOK, apiTeam)
   141  }
   142  
   143  func attachTeamUnits(team *organization.Team, units []string) {
   144  	unitTypes, _ := unit_model.FindUnitTypes(units...)
   145  	team.Units = make([]*organization.TeamUnit, 0, len(units))
   146  	for _, tp := range unitTypes {
   147  		team.Units = append(team.Units, &organization.TeamUnit{
   148  			OrgID:      team.OrgID,
   149  			Type:       tp,
   150  			AccessMode: team.AccessMode,
   151  		})
   152  	}
   153  }
   154  
   155  func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode {
   156  	res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap))
   157  	for unitKey, p := range unitsMap {
   158  		res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p)
   159  	}
   160  	return res
   161  }
   162  
   163  func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) {
   164  	team.Units = make([]*organization.TeamUnit, 0, len(unitsMap))
   165  	for unitKey, p := range unitsMap {
   166  		team.Units = append(team.Units, &organization.TeamUnit{
   167  			OrgID:      team.OrgID,
   168  			Type:       unit_model.TypeFromKey(unitKey),
   169  			AccessMode: perm.ParseAccessMode(p),
   170  		})
   171  	}
   172  }
   173  
   174  func attachAdminTeamUnits(team *organization.Team) {
   175  	team.Units = make([]*organization.TeamUnit, 0, len(unit_model.AllRepoUnitTypes))
   176  	for _, ut := range unit_model.AllRepoUnitTypes {
   177  		up := perm.AccessModeAdmin
   178  		if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
   179  			up = perm.AccessModeRead
   180  		}
   181  		team.Units = append(team.Units, &organization.TeamUnit{
   182  			OrgID:      team.OrgID,
   183  			Type:       ut,
   184  			AccessMode: up,
   185  		})
   186  	}
   187  }
   188  
   189  // CreateTeam api for create a team
   190  func CreateTeam(ctx *context.APIContext) {
   191  	// swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
   192  	// ---
   193  	// summary: Create a team
   194  	// consumes:
   195  	// - application/json
   196  	// produces:
   197  	// - application/json
   198  	// parameters:
   199  	// - name: org
   200  	//   in: path
   201  	//   description: name of the organization
   202  	//   type: string
   203  	//   required: true
   204  	// - name: body
   205  	//   in: body
   206  	//   schema:
   207  	//     "$ref": "#/definitions/CreateTeamOption"
   208  	// responses:
   209  	//   "201":
   210  	//     "$ref": "#/responses/Team"
   211  	//   "404":
   212  	//     "$ref": "#/responses/notFound"
   213  	//   "422":
   214  	//     "$ref": "#/responses/validationError"
   215  	form := web.GetForm(ctx).(*api.CreateTeamOption)
   216  	p := perm.ParseAccessMode(form.Permission)
   217  	if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
   218  		p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
   219  	}
   220  	team := &organization.Team{
   221  		OrgID:                   ctx.Org.Organization.ID,
   222  		Name:                    form.Name,
   223  		Description:             form.Description,
   224  		IncludesAllRepositories: form.IncludesAllRepositories,
   225  		CanCreateOrgRepo:        form.CanCreateOrgRepo,
   226  		AccessMode:              p,
   227  	}
   228  
   229  	if team.AccessMode < perm.AccessModeAdmin {
   230  		if len(form.UnitsMap) > 0 {
   231  			attachTeamUnitsMap(team, form.UnitsMap)
   232  		} else if len(form.Units) > 0 {
   233  			attachTeamUnits(team, form.Units)
   234  		} else {
   235  			ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
   236  			return
   237  		}
   238  	} else {
   239  		attachAdminTeamUnits(team)
   240  	}
   241  
   242  	if err := models.NewTeam(ctx, team); err != nil {
   243  		if organization.IsErrTeamAlreadyExist(err) {
   244  			ctx.Error(http.StatusUnprocessableEntity, "", err)
   245  		} else {
   246  			ctx.Error(http.StatusInternalServerError, "NewTeam", err)
   247  		}
   248  		return
   249  	}
   250  
   251  	apiTeam, err := convert.ToTeam(ctx, team, true)
   252  	if err != nil {
   253  		ctx.InternalServerError(err)
   254  		return
   255  	}
   256  	ctx.JSON(http.StatusCreated, apiTeam)
   257  }
   258  
   259  // EditTeam api for edit a team
   260  func EditTeam(ctx *context.APIContext) {
   261  	// swagger:operation PATCH /teams/{id} organization orgEditTeam
   262  	// ---
   263  	// summary: Edit a team
   264  	// consumes:
   265  	// - application/json
   266  	// produces:
   267  	// - application/json
   268  	// parameters:
   269  	// - name: id
   270  	//   in: path
   271  	//   description: id of the team to edit
   272  	//   type: integer
   273  	//   required: true
   274  	// - name: body
   275  	//   in: body
   276  	//   schema:
   277  	//     "$ref": "#/definitions/EditTeamOption"
   278  	// responses:
   279  	//   "200":
   280  	//     "$ref": "#/responses/Team"
   281  	//   "404":
   282  	//     "$ref": "#/responses/notFound"
   283  
   284  	form := web.GetForm(ctx).(*api.EditTeamOption)
   285  	team := ctx.Org.Team
   286  	if err := team.LoadUnits(ctx); err != nil {
   287  		ctx.InternalServerError(err)
   288  		return
   289  	}
   290  
   291  	if form.CanCreateOrgRepo != nil {
   292  		team.CanCreateOrgRepo = team.IsOwnerTeam() || *form.CanCreateOrgRepo
   293  	}
   294  
   295  	if len(form.Name) > 0 {
   296  		team.Name = form.Name
   297  	}
   298  
   299  	if form.Description != nil {
   300  		team.Description = *form.Description
   301  	}
   302  
   303  	isAuthChanged := false
   304  	isIncludeAllChanged := false
   305  	if !team.IsOwnerTeam() && len(form.Permission) != 0 {
   306  		// Validate permission level.
   307  		p := perm.ParseAccessMode(form.Permission)
   308  		if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
   309  			p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
   310  		}
   311  
   312  		if team.AccessMode != p {
   313  			isAuthChanged = true
   314  			team.AccessMode = p
   315  		}
   316  
   317  		if form.IncludesAllRepositories != nil {
   318  			isIncludeAllChanged = true
   319  			team.IncludesAllRepositories = *form.IncludesAllRepositories
   320  		}
   321  	}
   322  
   323  	if team.AccessMode < perm.AccessModeAdmin {
   324  		if len(form.UnitsMap) > 0 {
   325  			attachTeamUnitsMap(team, form.UnitsMap)
   326  		} else if len(form.Units) > 0 {
   327  			attachTeamUnits(team, form.Units)
   328  		}
   329  	} else {
   330  		attachAdminTeamUnits(team)
   331  	}
   332  
   333  	if err := models.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
   334  		ctx.Error(http.StatusInternalServerError, "EditTeam", err)
   335  		return
   336  	}
   337  
   338  	apiTeam, err := convert.ToTeam(ctx, team)
   339  	if err != nil {
   340  		ctx.InternalServerError(err)
   341  		return
   342  	}
   343  	ctx.JSON(http.StatusOK, apiTeam)
   344  }
   345  
   346  // DeleteTeam api for delete a team
   347  func DeleteTeam(ctx *context.APIContext) {
   348  	// swagger:operation DELETE /teams/{id} organization orgDeleteTeam
   349  	// ---
   350  	// summary: Delete a team
   351  	// parameters:
   352  	// - name: id
   353  	//   in: path
   354  	//   description: id of the team to delete
   355  	//   type: integer
   356  	//   format: int64
   357  	//   required: true
   358  	// responses:
   359  	//   "204":
   360  	//     description: team deleted
   361  	//   "404":
   362  	//     "$ref": "#/responses/notFound"
   363  
   364  	if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
   365  		ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
   366  		return
   367  	}
   368  	ctx.Status(http.StatusNoContent)
   369  }
   370  
   371  // GetTeamMembers api for get a team's members
   372  func GetTeamMembers(ctx *context.APIContext) {
   373  	// swagger:operation GET /teams/{id}/members organization orgListTeamMembers
   374  	// ---
   375  	// summary: List a team's members
   376  	// produces:
   377  	// - application/json
   378  	// parameters:
   379  	// - name: id
   380  	//   in: path
   381  	//   description: id of the team
   382  	//   type: integer
   383  	//   format: int64
   384  	//   required: true
   385  	// - name: page
   386  	//   in: query
   387  	//   description: page number of results to return (1-based)
   388  	//   type: integer
   389  	// - name: limit
   390  	//   in: query
   391  	//   description: page size of results
   392  	//   type: integer
   393  	// responses:
   394  	//   "200":
   395  	//     "$ref": "#/responses/UserList"
   396  	//   "404":
   397  	//     "$ref": "#/responses/notFound"
   398  
   399  	isMember, err := organization.IsOrganizationMember(ctx, ctx.Org.Team.OrgID, ctx.Doer.ID)
   400  	if err != nil {
   401  		ctx.Error(http.StatusInternalServerError, "IsOrganizationMember", err)
   402  		return
   403  	} else if !isMember && !ctx.Doer.IsAdmin {
   404  		ctx.NotFound()
   405  		return
   406  	}
   407  
   408  	teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
   409  		ListOptions: utils.GetListOptions(ctx),
   410  		TeamID:      ctx.Org.Team.ID,
   411  	})
   412  	if err != nil {
   413  		ctx.Error(http.StatusInternalServerError, "GetTeamMembers", err)
   414  		return
   415  	}
   416  
   417  	members := make([]*api.User, len(teamMembers))
   418  	for i, member := range teamMembers {
   419  		members[i] = convert.ToUser(ctx, member, ctx.Doer)
   420  	}
   421  
   422  	ctx.SetTotalCountHeader(int64(ctx.Org.Team.NumMembers))
   423  	ctx.JSON(http.StatusOK, members)
   424  }
   425  
   426  // GetTeamMember api for get a particular member of team
   427  func GetTeamMember(ctx *context.APIContext) {
   428  	// swagger:operation GET /teams/{id}/members/{username} organization orgListTeamMember
   429  	// ---
   430  	// summary: List a particular member of team
   431  	// produces:
   432  	// - application/json
   433  	// parameters:
   434  	// - name: id
   435  	//   in: path
   436  	//   description: id of the team
   437  	//   type: integer
   438  	//   format: int64
   439  	//   required: true
   440  	// - name: username
   441  	//   in: path
   442  	//   description: username of the member to list
   443  	//   type: string
   444  	//   required: true
   445  	// responses:
   446  	//   "200":
   447  	//     "$ref": "#/responses/User"
   448  	//   "404":
   449  	//     "$ref": "#/responses/notFound"
   450  
   451  	u := user.GetUserByParams(ctx)
   452  	if ctx.Written() {
   453  		return
   454  	}
   455  	teamID := ctx.ParamsInt64("teamid")
   456  	isTeamMember, err := organization.IsUserInTeams(ctx, u.ID, []int64{teamID})
   457  	if err != nil {
   458  		ctx.Error(http.StatusInternalServerError, "IsUserInTeams", err)
   459  		return
   460  	} else if !isTeamMember {
   461  		ctx.NotFound()
   462  		return
   463  	}
   464  	ctx.JSON(http.StatusOK, convert.ToUser(ctx, u, ctx.Doer))
   465  }
   466  
   467  // AddTeamMember api for add a member to a team
   468  func AddTeamMember(ctx *context.APIContext) {
   469  	// swagger:operation PUT /teams/{id}/members/{username} organization orgAddTeamMember
   470  	// ---
   471  	// summary: Add a team member
   472  	// produces:
   473  	// - application/json
   474  	// parameters:
   475  	// - name: id
   476  	//   in: path
   477  	//   description: id of the team
   478  	//   type: integer
   479  	//   format: int64
   480  	//   required: true
   481  	// - name: username
   482  	//   in: path
   483  	//   description: username of the user to add
   484  	//   type: string
   485  	//   required: true
   486  	// responses:
   487  	//   "204":
   488  	//     "$ref": "#/responses/empty"
   489  	//   "404":
   490  	//     "$ref": "#/responses/notFound"
   491  
   492  	u := user.GetUserByParams(ctx)
   493  	if ctx.Written() {
   494  		return
   495  	}
   496  	if err := models.AddTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
   497  		ctx.Error(http.StatusInternalServerError, "AddMember", err)
   498  		return
   499  	}
   500  	ctx.Status(http.StatusNoContent)
   501  }
   502  
   503  // RemoveTeamMember api for remove one member from a team
   504  func RemoveTeamMember(ctx *context.APIContext) {
   505  	// swagger:operation DELETE /teams/{id}/members/{username} organization orgRemoveTeamMember
   506  	// ---
   507  	// summary: Remove a team member
   508  	// produces:
   509  	// - application/json
   510  	// parameters:
   511  	// - name: id
   512  	//   in: path
   513  	//   description: id of the team
   514  	//   type: integer
   515  	//   format: int64
   516  	//   required: true
   517  	// - name: username
   518  	//   in: path
   519  	//   description: username of the user to remove
   520  	//   type: string
   521  	//   required: true
   522  	// responses:
   523  	//   "204":
   524  	//     "$ref": "#/responses/empty"
   525  	//   "404":
   526  	//     "$ref": "#/responses/notFound"
   527  
   528  	u := user.GetUserByParams(ctx)
   529  	if ctx.Written() {
   530  		return
   531  	}
   532  
   533  	if err := models.RemoveTeamMember(ctx, ctx.Org.Team, u.ID); err != nil {
   534  		ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
   535  		return
   536  	}
   537  	ctx.Status(http.StatusNoContent)
   538  }
   539  
   540  // GetTeamRepos api for get a team's repos
   541  func GetTeamRepos(ctx *context.APIContext) {
   542  	// swagger:operation GET /teams/{id}/repos organization orgListTeamRepos
   543  	// ---
   544  	// summary: List a team's repos
   545  	// produces:
   546  	// - application/json
   547  	// parameters:
   548  	// - name: id
   549  	//   in: path
   550  	//   description: id of the team
   551  	//   type: integer
   552  	//   format: int64
   553  	//   required: true
   554  	// - name: page
   555  	//   in: query
   556  	//   description: page number of results to return (1-based)
   557  	//   type: integer
   558  	// - name: limit
   559  	//   in: query
   560  	//   description: page size of results
   561  	//   type: integer
   562  	// responses:
   563  	//   "200":
   564  	//     "$ref": "#/responses/RepositoryList"
   565  	//   "404":
   566  	//     "$ref": "#/responses/notFound"
   567  
   568  	team := ctx.Org.Team
   569  	teamRepos, err := organization.GetTeamRepositories(ctx, &organization.SearchTeamRepoOptions{
   570  		ListOptions: utils.GetListOptions(ctx),
   571  		TeamID:      team.ID,
   572  	})
   573  	if err != nil {
   574  		ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
   575  		return
   576  	}
   577  	repos := make([]*api.Repository, len(teamRepos))
   578  	for i, repo := range teamRepos {
   579  		permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
   580  		if err != nil {
   581  			ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
   582  			return
   583  		}
   584  		repos[i] = convert.ToRepo(ctx, repo, permission)
   585  	}
   586  	ctx.SetTotalCountHeader(int64(team.NumRepos))
   587  	ctx.JSON(http.StatusOK, repos)
   588  }
   589  
   590  // GetTeamRepo api for get a particular repo of team
   591  func GetTeamRepo(ctx *context.APIContext) {
   592  	// swagger:operation GET /teams/{id}/repos/{org}/{repo} organization orgListTeamRepo
   593  	// ---
   594  	// summary: List a particular repo of team
   595  	// produces:
   596  	// - application/json
   597  	// parameters:
   598  	// - name: id
   599  	//   in: path
   600  	//   description: id of the team
   601  	//   type: integer
   602  	//   format: int64
   603  	//   required: true
   604  	// - name: org
   605  	//   in: path
   606  	//   description: organization that owns the repo to list
   607  	//   type: string
   608  	//   required: true
   609  	// - name: repo
   610  	//   in: path
   611  	//   description: name of the repo to list
   612  	//   type: string
   613  	//   required: true
   614  	// responses:
   615  	//   "200":
   616  	//     "$ref": "#/responses/Repository"
   617  	//   "404":
   618  	//     "$ref": "#/responses/notFound"
   619  
   620  	repo := getRepositoryByParams(ctx)
   621  	if ctx.Written() {
   622  		return
   623  	}
   624  
   625  	if !organization.HasTeamRepo(ctx, ctx.Org.Team.OrgID, ctx.Org.Team.ID, repo.ID) {
   626  		ctx.NotFound()
   627  		return
   628  	}
   629  
   630  	permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
   631  	if err != nil {
   632  		ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
   633  		return
   634  	}
   635  
   636  	ctx.JSON(http.StatusOK, convert.ToRepo(ctx, repo, permission))
   637  }
   638  
   639  // getRepositoryByParams get repository by a team's organization ID and repo name
   640  func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
   641  	repo, err := repo_model.GetRepositoryByName(ctx.Org.Team.OrgID, ctx.Params(":reponame"))
   642  	if err != nil {
   643  		if repo_model.IsErrRepoNotExist(err) {
   644  			ctx.NotFound()
   645  		} else {
   646  			ctx.Error(http.StatusInternalServerError, "GetRepositoryByName", err)
   647  		}
   648  		return nil
   649  	}
   650  	return repo
   651  }
   652  
   653  // AddTeamRepository api for adding a repository to a team
   654  func AddTeamRepository(ctx *context.APIContext) {
   655  	// swagger:operation PUT /teams/{id}/repos/{org}/{repo} organization orgAddTeamRepository
   656  	// ---
   657  	// summary: Add a repository to a team
   658  	// produces:
   659  	// - application/json
   660  	// parameters:
   661  	// - name: id
   662  	//   in: path
   663  	//   description: id of the team
   664  	//   type: integer
   665  	//   format: int64
   666  	//   required: true
   667  	// - name: org
   668  	//   in: path
   669  	//   description: organization that owns the repo to add
   670  	//   type: string
   671  	//   required: true
   672  	// - name: repo
   673  	//   in: path
   674  	//   description: name of the repo to add
   675  	//   type: string
   676  	//   required: true
   677  	// responses:
   678  	//   "204":
   679  	//     "$ref": "#/responses/empty"
   680  	//   "403":
   681  	//     "$ref": "#/responses/forbidden"
   682  	//   "404":
   683  	//     "$ref": "#/responses/notFound"
   684  
   685  	repo := getRepositoryByParams(ctx)
   686  	if ctx.Written() {
   687  		return
   688  	}
   689  	if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
   690  		ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
   691  		return
   692  	} else if access < perm.AccessModeAdmin {
   693  		ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
   694  		return
   695  	}
   696  	if err := org_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
   697  		ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
   698  		return
   699  	}
   700  	ctx.Status(http.StatusNoContent)
   701  }
   702  
   703  // RemoveTeamRepository api for removing a repository from a team
   704  func RemoveTeamRepository(ctx *context.APIContext) {
   705  	// swagger:operation DELETE /teams/{id}/repos/{org}/{repo} organization orgRemoveTeamRepository
   706  	// ---
   707  	// summary: Remove a repository from a team
   708  	// description: This does not delete the repository, it only removes the
   709  	//              repository from the team.
   710  	// produces:
   711  	// - application/json
   712  	// parameters:
   713  	// - name: id
   714  	//   in: path
   715  	//   description: id of the team
   716  	//   type: integer
   717  	//   format: int64
   718  	//   required: true
   719  	// - name: org
   720  	//   in: path
   721  	//   description: organization that owns the repo to remove
   722  	//   type: string
   723  	//   required: true
   724  	// - name: repo
   725  	//   in: path
   726  	//   description: name of the repo to remove
   727  	//   type: string
   728  	//   required: true
   729  	// responses:
   730  	//   "204":
   731  	//     "$ref": "#/responses/empty"
   732  	//   "403":
   733  	//     "$ref": "#/responses/forbidden"
   734  	//   "404":
   735  	//     "$ref": "#/responses/notFound"
   736  
   737  	repo := getRepositoryByParams(ctx)
   738  	if ctx.Written() {
   739  		return
   740  	}
   741  	if access, err := access_model.AccessLevel(ctx, ctx.Doer, repo); err != nil {
   742  		ctx.Error(http.StatusInternalServerError, "AccessLevel", err)
   743  		return
   744  	} else if access < perm.AccessModeAdmin {
   745  		ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
   746  		return
   747  	}
   748  	if err := repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, repo.ID); err != nil {
   749  		ctx.Error(http.StatusInternalServerError, "RemoveRepository", err)
   750  		return
   751  	}
   752  	ctx.Status(http.StatusNoContent)
   753  }
   754  
   755  // SearchTeam api for searching teams
   756  func SearchTeam(ctx *context.APIContext) {
   757  	// swagger:operation GET /orgs/{org}/teams/search organization teamSearch
   758  	// ---
   759  	// summary: Search for teams within an organization
   760  	// produces:
   761  	// - application/json
   762  	// parameters:
   763  	// - name: org
   764  	//   in: path
   765  	//   description: name of the organization
   766  	//   type: string
   767  	//   required: true
   768  	// - name: q
   769  	//   in: query
   770  	//   description: keywords to search
   771  	//   type: string
   772  	// - name: include_desc
   773  	//   in: query
   774  	//   description: include search within team description (defaults to true)
   775  	//   type: boolean
   776  	// - name: page
   777  	//   in: query
   778  	//   description: page number of results to return (1-based)
   779  	//   type: integer
   780  	// - name: limit
   781  	//   in: query
   782  	//   description: page size of results
   783  	//   type: integer
   784  	// responses:
   785  	//   "200":
   786  	//     description: "SearchResults of a successful search"
   787  	//     schema:
   788  	//       type: object
   789  	//       properties:
   790  	//         ok:
   791  	//           type: boolean
   792  	//         data:
   793  	//           type: array
   794  	//           items:
   795  	//             "$ref": "#/definitions/Team"
   796  	//   "404":
   797  	//     "$ref": "#/responses/notFound"
   798  
   799  	listOptions := utils.GetListOptions(ctx)
   800  
   801  	opts := &organization.SearchTeamOptions{
   802  		Keyword:     ctx.FormTrim("q"),
   803  		OrgID:       ctx.Org.Organization.ID,
   804  		IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"),
   805  		ListOptions: listOptions,
   806  	}
   807  
   808  	// Only admin is allowd to search for all teams
   809  	if !ctx.Doer.IsAdmin {
   810  		opts.UserID = ctx.Doer.ID
   811  	}
   812  
   813  	teams, maxResults, err := organization.SearchTeam(ctx, opts)
   814  	if err != nil {
   815  		log.Error("SearchTeam failed: %v", err)
   816  		ctx.JSON(http.StatusInternalServerError, map[string]any{
   817  			"ok":    false,
   818  			"error": "SearchTeam internal failure",
   819  		})
   820  		return
   821  	}
   822  
   823  	apiTeams, err := convert.ToTeams(ctx, teams, false)
   824  	if err != nil {
   825  		ctx.InternalServerError(err)
   826  		return
   827  	}
   828  
   829  	ctx.SetLinkHeader(int(maxResults), listOptions.PageSize)
   830  	ctx.SetTotalCountHeader(maxResults)
   831  	ctx.JSON(http.StatusOK, map[string]any{
   832  		"ok":   true,
   833  		"data": apiTeams,
   834  	})
   835  }
   836  
   837  func ListTeamActivityFeeds(ctx *context.APIContext) {
   838  	// swagger:operation GET /teams/{id}/activities/feeds organization orgListTeamActivityFeeds
   839  	// ---
   840  	// summary: List a team's activity feeds
   841  	// produces:
   842  	// - application/json
   843  	// parameters:
   844  	// - name: id
   845  	//   in: path
   846  	//   description: id of the team
   847  	//   type: integer
   848  	//   format: int64
   849  	//   required: true
   850  	// - name: date
   851  	//   in: query
   852  	//   description: the date of the activities to be found
   853  	//   type: string
   854  	//   format: date
   855  	// - name: page
   856  	//   in: query
   857  	//   description: page number of results to return (1-based)
   858  	//   type: integer
   859  	// - name: limit
   860  	//   in: query
   861  	//   description: page size of results
   862  	//   type: integer
   863  	// responses:
   864  	//   "200":
   865  	//     "$ref": "#/responses/ActivityFeedsList"
   866  	//   "404":
   867  	//     "$ref": "#/responses/notFound"
   868  
   869  	listOptions := utils.GetListOptions(ctx)
   870  
   871  	opts := activities_model.GetFeedsOptions{
   872  		RequestedTeam:  ctx.Org.Team,
   873  		Actor:          ctx.Doer,
   874  		IncludePrivate: true,
   875  		Date:           ctx.FormString("date"),
   876  		ListOptions:    listOptions,
   877  	}
   878  
   879  	feeds, count, err := activities_model.GetFeeds(ctx, opts)
   880  	if err != nil {
   881  		ctx.Error(http.StatusInternalServerError, "GetFeeds", err)
   882  		return
   883  	}
   884  	ctx.SetTotalCountHeader(count)
   885  
   886  	ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
   887  }