初始提交: Gitea 项目代码

This commit is contained in:
root
2026-05-30 22:47:36 +08:00
commit f288f76350
6116 changed files with 776822 additions and 0 deletions
+453
View File
@@ -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)
}
+424
View File
@@ -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))
}
+65
View File
@@ -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)
}
+96
View File
@@ -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)
}
+132
View File
@@ -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)
}
+267
View File
@@ -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)
}
+305
View File
@@ -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)
}
}
+34
View File
@@ -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")
}
+159
View File
@@ -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"),
)
}
+304
View File
@@ -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)
}
+169
View File
@@ -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)
}
+126
View File
@@ -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"))
}
+65
View File
@@ -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))
}
+216
View File
@@ -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)
}
+223
View File
@@ -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))
}
+224
View File
@@ -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"
}