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