初始提交: Gitea 项目代码
This commit is contained in:
@@ -0,0 +1,453 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
actions_model "gitea.dev/models/actions"
|
||||
"gitea.dev/models/db"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/util"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
actions_service "gitea.dev/services/actions"
|
||||
"gitea.dev/services/context"
|
||||
secret_service "gitea.dev/services/secrets"
|
||||
)
|
||||
|
||||
// create or update one secret of the user scope
|
||||
func CreateOrUpdateSecret(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /user/actions/secrets/{secretname} user updateUserSecret
|
||||
// ---
|
||||
// summary: Create or Update a secret value in a user scope
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: secretname
|
||||
// in: path
|
||||
// description: name of the secret
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateOrUpdateSecretOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when creating a secret
|
||||
// "204":
|
||||
// description: response when updating a secret
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateOrUpdateSecretOption)
|
||||
|
||||
_, created, err := secret_service.CreateOrUpdateSecret(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"), opt.Data, opt.Description)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if created {
|
||||
ctx.Status(http.StatusCreated)
|
||||
} else {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteSecret delete one secret of the user scope
|
||||
func DeleteSecret(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/secrets/{secretname} user deleteUserSecret
|
||||
// ---
|
||||
// summary: Delete a secret in a user scope
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: secretname
|
||||
// in: path
|
||||
// description: name of the secret
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: delete one secret of the user
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := secret_service.DeleteSecretByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("secretname"))
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// CreateVariable create a user-level variable
|
||||
func CreateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/actions/variables/{variablename} user createUserVariable
|
||||
// ---
|
||||
// summary: Create a user-level variable
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: successfully created the user-level variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "409":
|
||||
// description: variable name already exists.
|
||||
|
||||
opt := web.GetForm(ctx).(*api.CreateVariableOption)
|
||||
|
||||
ownerID := ctx.Doer.ID
|
||||
variableName := ctx.PathParam("variablename")
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ownerID,
|
||||
Name: variableName,
|
||||
})
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if v != nil && v.ID > 0 {
|
||||
ctx.APIError(http.StatusConflict, util.NewAlreadyExistErrorf("variable name %s already exists", variableName))
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := actions_service.CreateVariable(ctx, ownerID, 0, variableName, opt.Value, opt.Description); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusCreated)
|
||||
}
|
||||
|
||||
// UpdateVariable update a user-level variable which is created by current doer
|
||||
func UpdateVariable(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /user/actions/variables/{variablename} user updateUserVariable
|
||||
// ---
|
||||
// summary: Update a user-level variable which is created by current doer
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateVariableOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when updating a variable
|
||||
// "204":
|
||||
// description: response when updating a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
opt := web.GetForm(ctx).(*api.UpdateVariableOption)
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Name: ctx.PathParam("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if opt.Name == "" {
|
||||
opt.Name = ctx.PathParam("variablename")
|
||||
}
|
||||
|
||||
v.Name = opt.Name
|
||||
v.Data = opt.Value
|
||||
v.Description = opt.Description
|
||||
|
||||
if _, err := actions_service.UpdateVariableNameData(ctx, v); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeleteVariable delete a user-level variable which is created by current doer
|
||||
func DeleteVariable(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/variables/{variablename} user deleteUserVariable
|
||||
// ---
|
||||
// summary: Delete a user-level variable which is created by current doer
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "201":
|
||||
// description: response when deleting a variable
|
||||
// "204":
|
||||
// description: response when deleting a variable
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := actions_service.DeleteVariableByName(ctx, ctx.Doer.ID, 0, ctx.PathParam("variablename")); err != nil {
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetVariable get a user-level variable which is created by current doer
|
||||
func GetVariable(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/variables/{variablename} user getUserVariable
|
||||
// ---
|
||||
// summary: Get a user-level variable which is created by current doer
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: variablename
|
||||
// in: path
|
||||
// description: name of the variable
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActionVariable"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
v, err := actions_service.GetVariable(ctx, actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Name: ctx.PathParam("variablename"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
variable := &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
Description: v.Description,
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, variable)
|
||||
}
|
||||
|
||||
// ListVariables list user-level variables
|
||||
func ListVariables(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/variables user getUserVariablesList
|
||||
// ---
|
||||
// summary: Get the user-level list of variables which is created by current doer
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/VariableList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
vars, count, err := db.FindAndCount[actions_model.ActionVariable](ctx, &actions_model.FindVariablesOpts{
|
||||
OwnerID: ctx.Doer.ID,
|
||||
ListOptions: listOptions,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
variables := make([]*api.ActionVariable, len(vars))
|
||||
for i, v := range vars {
|
||||
variables[i] = &api.ActionVariable{
|
||||
OwnerID: v.OwnerID,
|
||||
RepoID: v.RepoID,
|
||||
Name: v.Name,
|
||||
Data: v.Data,
|
||||
Description: v.Description,
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, variables)
|
||||
}
|
||||
|
||||
// ListWorkflowRuns lists workflow runs
|
||||
func ListWorkflowRuns(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runs user getUserWorkflowRuns
|
||||
// ---
|
||||
// summary: Get workflow runs
|
||||
// parameters:
|
||||
// - name: event
|
||||
// in: query
|
||||
// description: workflow event name
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: branch
|
||||
// in: query
|
||||
// description: workflow branch
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: actor
|
||||
// in: query
|
||||
// description: triggered by user
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: head_sha
|
||||
// in: query
|
||||
// description: triggering sha of the workflow run
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowRunsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRuns(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// ListWorkflowJobs lists workflow jobs
|
||||
func ListWorkflowJobs(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/jobs user getUserWorkflowJobs
|
||||
// ---
|
||||
// summary: Get workflow jobs
|
||||
// parameters:
|
||||
// - name: status
|
||||
// in: query
|
||||
// description: workflow status (pending, queued, in_progress, failure, success, skipped)
|
||||
// type: string
|
||||
// required: false
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// - name: sort
|
||||
// in: query
|
||||
// description: sort jobs by attribute. Supported values are "id". Default is "id"
|
||||
// type: string
|
||||
// - name: order
|
||||
// in: query
|
||||
// description: sort order, either "asc" (ascending) or "desc" (descending). Default is "asc"
|
||||
// type: string
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WorkflowJobsList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
shared.ListJobs(ctx, ctx.Doer.ID, 0, 0, nil)
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
auth_model "gitea.dev/models/auth"
|
||||
"gitea.dev/models/db"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
"gitea.dev/services/forms"
|
||||
)
|
||||
|
||||
// ListAccessTokens list all the access tokens
|
||||
func ListAccessTokens(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/tokens user userGetTokens
|
||||
// ---
|
||||
// summary: List the authenticated user's access tokens
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of to user whose access tokens are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/AccessTokenList"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
opts := auth_model.ListAccessTokensOptions{UserID: ctx.ContextUser.ID, ListOptions: utils.GetListOptions(ctx)}
|
||||
|
||||
tokens, count, err := db.FindAndCount[auth_model.AccessToken](ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiTokens := make([]*api.AccessToken, len(tokens))
|
||||
for i := range tokens {
|
||||
apiTokens[i] = &api.AccessToken{
|
||||
ID: tokens[i].ID,
|
||||
Name: tokens[i].Name,
|
||||
TokenLastEight: tokens[i].TokenLastEight,
|
||||
Scopes: tokens[i].Scope.StringSlice(),
|
||||
Created: tokens[i].CreatedUnix.AsTime(),
|
||||
Updated: tokens[i].UpdatedUnix.AsTime(),
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &apiTokens)
|
||||
}
|
||||
|
||||
// CreateAccessToken create access tokens
|
||||
func CreateAccessToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /users/{username}/tokens user userCreateToken
|
||||
// ---
|
||||
// summary: Create an access token
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose token is to be created
|
||||
// required: true
|
||||
// type: string
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateAccessTokenOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/AccessToken"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateAccessTokenOption)
|
||||
|
||||
t := &auth_model.AccessToken{
|
||||
UID: ctx.ContextUser.ID,
|
||||
Name: form.Name,
|
||||
}
|
||||
|
||||
exist, err := auth_model.AccessTokenByNameExists(ctx, t)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
ctx.APIError(http.StatusBadRequest, errors.New("access token name has been used already"))
|
||||
return
|
||||
}
|
||||
|
||||
scope, err := auth_model.AccessTokenScope(strings.Join(form.Scopes, ",")).Normalize()
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, fmt.Errorf("invalid access token scope provided: %w", err))
|
||||
return
|
||||
}
|
||||
if scope == "" {
|
||||
ctx.APIError(http.StatusBadRequest, "access token must have a scope")
|
||||
return
|
||||
}
|
||||
t.Scope = scope
|
||||
|
||||
if err := auth_model.NewAccessToken(ctx, t); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, &api.AccessToken{
|
||||
Name: t.Name,
|
||||
Token: t.Token,
|
||||
ID: t.ID,
|
||||
TokenLastEight: t.TokenLastEight,
|
||||
Scopes: t.Scope.StringSlice(),
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteAccessToken delete access tokens
|
||||
func DeleteAccessToken(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /users/{username}/tokens/{token} user userDeleteAccessToken
|
||||
// ---
|
||||
// summary: delete an access token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose token is to be deleted
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: token
|
||||
// in: path
|
||||
// description: token to be deleted, identified by ID and if not available by name
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/error"
|
||||
|
||||
token := ctx.PathParam("id")
|
||||
tokenID, _ := strconv.ParseInt(token, 0, 64)
|
||||
|
||||
if tokenID == 0 {
|
||||
tokens, err := db.Find[auth_model.AccessToken](ctx, auth_model.ListAccessTokensOptions{
|
||||
Name: token,
|
||||
UserID: ctx.ContextUser.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
switch len(tokens) {
|
||||
case 0:
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
case 1:
|
||||
tokenID = tokens[0].ID
|
||||
default:
|
||||
ctx.APIError(http.StatusUnprocessableEntity, fmt.Errorf("multiple matches for token name '%s'", token))
|
||||
return
|
||||
}
|
||||
}
|
||||
if tokenID == 0 {
|
||||
ctx.APIErrorInternal(nil)
|
||||
return
|
||||
}
|
||||
|
||||
if err := auth_model.DeleteAccessTokenByID(ctx, tokenID, ctx.ContextUser.ID); err != nil {
|
||||
if auth_model.IsErrAccessTokenNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// CreateOauth2Application is the handler to create a new OAuth2 Application for the authenticated user
|
||||
func CreateOauth2Application(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/applications/oauth2 user userCreateOAuth2Application
|
||||
// ---
|
||||
// summary: creates a new OAuth2 application
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateOAuth2ApplicationOptions"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/OAuth2Application"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
|
||||
data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
|
||||
if invalidURI := forms.DetectInvalidOAuth2ApplicationRedirectURI(data.RedirectURIs); invalidURI != "" {
|
||||
ctx.APIError(http.StatusBadRequest, "invalid redirect URI: "+invalidURI)
|
||||
return
|
||||
}
|
||||
app, err := auth_model.CreateOAuth2Application(ctx, auth_model.CreateOAuth2ApplicationOptions{
|
||||
Name: data.Name,
|
||||
UserID: ctx.Doer.ID,
|
||||
RedirectURIs: data.RedirectURIs,
|
||||
ConfidentialClient: data.ConfidentialClient,
|
||||
SkipSecondaryAuthorization: data.SkipSecondaryAuthorization,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, "error creating oauth2 application")
|
||||
return
|
||||
}
|
||||
secret, err := app.GenerateClientSecret(ctx)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, "error creating application secret")
|
||||
return
|
||||
}
|
||||
app.ClientSecret = secret
|
||||
|
||||
ctx.JSON(http.StatusCreated, convert.ToOAuth2Application(app))
|
||||
}
|
||||
|
||||
// ListOauth2Applications list all the Oauth2 application
|
||||
func ListOauth2Applications(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/applications/oauth2 user userGetOauth2Application
|
||||
// ---
|
||||
// summary: List the authenticated user's oauth2 applications
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/OAuth2ApplicationList"
|
||||
|
||||
apps, total, err := db.FindAndCount[auth_model.OAuth2Application](ctx, auth_model.FindOAuth2ApplicationsOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
OwnerID: ctx.Doer.ID,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiApps := make([]*api.OAuth2Application, len(apps))
|
||||
for i := range apps {
|
||||
apiApps[i] = convert.ToOAuth2Application(apps[i])
|
||||
apiApps[i].ClientSecret = "" // Hide secret on application list
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &apiApps)
|
||||
}
|
||||
|
||||
// DeleteOauth2Application delete OAuth2 Application
|
||||
func DeleteOauth2Application(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/applications/oauth2/{id} user userDeleteOAuth2Application
|
||||
// ---
|
||||
// summary: delete an OAuth2 Application
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: token to be deleted
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
appID := ctx.PathParamInt64("id")
|
||||
if err := auth_model.DeleteOAuth2Application(ctx, appID, ctx.Doer.ID); err != nil {
|
||||
if auth_model.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetOauth2Application get OAuth2 Application
|
||||
func GetOauth2Application(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/applications/oauth2/{id} user userGetOAuth2Application
|
||||
// ---
|
||||
// summary: get an OAuth2 Application
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: Application ID to be found
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/OAuth2Application"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
appID := ctx.PathParamInt64("id")
|
||||
app, err := auth_model.GetOAuth2ApplicationByID(ctx, appID)
|
||||
if err != nil {
|
||||
if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if app.UID != ctx.Doer.ID {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
app.ClientSecret = ""
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app))
|
||||
}
|
||||
|
||||
// UpdateOauth2Application update OAuth2 Application
|
||||
func UpdateOauth2Application(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /user/applications/oauth2/{id} user userUpdateOAuth2Application
|
||||
// ---
|
||||
// summary: update an OAuth2 Application, this includes regenerating the client secret
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: application to be updated
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateOAuth2ApplicationOptions"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/OAuth2Application"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
appID := ctx.PathParamInt64("id")
|
||||
|
||||
data := web.GetForm(ctx).(*api.CreateOAuth2ApplicationOptions)
|
||||
if invalidURI := forms.DetectInvalidOAuth2ApplicationRedirectURI(data.RedirectURIs); invalidURI != "" {
|
||||
ctx.APIError(http.StatusBadRequest, "invalid redirect URI: "+invalidURI)
|
||||
return
|
||||
}
|
||||
|
||||
app, err := auth_model.UpdateOAuth2Application(ctx, auth_model.UpdateOAuth2ApplicationOptions{
|
||||
Name: data.Name,
|
||||
UserID: ctx.Doer.ID,
|
||||
ID: appID,
|
||||
RedirectURIs: data.RedirectURIs,
|
||||
ConfidentialClient: data.ConfidentialClient,
|
||||
SkipSecondaryAuthorization: data.SkipSecondaryAuthorization,
|
||||
})
|
||||
if err != nil {
|
||||
if auth_model.IsErrOauthClientIDInvalid(err) || auth_model.IsErrOAuthApplicationNotFound(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
app.ClientSecret, err = app.GenerateClientSecret(ctx)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, "error updating application secret")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToOAuth2Application(app))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
// UpdateAvatar updates the Avatar of a User
|
||||
func UpdateAvatar(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/avatar user userUpdateAvatar
|
||||
// ---
|
||||
// summary: Update Avatar
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UpdateUserAvatarOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
form := web.GetForm(ctx).(*api.UpdateUserAvatarOption)
|
||||
|
||||
content, err := base64.StdEncoding.DecodeString(form.Image)
|
||||
if err != nil {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = user_service.UploadAvatar(ctx, ctx.Doer, content)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// DeleteAvatar deletes the Avatar of a User
|
||||
func DeleteAvatar(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/avatar user userDeleteAvatar
|
||||
// ---
|
||||
// summary: Delete Avatar
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
err := user_service.DeleteAvatar(ctx, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2024 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
func ListBlocks(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/blocks user userListBlocks
|
||||
// ---
|
||||
// summary: List users blocked by the authenticated user
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
|
||||
shared.ListBlocks(ctx, ctx.Doer)
|
||||
}
|
||||
|
||||
func CheckUserBlock(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/blocks/{username} user userCheckUserBlock
|
||||
// ---
|
||||
// summary: Check if a user is blocked by the authenticated user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to check
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
shared.CheckUserBlock(ctx, ctx.Doer)
|
||||
}
|
||||
|
||||
func BlockUser(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /user/blocks/{username} user userBlockUser
|
||||
// ---
|
||||
// summary: Block a user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to block
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: note
|
||||
// in: query
|
||||
// description: optional note for the block
|
||||
// type: string
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
shared.BlockUser(ctx, ctx.Doer)
|
||||
}
|
||||
|
||||
func UnblockUser(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/blocks/{username} user userUnblockUser
|
||||
// ---
|
||||
// summary: Unblock a user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to unblock
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
shared.UnblockUser(ctx, ctx.Doer, ctx.Doer)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
// ListEmails list all of the authenticated user's email addresses
|
||||
// see https://github.com/gogits/go-gogs-client/wiki/Users-Emails#list-email-addresses-for-a-user
|
||||
func ListEmails(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/emails user userListEmails
|
||||
// ---
|
||||
// summary: List the authenticated user's email addresses
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/EmailList"
|
||||
|
||||
emails, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
apiEmails := make([]*api.Email, len(emails))
|
||||
for i := range emails {
|
||||
apiEmails[i] = convert.ToEmail(emails[i])
|
||||
}
|
||||
ctx.JSON(http.StatusOK, &apiEmails)
|
||||
}
|
||||
|
||||
// AddEmail add an email address
|
||||
func AddEmail(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/emails user userAddEmail
|
||||
// ---
|
||||
// summary: Add email addresses
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateEmailOption"
|
||||
// responses:
|
||||
// '201':
|
||||
// "$ref": "#/responses/EmailList"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateEmailOption)
|
||||
if len(form.Emails) == 0 {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Email list empty")
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_service.AddEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
|
||||
if user_model.IsErrEmailAlreadyUsed(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "Email address has been used: "+err.(user_model.ErrEmailAlreadyUsed).Email)
|
||||
} else if user_model.IsErrEmailCharIsNotSupported(err) || user_model.IsErrEmailInvalid(err) {
|
||||
email := ""
|
||||
if typedError, ok := err.(user_model.ErrEmailInvalid); ok {
|
||||
email = typedError.Email
|
||||
}
|
||||
if typedError, ok := err.(user_model.ErrEmailCharIsNotSupported); ok {
|
||||
email = typedError.Email
|
||||
}
|
||||
|
||||
errMsg := fmt.Sprintf("Email address %q invalid", email)
|
||||
ctx.APIError(http.StatusUnprocessableEntity, errMsg)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
emails, err := user_model.GetEmailAddresses(ctx, ctx.Doer.ID)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiEmails := make([]*api.Email, 0, len(emails))
|
||||
for _, email := range emails {
|
||||
apiEmails = append(apiEmails, convert.ToEmail(email))
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, apiEmails)
|
||||
}
|
||||
|
||||
// DeleteEmail delete email
|
||||
func DeleteEmail(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/emails user userDeleteEmail
|
||||
// ---
|
||||
// summary: Delete email addresses
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/DeleteEmailOption"
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
form := web.GetForm(ctx).(*api.DeleteEmailOption)
|
||||
if len(form.Emails) == 0 {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
if err := user_service.DeleteEmailAddresses(ctx, ctx.Doer, form.Emails); err != nil {
|
||||
if user_model.IsErrEmailAddressNotExist(err) {
|
||||
ctx.APIError(http.StatusNotFound, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
|
||||
apiUsers := make([]*api.User, len(users))
|
||||
for i := range users {
|
||||
apiUsers[i] = convert.ToUser(ctx, users[i], ctx.Doer)
|
||||
}
|
||||
ctx.JSON(http.StatusOK, &apiUsers)
|
||||
}
|
||||
|
||||
func listUserFollowers(ctx *context.APIContext, u *user_model.User) {
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
responseAPIUsers(ctx, users)
|
||||
}
|
||||
|
||||
// ListMyFollowers list the authenticated user's followers
|
||||
func ListMyFollowers(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/followers user userCurrentListFollowers
|
||||
// ---
|
||||
// summary: List the authenticated user's followers
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
|
||||
listUserFollowers(ctx, ctx.Doer)
|
||||
}
|
||||
|
||||
// ListFollowers list the given user's followers
|
||||
func ListFollowers(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/followers user userListFollowers
|
||||
// ---
|
||||
// summary: List the given user's followers
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose followers are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listUserFollowers(ctx, ctx.ContextUser)
|
||||
}
|
||||
|
||||
func listUserFollowing(ctx *context.APIContext, u *user_model.User) {
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, listOptions)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
responseAPIUsers(ctx, users)
|
||||
}
|
||||
|
||||
// ListMyFollowing list the users that the authenticated user is following
|
||||
func ListMyFollowing(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/following user userCurrentListFollowing
|
||||
// ---
|
||||
// summary: List the users that the authenticated user is following
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
|
||||
listUserFollowing(ctx, ctx.Doer)
|
||||
}
|
||||
|
||||
// ListFollowing list the users that the given user is following
|
||||
func ListFollowing(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/following user userListFollowing
|
||||
// ---
|
||||
// summary: List the users that the given user is following
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose followed users are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listUserFollowing(ctx, ctx.ContextUser)
|
||||
}
|
||||
|
||||
func checkUserFollowing(ctx *context.APIContext, u *user_model.User, followID int64) {
|
||||
if user_model.IsFollowing(ctx, u.ID, followID) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
// CheckMyFollowing whether the given user is followed by the authenticated user
|
||||
func CheckMyFollowing(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/following/{username} user userCurrentCheckFollowing
|
||||
// ---
|
||||
// summary: Check whether a user is followed by the authenticated user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to check for authenticated followers
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
checkUserFollowing(ctx, ctx.Doer, ctx.ContextUser.ID)
|
||||
}
|
||||
|
||||
// CheckFollowing check if one user is following another user
|
||||
func CheckFollowing(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/following/{target} user userCheckFollowing
|
||||
// ---
|
||||
// summary: Check if one user is following another user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the following user
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: target
|
||||
// in: path
|
||||
// description: username of the followed user
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
target := GetUserByPathParam(ctx, "target") // FIXME: it is not right to call this function, it should load the "target" directly
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
checkUserFollowing(ctx, ctx.ContextUser, target.ID)
|
||||
}
|
||||
|
||||
// Follow follow a user
|
||||
func Follow(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /user/following/{username} user userCurrentPutFollow
|
||||
// ---
|
||||
// summary: Follow a user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to follow
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := user_model.FollowUser(ctx, ctx.Doer, ctx.ContextUser); err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Unfollow unfollow a user
|
||||
func Unfollow(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/following/{username} user userCurrentDeleteFollow
|
||||
// ---
|
||||
// summary: Unfollow a user
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user to unfollow
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if err := user_model.UnfollowUser(ctx, ctx.Doer.ID, ctx.ContextUser.ID); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/db"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
|
||||
keys, total, err := db.FindAndCount[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
|
||||
ListOptions: listOptions,
|
||||
OwnerID: uid,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiKeys := make([]*api.GPGKey, len(keys))
|
||||
for i := range keys {
|
||||
apiKeys[i] = convert.ToGPGKey(keys[i])
|
||||
}
|
||||
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &apiKeys)
|
||||
}
|
||||
|
||||
// ListGPGKeys get the GPG key list of a user
|
||||
func ListGPGKeys(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/gpg_keys user userListGPGKeys
|
||||
// ---
|
||||
// summary: List the given user's GPG keys
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose GPG key list is to be obtained
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/GPGKeyList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listGPGKeys(ctx, ctx.ContextUser.ID, utils.GetListOptions(ctx))
|
||||
}
|
||||
|
||||
// ListMyGPGKeys get the GPG key list of the authenticated user
|
||||
func ListMyGPGKeys(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/gpg_keys user userCurrentListGPGKeys
|
||||
// ---
|
||||
// summary: List the authenticated user's GPG keys
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/GPGKeyList"
|
||||
|
||||
listGPGKeys(ctx, ctx.Doer.ID, utils.GetListOptions(ctx))
|
||||
}
|
||||
|
||||
// GetGPGKey get the GPG key based on a id
|
||||
func GetGPGKey(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/gpg_keys/{id} user userCurrentGetGPGKey
|
||||
// ---
|
||||
// summary: Get a GPG key
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of key to get
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/GPGKey"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err := key.LoadSubKeys(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
|
||||
}
|
||||
|
||||
// CreateUserGPGKey creates new GPG key to given user by ID.
|
||||
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
||||
token := asymkey_model.VerificationToken(ctx.Doer, 1)
|
||||
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
|
||||
|
||||
keys, err := asymkey_model.AddGPGKey(ctx, uid, form.ArmoredKey, token, form.Signature)
|
||||
if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
|
||||
keys, err = asymkey_model.AddGPGKey(ctx, uid, form.ArmoredKey, lastToken, form.Signature)
|
||||
}
|
||||
if err != nil {
|
||||
HandleAddGPGKeyError(ctx, err, token)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, convert.ToGPGKey(keys[0]))
|
||||
}
|
||||
|
||||
// GetVerificationToken returns the current token to be signed for this user
|
||||
func GetVerificationToken(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/gpg_key_token user getVerificationToken
|
||||
// ---
|
||||
// summary: Get a Token to verify
|
||||
// produces:
|
||||
// - text/plain
|
||||
// parameters:
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/string"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
token := asymkey_model.VerificationToken(ctx.Doer, 1)
|
||||
ctx.PlainText(http.StatusOK, token)
|
||||
}
|
||||
|
||||
// VerifyUserGPGKey creates new GPG key to given user by ID.
|
||||
func VerifyUserGPGKey(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/gpg_key_verify user userVerifyGPGKey
|
||||
// ---
|
||||
// summary: Verify a GPG key
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/GPGKey"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.VerifyGPGKeyOption)
|
||||
token := asymkey_model.VerificationToken(ctx.Doer, 1)
|
||||
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
|
||||
|
||||
form.KeyID = strings.TrimLeft(form.KeyID, "0")
|
||||
if form.KeyID == "" {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
_, err := asymkey_model.VerifyGPGKey(ctx, ctx.Doer.ID, form.KeyID, token, form.Signature)
|
||||
if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
|
||||
_, err = asymkey_model.VerifyGPGKey(ctx, ctx.Doer.ID, form.KeyID, lastToken, form.Signature)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrGPGInvalidTokenSignature(err) {
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: "+token)
|
||||
return
|
||||
}
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
|
||||
KeyID: form.KeyID,
|
||||
IncludeSubKeys: true,
|
||||
})
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToGPGKey(keys[0]))
|
||||
}
|
||||
|
||||
// swagger:parameters userCurrentPostGPGKey
|
||||
type swaggerUserCurrentPostGPGKey struct {
|
||||
// in:body
|
||||
Form api.CreateGPGKeyOption
|
||||
}
|
||||
|
||||
// CreateGPGKey create a GPG key belonging to the authenticated user
|
||||
func CreateGPGKey(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/gpg_keys user userCurrentPostGPGKey
|
||||
// ---
|
||||
// summary: Create a GPG key
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/GPGKey"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateGPGKeyOption)
|
||||
CreateUserGPGKey(ctx, *form, ctx.Doer.ID)
|
||||
}
|
||||
|
||||
// DeleteGPGKey remove a GPG key belonging to the authenticated user
|
||||
func DeleteGPGKey(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/gpg_keys/{id} user userCurrentDeleteGPGKey
|
||||
// ---
|
||||
// summary: Remove a GPG key
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of key to delete
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
|
||||
ctx.APIErrorNotFound("Not Found", errors.New("gpg keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
||||
if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.PathParamInt64("id")); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// HandleAddGPGKeyError handle add GPGKey error
|
||||
func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) {
|
||||
switch {
|
||||
case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "A key with the same id already exists")
|
||||
case asymkey_model.IsErrGPGKeyParsing(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, err)
|
||||
case asymkey_model.IsErrGPGNoEmailFound(err):
|
||||
ctx.APIError(http.StatusNotFound, "None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: "+token)
|
||||
case asymkey_model.IsErrGPGInvalidTokenSignature(err):
|
||||
ctx.APIError(http.StatusUnprocessableEntity, "The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: "+token)
|
||||
default:
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright 2021 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// GetUserByPathParam get user by the path param name
|
||||
// it will redirect to the user's new name if the user's name has been changed
|
||||
func GetUserByPathParam(ctx *context.APIContext, name string) *user_model.User {
|
||||
username := ctx.PathParam(name)
|
||||
user, err := user_model.GetUserByName(ctx, username)
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
if redirectUserID, err2 := user_model.LookupUserRedirect(ctx, username); err2 == nil {
|
||||
context.RedirectToUser(ctx.Base, ctx.Doer, username, redirectUserID)
|
||||
} else {
|
||||
ctx.APIErrorNotFound("GetUserByName", err)
|
||||
}
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
// GetContextUserByPathParam returns user whose name is presented in URL (path param "username").
|
||||
func GetContextUserByPathParam(ctx *context.APIContext) *user_model.User {
|
||||
return GetUserByPathParam(ctx, "username")
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
webhook_service "gitea.dev/services/webhook"
|
||||
)
|
||||
|
||||
// ListHooks list the authenticated user's webhooks
|
||||
func ListHooks(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/hooks user userListHooks
|
||||
// ---
|
||||
// summary: List the authenticated user's webhooks
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/HookList"
|
||||
|
||||
utils.ListOwnerHooks(
|
||||
ctx,
|
||||
ctx.Doer,
|
||||
)
|
||||
}
|
||||
|
||||
// GetHook get the authenticated user's hook by id
|
||||
func GetHook(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/hooks/{id} user userGetHook
|
||||
// ---
|
||||
// summary: Get a hook
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the hook to get
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Hook"
|
||||
|
||||
hook, err := utils.GetOwnerHook(ctx, ctx.Doer.ID, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !ctx.Doer.IsAdmin && hook.OwnerID != ctx.Doer.ID {
|
||||
ctx.APIErrorNotFound()
|
||||
return
|
||||
}
|
||||
|
||||
apiHook, err := webhook_service.ToHook(ctx.Doer.HomeLink(), hook)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, apiHook)
|
||||
}
|
||||
|
||||
// CreateHook create a hook for the authenticated user
|
||||
func CreateHook(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/hooks user userCreateHook
|
||||
// ---
|
||||
// summary: Create a hook
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// required: true
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateHookOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/Hook"
|
||||
|
||||
utils.AddOwnerHook(
|
||||
ctx,
|
||||
ctx.Doer,
|
||||
web.GetForm(ctx).(*api.CreateHookOption),
|
||||
)
|
||||
}
|
||||
|
||||
// EditHook modify a hook of the authenticated user
|
||||
func EditHook(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /user/hooks/{id} user userEditHook
|
||||
// ---
|
||||
// summary: Update a hook
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the hook to update
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditHookOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Hook"
|
||||
|
||||
utils.EditOwnerHook(
|
||||
ctx,
|
||||
ctx.Doer,
|
||||
web.GetForm(ctx).(*api.EditHookOption),
|
||||
ctx.PathParamInt64("id"),
|
||||
)
|
||||
}
|
||||
|
||||
// DeleteHook delete a hook of the authenticated user
|
||||
func DeleteHook(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/hooks/{id} user userDeleteHook
|
||||
// ---
|
||||
// summary: Delete a hook
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of the hook to delete
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
|
||||
utils.DeleteOwnerHook(
|
||||
ctx,
|
||||
ctx.Doer,
|
||||
ctx.PathParamInt64("id"),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
std_ctx "context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
asymkey_model "gitea.dev/models/asymkey"
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/models/perm"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/modules/setting"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/routers/api/v1/repo"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
asymkey_service "gitea.dev/services/asymkey"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// appendPrivateInformation appends the owner and key type information to api.PublicKey
|
||||
func appendPrivateInformation(ctx std_ctx.Context, apiKey *api.PublicKey, key *asymkey_model.PublicKey, defaultUser *user_model.User) (*api.PublicKey, error) {
|
||||
switch key.Type {
|
||||
case asymkey_model.KeyTypeDeploy:
|
||||
apiKey.KeyType = "deploy"
|
||||
case asymkey_model.KeyTypeUser:
|
||||
apiKey.KeyType = "user"
|
||||
|
||||
if defaultUser.ID == key.OwnerID {
|
||||
apiKey.Owner = convert.ToUser(ctx, defaultUser, defaultUser)
|
||||
} else {
|
||||
user, err := user_model.GetUserByID(ctx, key.OwnerID)
|
||||
if err != nil {
|
||||
return apiKey, err
|
||||
}
|
||||
apiKey.Owner = convert.ToUser(ctx, user, user)
|
||||
}
|
||||
default:
|
||||
apiKey.KeyType = "unknown"
|
||||
}
|
||||
apiKey.ReadOnly = key.Mode == perm.AccessModeRead
|
||||
return apiKey, nil
|
||||
}
|
||||
|
||||
func composePublicKeysAPILink() string {
|
||||
return setting.AppURL + "api/v1/user/keys/"
|
||||
}
|
||||
|
||||
func listPublicKeys(ctx *context.APIContext, user *user_model.User) {
|
||||
var keys []*asymkey_model.PublicKey
|
||||
var err error
|
||||
var count int64
|
||||
|
||||
fingerprint := ctx.FormString("fingerprint")
|
||||
username := ctx.PathParam("username")
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
if fingerprint != "" {
|
||||
var userID int64 // Unrestricted
|
||||
// Querying not just listing
|
||||
if username != "" {
|
||||
// Restrict to provided uid
|
||||
userID = user.ID
|
||||
}
|
||||
keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
|
||||
ListOptions: listOptions,
|
||||
OwnerID: userID,
|
||||
Fingerprint: fingerprint,
|
||||
})
|
||||
} else {
|
||||
// Use ListPublicKeys
|
||||
keys, count, err = db.FindAndCount[asymkey_model.PublicKey](ctx, asymkey_model.FindPublicKeyOptions{
|
||||
ListOptions: listOptions,
|
||||
OwnerID: user.ID,
|
||||
NotKeytype: asymkey_model.KeyTypePrincipal,
|
||||
})
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiLink := composePublicKeysAPILink()
|
||||
apiKeys := make([]*api.PublicKey, len(keys))
|
||||
for i := range keys {
|
||||
apiKeys[i] = convert.ToPublicKey(apiLink, keys[i])
|
||||
if ctx.Doer.IsAdmin || ctx.Doer.ID == keys[i].OwnerID {
|
||||
apiKeys[i], _ = appendPrivateInformation(ctx, apiKeys[i], keys[i], user)
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &apiKeys)
|
||||
}
|
||||
|
||||
// ListMyPublicKeys list all of the authenticated user's public keys
|
||||
func ListMyPublicKeys(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/keys user userCurrentListKeys
|
||||
// ---
|
||||
// summary: List the authenticated user's public keys
|
||||
// parameters:
|
||||
// - name: fingerprint
|
||||
// in: query
|
||||
// description: fingerprint of the key
|
||||
// type: string
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PublicKeyList"
|
||||
|
||||
listPublicKeys(ctx, ctx.Doer)
|
||||
}
|
||||
|
||||
// ListPublicKeys list the given user's public keys
|
||||
func ListPublicKeys(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/keys user userListKeys
|
||||
// ---
|
||||
// summary: List the given user's public keys
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose public keys are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: fingerprint
|
||||
// in: query
|
||||
// description: fingerprint of the key
|
||||
// type: string
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PublicKeyList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listPublicKeys(ctx, ctx.ContextUser)
|
||||
}
|
||||
|
||||
// GetPublicKey get a public key
|
||||
func GetPublicKey(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/keys/{id} user userCurrentGetKey
|
||||
// ---
|
||||
// summary: Get a public key
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of key to get
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/PublicKey"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
key, err := asymkey_model.GetPublicKeyByID(ctx, ctx.PathParamInt64("id"))
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrKeyNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
apiLink := composePublicKeysAPILink()
|
||||
apiKey := convert.ToPublicKey(apiLink, key)
|
||||
if ctx.Doer.IsAdmin || ctx.Doer.ID == key.OwnerID {
|
||||
apiKey, _ = appendPrivateInformation(ctx, apiKey, key, ctx.Doer)
|
||||
}
|
||||
ctx.JSON(http.StatusOK, apiKey)
|
||||
}
|
||||
|
||||
// CreateUserPublicKey creates new public key to given user by ID.
|
||||
func CreateUserPublicKey(ctx *context.APIContext, form api.CreateKeyOption, uid int64) {
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
||||
content, err := asymkey_model.CheckPublicKeyString(form.Key)
|
||||
if err != nil {
|
||||
repo.HandleCheckKeyStringError(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
key, err := asymkey_model.AddPublicKey(ctx, uid, form.Title, content, 0, false)
|
||||
if err != nil {
|
||||
repo.HandleAddKeyError(ctx, err)
|
||||
return
|
||||
}
|
||||
apiLink := composePublicKeysAPILink()
|
||||
apiKey := convert.ToPublicKey(apiLink, key)
|
||||
if ctx.Doer.IsAdmin || ctx.Doer.ID == key.OwnerID {
|
||||
apiKey, _ = appendPrivateInformation(ctx, apiKey, key, ctx.Doer)
|
||||
}
|
||||
ctx.JSON(http.StatusCreated, apiKey)
|
||||
}
|
||||
|
||||
// CreatePublicKey create one public key for me
|
||||
func CreatePublicKey(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/keys user userCurrentPostKey
|
||||
// ---
|
||||
// summary: Create a public key
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/CreateKeyOption"
|
||||
// responses:
|
||||
// "201":
|
||||
// "$ref": "#/responses/PublicKey"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
|
||||
form := web.GetForm(ctx).(*api.CreateKeyOption)
|
||||
CreateUserPublicKey(ctx, *form, ctx.Doer.ID)
|
||||
}
|
||||
|
||||
// DeletePublicKey delete one public key
|
||||
func DeletePublicKey(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/keys/{id} user userCurrentDeleteKey
|
||||
// ---
|
||||
// summary: Delete a public key
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: id
|
||||
// in: path
|
||||
// description: id of key to delete
|
||||
// type: integer
|
||||
// format: int64
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageSSHKeys) {
|
||||
ctx.APIErrorNotFound("Not Found", errors.New("ssh keys setting is not allowed to be visited"))
|
||||
return
|
||||
}
|
||||
|
||||
id := ctx.PathParamInt64("id")
|
||||
externallyManaged, err := asymkey_model.PublicKeyIsExternallyManaged(ctx, id)
|
||||
if err != nil {
|
||||
if asymkey_model.IsErrKeyNotExist(err) {
|
||||
ctx.APIErrorNotFound()
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if externallyManaged {
|
||||
ctx.APIError(http.StatusForbidden, "SSH Key is externally managed for this user")
|
||||
return
|
||||
}
|
||||
|
||||
if err := asymkey_service.DeletePublicKey(ctx, ctx.Doer, id); err != nil {
|
||||
if asymkey_model.IsErrKeyAccessDenied(err) {
|
||||
ctx.APIError(http.StatusForbidden, "You do not have access to this key")
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// listUserRepos - List the repositories owned by the given user.
|
||||
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
||||
opts := utils.GetListOptions(ctx)
|
||||
|
||||
searchOpts := repo_model.SearchRepoOptions{
|
||||
Actor: u,
|
||||
Private: private,
|
||||
ListOptions: opts,
|
||||
OrderBy: "id ASC",
|
||||
}
|
||||
searchOpts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
repos, count, err := repo_model.GetUserRepositories(ctx, searchOpts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := repos.LoadAttributes(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
apiRepos := make([]*api.Repository, 0, len(repos))
|
||||
for i := range repos {
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repos[i], ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.HasAnyUnitAccess() {
|
||||
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, opts.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &apiRepos)
|
||||
}
|
||||
|
||||
// ListUserRepos - list the repos owned by the given user.
|
||||
func ListUserRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/repos user userListRepos
|
||||
// ---
|
||||
// summary: List the repos owned by the given user
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose owned repos are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listUserRepos(ctx, ctx.ContextUser, ctx.IsSigned)
|
||||
}
|
||||
|
||||
// ListMyRepos - list the repositories you own or have access to.
|
||||
func ListMyRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/repos user userCurrentListRepos
|
||||
// ---
|
||||
// summary: List the repos that the authenticated user owns
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
Actor: ctx.Doer,
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Private: ctx.IsSigned,
|
||||
IncludeDescription: true,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
repos, count, err := repo_model.SearchRepository(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
results := make([]*api.Repository, len(repos))
|
||||
for i, repo := range repos {
|
||||
if err = repo.LoadOwner(ctx); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
results[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(count, opts.ListOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(count)
|
||||
ctx.JSON(http.StatusOK, &results)
|
||||
}
|
||||
|
||||
// ListOrgRepos - list the repositories of an organization.
|
||||
func ListOrgRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /orgs/{org}/repos organization orgListRepos
|
||||
// ---
|
||||
// summary: List an organization's repos
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: org
|
||||
// in: path
|
||||
// description: name of the organization
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
listUserRepos(ctx, ctx.Org.Organization.AsUser(), ctx.IsSigned)
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"gitea.dev/routers/api/v1/shared"
|
||||
"gitea.dev/services/context"
|
||||
)
|
||||
|
||||
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||
|
||||
// CreateRegistrationToken returns the token to register user runners
|
||||
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||
// swagger:operation POST /user/actions/runners/registration-token user userCreateRunnerRegistrationToken
|
||||
// ---
|
||||
// summary: Get a user's actions runner registration token
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RegistrationToken"
|
||||
|
||||
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// ListRunners get user-level runners
|
||||
func ListRunners(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runners user getUserRunners
|
||||
// ---
|
||||
// summary: Get user-level runners
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: disabled
|
||||
// in: query
|
||||
// description: filter by disabled status (true or false)
|
||||
// type: boolean
|
||||
// required: false
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RunnerList"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.ListRunners(ctx, ctx.Doer.ID, 0)
|
||||
}
|
||||
|
||||
// GetRunner get a user-level runner
|
||||
func GetRunner(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
|
||||
// ---
|
||||
// summary: Get a user-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// DeleteRunner delete a user-level runner
|
||||
func DeleteRunner(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
||||
// ---
|
||||
// summary: Delete a user-level runner
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// description: runner has been deleted
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
|
||||
// UpdateRunner update a user-level runner
|
||||
func UpdateRunner(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /user/actions/runners/{runner_id} user updateUserRunner
|
||||
// ---
|
||||
// summary: Update a user-level runner
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: runner_id
|
||||
// in: path
|
||||
// description: id of the runner
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/EditActionRunnerOption"
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/Runner"
|
||||
// "400":
|
||||
// "$ref": "#/responses/error"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "422":
|
||||
// "$ref": "#/responses/validationError"
|
||||
shared.UpdateRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"gitea.dev/modules/optional"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/modules/web"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
user_service "gitea.dev/services/user"
|
||||
)
|
||||
|
||||
// GetUserSettings returns user settings
|
||||
func GetUserSettings(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/settings user getUserSettings
|
||||
// ---
|
||||
// summary: Get user settings
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserSettings"
|
||||
ctx.JSON(http.StatusOK, convert.User2UserSettings(ctx.Doer))
|
||||
}
|
||||
|
||||
// UpdateUserSettings returns user settings
|
||||
func UpdateUserSettings(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /user/settings user updateUserSettings
|
||||
// ---
|
||||
// summary: Update user settings
|
||||
// parameters:
|
||||
// - name: body
|
||||
// in: body
|
||||
// schema:
|
||||
// "$ref": "#/definitions/UserSettingsOptions"
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserSettings"
|
||||
|
||||
form := web.GetForm(ctx).(*api.UserSettingsOptions)
|
||||
|
||||
opts := &user_service.UpdateOptions{
|
||||
FullName: optional.FromPtr(form.FullName),
|
||||
Description: optional.FromPtr(form.Description),
|
||||
Website: optional.FromPtr(form.Website),
|
||||
Location: optional.FromPtr(form.Location),
|
||||
Language: optional.FromPtr(form.Language),
|
||||
Theme: optional.FromPtr(form.Theme),
|
||||
DiffViewStyle: optional.FromPtr(form.DiffViewStyle),
|
||||
KeepEmailPrivate: optional.FromPtr(form.HideEmail),
|
||||
KeepActivityPrivate: optional.FromPtr(form.HideActivity),
|
||||
}
|
||||
if err := user_service.UpdateUser(ctx, ctx.Doer, opts); err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.User2UserSettings(ctx.Doer))
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// getStarredRepos returns the repos that the user with the specified userID has
|
||||
// starred
|
||||
func getStarredRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, error) {
|
||||
opts := &repo_model.StarredReposOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
StarrerID: user.ID,
|
||||
IncludePrivate: private,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(starredRepos))
|
||||
for i, starred := range starredRepos {
|
||||
permission, err := access_model.GetIndividualUserRepoPermission(ctx, starred, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, starred, permission)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetStarredRepos returns the repos that the given user has starred
|
||||
func GetStarredRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/starred user userListStarred
|
||||
// ---
|
||||
// summary: The repos that the given user has starred
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose starred repos are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
private := ctx.ContextUser.ID == ctx.Doer.ID
|
||||
repos, err := getStarredRepos(ctx, ctx.ContextUser, private)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(ctx.ContextUser.NumStars), utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(int64(ctx.ContextUser.NumStars))
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
|
||||
// GetMyStarredRepos returns the repos that the authenticated user has starred
|
||||
func GetMyStarredRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/starred user userCurrentListStarred
|
||||
// ---
|
||||
// summary: The repos that the authenticated user has starred
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
repos, err := getStarredRepos(ctx, ctx.Doer, true)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int64(ctx.Doer.NumStars), utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(int64(ctx.Doer.NumStars))
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
|
||||
// IsStarring returns whether the authenticated is starring the repo
|
||||
func IsStarring(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/starred/{owner}/{repo} user userCurrentCheckStarring
|
||||
// ---
|
||||
// summary: Whether the authenticated is starring the repo
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
if repo_model.IsStaring(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
|
||||
ctx.Status(http.StatusNoContent)
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
// Star the repo specified in the APIContext, as the authenticated user
|
||||
func Star(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /user/starred/{owner}/{repo} user userCurrentPutStar
|
||||
// ---
|
||||
// summary: Star the given repo
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo to star
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo to star
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// Unstar the repo specified in the APIContext, as the authenticated user
|
||||
func Unstar(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /user/starred/{owner}/{repo} user userCurrentDeleteStar
|
||||
// ---
|
||||
// summary: Unstar the given repo
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo to unstar
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo to unstar
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
|
||||
err := repo_model.StarRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2020 The Gitea Authors.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
activities_model "gitea.dev/models/activities"
|
||||
user_model "gitea.dev/models/user"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
feed_service "gitea.dev/services/feed"
|
||||
)
|
||||
|
||||
// Search search users
|
||||
func Search(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/search user userSearch
|
||||
// ---
|
||||
// summary: Search for users
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: q
|
||||
// in: query
|
||||
// description: keyword
|
||||
// type: string
|
||||
// - name: uid
|
||||
// in: query
|
||||
// description: ID of the user to search for
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// description: "SearchResults of a successful search"
|
||||
// schema:
|
||||
// type: object
|
||||
// properties:
|
||||
// ok:
|
||||
// type: boolean
|
||||
// data:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/User"
|
||||
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
uid := ctx.FormInt64("uid")
|
||||
var users []*user_model.User
|
||||
var maxResults int64
|
||||
var err error
|
||||
|
||||
switch uid {
|
||||
case user_model.GhostUserID:
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewGhostUser()}
|
||||
case user_model.ActionsUserID:
|
||||
maxResults = 1
|
||||
users = []*user_model.User{user_model.NewActionsUser()}
|
||||
default:
|
||||
opts := user_model.SearchUserOptions{
|
||||
Actor: ctx.Doer,
|
||||
Keyword: ctx.FormTrim("q"),
|
||||
UID: uid,
|
||||
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||
SearchByEmail: true,
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
users, maxResults, err = user_model.SearchUsers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||
"ok": false,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(maxResults, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(maxResults)
|
||||
|
||||
ctx.JSON(http.StatusOK, map[string]any{
|
||||
"ok": true,
|
||||
"data": convert.ToUsers(ctx, ctx.Doer, users),
|
||||
})
|
||||
}
|
||||
|
||||
// GetInfo get user's information
|
||||
func GetInfo(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username} user userGet
|
||||
// ---
|
||||
// summary: Get a user
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose data is to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/User"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if !user_model.IsUserVisibleToViewer(ctx, ctx.ContextUser, ctx.Doer) {
|
||||
// fake ErrUserNotExist error message to not leak information about existence
|
||||
ctx.APIErrorNotFound("GetUserByName", user_model.ErrUserNotExist{Name: ctx.PathParam("username")})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.ContextUser, ctx.Doer))
|
||||
}
|
||||
|
||||
// GetAuthenticatedUser get current user's information
|
||||
func GetAuthenticatedUser(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user user userGetCurrent
|
||||
// ---
|
||||
// summary: Get the authenticated user
|
||||
// produces:
|
||||
// - application/json
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/User"
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToUser(ctx, ctx.Doer, ctx.Doer))
|
||||
}
|
||||
|
||||
// GetUserHeatmapData is the handler to get a users heatmap
|
||||
func GetUserHeatmapData(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/heatmap user userGetHeatmapData
|
||||
// ---
|
||||
// summary: Get a user's heatmap
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose heatmap is to be obtained
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/UserHeatmapData"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
heatmap, err := activities_model.GetUserHeatmapDataByUser(ctx, ctx.ContextUser, ctx.Doer)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, heatmap)
|
||||
}
|
||||
|
||||
func ListUserActivityFeeds(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/activities/feeds user userListActivityFeeds
|
||||
// ---
|
||||
// summary: List a user's activity feeds
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// in: path
|
||||
// description: username of the user whose activity feeds are to be listed
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: only-performed-by
|
||||
// in: query
|
||||
// description: if true, only show actions performed by the requested user
|
||||
// type: boolean
|
||||
// - name: date
|
||||
// in: query
|
||||
// description: the date of the activities to be found
|
||||
// type: string
|
||||
// format: date
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/ActivityFeedsList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
includePrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
|
||||
listOptions := utils.GetListOptions(ctx)
|
||||
|
||||
opts := activities_model.GetFeedsOptions{
|
||||
RequestedUser: ctx.ContextUser,
|
||||
Actor: ctx.Doer,
|
||||
IncludePrivate: includePrivate,
|
||||
OnlyPerformedBy: ctx.FormBool("only-performed-by"),
|
||||
Date: ctx.FormString("date"),
|
||||
ListOptions: listOptions,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.SetTotalCountHeader(count)
|
||||
|
||||
ctx.JSON(http.StatusOK, convert.ToActivities(ctx, feeds, ctx.Doer))
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// Copyright 2016 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
access_model "gitea.dev/models/perm/access"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
user_model "gitea.dev/models/user"
|
||||
api "gitea.dev/modules/structs"
|
||||
"gitea.dev/routers/api/v1/utils"
|
||||
"gitea.dev/services/context"
|
||||
"gitea.dev/services/convert"
|
||||
)
|
||||
|
||||
// getWatchedRepos returns the repos that the user with the specified userID is watching
|
||||
func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, int64, error) {
|
||||
opts := &repo_model.WatchedReposOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
WatcherID: user.ID,
|
||||
IncludePrivate: private,
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
|
||||
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(watchedRepos))
|
||||
for i, watched := range watchedRepos {
|
||||
permission, err := access_model.GetIndividualUserRepoPermission(ctx, watched, user)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, watched, permission)
|
||||
}
|
||||
return repos, total, nil
|
||||
}
|
||||
|
||||
// GetWatchedRepos returns the repos that the user specified in ctx is watching
|
||||
func GetWatchedRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /users/{username}/subscriptions user userListSubscriptions
|
||||
// ---
|
||||
// summary: List the repositories watched by a user
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: username
|
||||
// type: string
|
||||
// in: path
|
||||
// description: username of the user whose watched repos are to be listed
|
||||
// required: true
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
private := ctx.ContextUser.ID == ctx.Doer.ID
|
||||
repos, total, err := getWatchedRepos(ctx, ctx.ContextUser, private)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
|
||||
// GetMyWatchedRepos returns the repos that the authenticated user is watching
|
||||
func GetMyWatchedRepos(ctx *context.APIContext) {
|
||||
// swagger:operation GET /user/subscriptions user userCurrentListSubscriptions
|
||||
// ---
|
||||
// summary: List repositories watched by the authenticated user
|
||||
// produces:
|
||||
// - application/json
|
||||
// parameters:
|
||||
// - name: page
|
||||
// in: query
|
||||
// description: page number of results to return (1-based)
|
||||
// type: integer
|
||||
// - name: limit
|
||||
// in: query
|
||||
// description: page size of results
|
||||
// type: integer
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/RepositoryList"
|
||||
|
||||
repos, total, err := getWatchedRepos(ctx, ctx.Doer, true)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
ctx.SetLinkHeader(total, utils.GetListOptions(ctx).PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, &repos)
|
||||
}
|
||||
|
||||
// IsWatching returns whether the authenticated user is watching the repo
|
||||
// specified in ctx
|
||||
func IsWatching(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/{owner}/{repo}/subscription repository userCurrentCheckSubscription
|
||||
// ---
|
||||
// summary: Check if the current user is watching a repo
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WatchInfo"
|
||||
// "404":
|
||||
// description: User is not watching this repo or repo do not exist
|
||||
|
||||
if repo_model.IsWatching(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID) {
|
||||
ctx.JSON(http.StatusOK, api.WatchInfo{
|
||||
Subscribed: true,
|
||||
Ignored: false,
|
||||
Reason: nil,
|
||||
CreatedAt: ctx.Repo.Repository.CreatedUnix.AsTime(),
|
||||
URL: subscriptionURL(ctx.Repo.Repository),
|
||||
RepositoryURL: ctx.Repo.Repository.APIURL(),
|
||||
})
|
||||
} else {
|
||||
ctx.APIErrorNotFound()
|
||||
}
|
||||
}
|
||||
|
||||
// Watch the repo specified in ctx, as the authenticated user
|
||||
func Watch(ctx *context.APIContext) {
|
||||
// swagger:operation PUT /repos/{owner}/{repo}/subscription repository userCurrentPutSubscription
|
||||
// ---
|
||||
// summary: Watch a repo
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "200":
|
||||
// "$ref": "#/responses/WatchInfo"
|
||||
// "403":
|
||||
// "$ref": "#/responses/forbidden"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, true)
|
||||
if err != nil {
|
||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||
ctx.APIError(http.StatusForbidden, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, api.WatchInfo{
|
||||
Subscribed: true,
|
||||
Ignored: false,
|
||||
Reason: nil,
|
||||
CreatedAt: ctx.Repo.Repository.CreatedUnix.AsTime(),
|
||||
URL: subscriptionURL(ctx.Repo.Repository),
|
||||
RepositoryURL: ctx.Repo.Repository.APIURL(),
|
||||
})
|
||||
}
|
||||
|
||||
// Unwatch the repo specified in ctx, as the authenticated user
|
||||
func Unwatch(ctx *context.APIContext) {
|
||||
// swagger:operation DELETE /repos/{owner}/{repo}/subscription repository userCurrentDeleteSubscription
|
||||
// ---
|
||||
// summary: Unwatch a repo
|
||||
// parameters:
|
||||
// - name: owner
|
||||
// in: path
|
||||
// description: owner of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// - name: repo
|
||||
// in: path
|
||||
// description: name of the repo
|
||||
// type: string
|
||||
// required: true
|
||||
// responses:
|
||||
// "204":
|
||||
// "$ref": "#/responses/empty"
|
||||
// "404":
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
err := repo_model.WatchRepo(ctx, ctx.Doer, ctx.Repo.Repository, false)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
ctx.Status(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// subscriptionURL returns the URL of the subscription API endpoint of a repo
|
||||
func subscriptionURL(repo *repo_model.Repository) string {
|
||||
return repo.APIURL() + "/subscription"
|
||||
}
|
||||
Reference in New Issue
Block a user