mirror of
https://github.com/ergochat/ergo.git
synced 2025-06-01 12:27:35 +02:00
API enhancements (#2261)
Fixes #2257 and #2260 * add `/v1/status` endpoint * add `/v1/account_list` endpoint * add fields to `/v1/account_details` response
This commit is contained in:
parent
4da6511674
commit
e4aac56bda
36
docs/API.md
36
docs/API.md
@ -51,6 +51,8 @@ The response is a JSON object with fields:
|
||||
* `success`: whether the account exists or not
|
||||
* `accountName`: canonical, case-unfolded version of the account name
|
||||
* `email`: email address of the account provided
|
||||
* `registeredAt`: string, registration date/time of the account (in ISO8601 format)
|
||||
* `channels`: array of strings, list of channels the account is registered on or associated with
|
||||
|
||||
`/v1/check_auth`
|
||||
----------------
|
||||
@ -86,3 +88,37 @@ The response is a JSON object with fields:
|
||||
* `success`: whether the account creation succeeded
|
||||
* `errorCode`: string, optional, machine-readable description of the error. Possible values include: `ACCOUNT_EXISTS`, `INVALID_PASSPHRASE`, `UNKNOWN_ERROR`.
|
||||
* `error`: string, optional, human-readable description of the failure.
|
||||
|
||||
`/v1/account_list`
|
||||
-------------------
|
||||
|
||||
This endpoint fetches a list of all accounts. The request body is ignored and can be empty.
|
||||
|
||||
The response is a JSON object with fields:
|
||||
|
||||
* `success`: whether the request succeeded
|
||||
* `accounts`: array of objects, each with fields:
|
||||
* `success`: boolean, whether this individual account query succeeded
|
||||
* `accountName`: string, canonical, case-unfolded version of the account name
|
||||
* `totalCount`: integer, total number of accounts returned
|
||||
|
||||
|
||||
`/v1/status`
|
||||
-------------
|
||||
|
||||
This endpoint returns status information about the running Ergo server. The request body is ignored and can be empty.
|
||||
|
||||
The response is a JSON object with fields:
|
||||
|
||||
* `success`: whether the request succeeded
|
||||
* `version`: string, Ergo server version string
|
||||
* `go_version`: string, version of Go runtime used
|
||||
* `start_time`: string, server start time in ISO8601 format
|
||||
* `users`: object with fields:
|
||||
* `total`: total number of users connected
|
||||
* `invisible`: number of invisible users
|
||||
* `operators`: number of operators connected
|
||||
* `unknown`: number of users with unknown status
|
||||
* `max`: maximum number of users seen connected at once
|
||||
* `channels`: integer, number of channels currently active
|
||||
* `servers`: integer, number of servers connected in the network
|
||||
|
103
irc/api.go
103
irc/api.go
@ -5,7 +5,10 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/ergochat/ergo/irc/utils"
|
||||
)
|
||||
|
||||
func newAPIHandler(server *Server) http.Handler {
|
||||
@ -18,6 +21,8 @@ func newAPIHandler(server *Server) http.Handler {
|
||||
api.mux.HandleFunc("POST /v1/check_auth", api.handleCheckAuth)
|
||||
api.mux.HandleFunc("POST /v1/saregister", api.handleSaregister)
|
||||
api.mux.HandleFunc("POST /v1/account_details", api.handleAccountDetails)
|
||||
api.mux.HandleFunc("POST /v1/account_list", api.handleAccountList)
|
||||
api.mux.HandleFunc("POST /v1/status", api.handleStatus)
|
||||
|
||||
return api
|
||||
}
|
||||
@ -29,7 +34,6 @@ type ergoAPI struct {
|
||||
|
||||
func (a *ergoAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
defer a.server.HandlePanic(nil)
|
||||
|
||||
defer a.server.logger.Debug("api", r.URL.Path)
|
||||
|
||||
if a.checkBearerAuth(r.Header.Get("Authorization")) {
|
||||
@ -117,8 +121,6 @@ func (a *ergoAPI) handleCheckAuth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// try passphrase if present
|
||||
if request.AccountName != "" && request.Passphrase != "" {
|
||||
// TODO this only checks the internal database, not auth-script;
|
||||
// it's a little weird to use both auth-script and the API but we should probably handle it
|
||||
account, err := a.server.accounts.checkPassphrase(request.AccountName, request.Passphrase)
|
||||
switch err {
|
||||
case nil:
|
||||
@ -133,7 +135,6 @@ func (a *ergoAPI) handleCheckAuth(w http.ResponseWriter, r *http.Request) {
|
||||
response.Error = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
// try certfp if present
|
||||
if !response.Success && request.Certfp != "" {
|
||||
// TODO support cerftp
|
||||
@ -175,8 +176,10 @@ func (a *ergoAPI) handleSaregister(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type apiAccountDetailsResponse struct {
|
||||
apiGenericResponse
|
||||
AccountName string `json:"accountName,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
AccountName string `json:"accountName,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
RegisteredAt string `json:"registeredAt,omitempty"`
|
||||
Channels []string `json:"channels,omitempty"`
|
||||
}
|
||||
|
||||
type apiAccountDetailsRequest struct {
|
||||
@ -191,8 +194,6 @@ func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var response apiAccountDetailsResponse
|
||||
|
||||
// TODO could probably use better error handling and more details
|
||||
|
||||
if request.AccountName != "" {
|
||||
accountData, err := a.server.accounts.LoadAccount(request.AccountName)
|
||||
if err == nil {
|
||||
@ -207,6 +208,12 @@ func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) {
|
||||
case nil:
|
||||
response.AccountName = accountData.Name
|
||||
response.Email = accountData.Settings.Email
|
||||
if !accountData.RegisteredAt.IsZero() {
|
||||
response.RegisteredAt = accountData.RegisteredAt.Format(utils.IRCv3TimestampFormat)
|
||||
}
|
||||
|
||||
// Get channels the account is in
|
||||
response.Channels = a.server.channels.ChannelsForAccount(accountData.NameCasefolded)
|
||||
response.Success = true
|
||||
case errAccountDoesNotExist, errAccountUnverified, errAccountSuspended:
|
||||
response.Success = false
|
||||
@ -222,3 +229,83 @@ func (a *ergoAPI) handleAccountDetails(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
a.writeJSONResponse(response, w, r)
|
||||
}
|
||||
|
||||
type apiAccountListResponse struct {
|
||||
apiGenericResponse
|
||||
Accounts []apiAccountDetailsResponse `json:"accounts"`
|
||||
TotalCount int `json:"totalCount"`
|
||||
}
|
||||
|
||||
func (a *ergoAPI) handleAccountList(w http.ResponseWriter, r *http.Request) {
|
||||
var response apiAccountListResponse
|
||||
|
||||
// Get all account names
|
||||
accounts := a.server.accounts.AllNicks()
|
||||
response.TotalCount = len(accounts)
|
||||
|
||||
// Load account details
|
||||
response.Accounts = make([]apiAccountDetailsResponse, len(accounts))
|
||||
for i, account := range accounts {
|
||||
accountData, err := a.server.accounts.LoadAccount(account)
|
||||
if err != nil {
|
||||
response.Accounts[i] = apiAccountDetailsResponse{
|
||||
apiGenericResponse: apiGenericResponse{
|
||||
Success: false,
|
||||
Error: err.Error(),
|
||||
},
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
response.Accounts[i] = apiAccountDetailsResponse{
|
||||
apiGenericResponse: apiGenericResponse{
|
||||
Success: true,
|
||||
},
|
||||
AccountName: accountData.Name,
|
||||
Email: accountData.Settings.Email,
|
||||
}
|
||||
}
|
||||
|
||||
response.Success = true
|
||||
a.writeJSONResponse(response, w, r)
|
||||
}
|
||||
|
||||
type apiStatusResponse struct {
|
||||
apiGenericResponse
|
||||
Version string `json:"version"`
|
||||
GoVersion string `json:"go_version"`
|
||||
Commit string `json:"commit,omitempty"`
|
||||
StartTime string `json:"start_time"`
|
||||
Users struct {
|
||||
Total int `json:"total"`
|
||||
Invisible int `json:"invisible"`
|
||||
Operators int `json:"operators"`
|
||||
Unknown int `json:"unknown"`
|
||||
Max int `json:"max"`
|
||||
} `json:"users"`
|
||||
Channels int `json:"channels"`
|
||||
Servers int `json:"servers"`
|
||||
}
|
||||
|
||||
func (a *ergoAPI) handleStatus(w http.ResponseWriter, r *http.Request) {
|
||||
server := a.server
|
||||
stats := server.stats.GetValues()
|
||||
|
||||
response := apiStatusResponse{
|
||||
apiGenericResponse: apiGenericResponse{Success: true},
|
||||
Version: SemVer,
|
||||
GoVersion: runtime.Version(),
|
||||
Commit: Commit,
|
||||
StartTime: server.ctime.Format(utils.IRCv3TimestampFormat),
|
||||
}
|
||||
|
||||
response.Users.Total = stats.Total
|
||||
response.Users.Invisible = stats.Invisible
|
||||
response.Users.Operators = stats.Operators
|
||||
response.Users.Unknown = stats.Unknown
|
||||
response.Users.Max = stats.Max
|
||||
response.Channels = server.channels.Len()
|
||||
response.Servers = 1
|
||||
|
||||
a.writeJSONResponse(response, w, r)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user