mirror of https://git.sr.ht/~sircmpwn/gql.sr.ht
Initial commit
This commit is contained in:
commit
d8356c5795
|
@ -0,0 +1,165 @@
|
|||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
|
@ -0,0 +1,4 @@
|
|||
# gql.sr.ht
|
||||
|
||||
This is a support library which houses code common to each *.sr.ht GraphQL API
|
||||
implementation.
|
|
@ -0,0 +1,260 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/vektah/gqlparser/gqlerror"
|
||||
|
||||
"git.sr.ht/~sircmpwn/gql.sr.ht/crypto"
|
||||
"git.sr.ht/~sircmpwn/gql.sr.ht/database"
|
||||
)
|
||||
|
||||
var userCtxKey = &contextKey{"user"}
|
||||
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
var bearerRegex = regexp.MustCompile(`^[0-9a-f]{32}$`)
|
||||
|
||||
const (
|
||||
USER_UNCONFIRMED = "unconfirmed"
|
||||
USER_ACTIVE_NON_PAYING = "active_non_paying"
|
||||
USER_ACTIVE_FREE = "active_free"
|
||||
USER_ACTIVE_PAYING = "active_paying"
|
||||
USER_ACTIVE_DELINQUENT = "active_delinquent"
|
||||
USER_ADMIN = "admin"
|
||||
USER_UNKNOWN = "unknown"
|
||||
USER_SUSPENDED = "suspended"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID int
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Username string
|
||||
Email string
|
||||
UserType string
|
||||
URL *string
|
||||
Location *string
|
||||
Bio *string
|
||||
SuspensionNotice *string
|
||||
}
|
||||
|
||||
func authError(w http.ResponseWriter, reason string, code int) {
|
||||
gqlerr := gqlerror.Errorf("Authentication error: %s", reason)
|
||||
b, err := json.Marshal(gqlerr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
type AuthCookie struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func cookieAuth(db *sql.DB, cookie *http.Cookie,
|
||||
w http.ResponseWriter, r *http.Request, next http.Handler) {
|
||||
|
||||
payload := crypto.Decrypt([]byte(cookie.Value))
|
||||
if payload == nil {
|
||||
authError(w, "Invalid authentication cookie", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
auth AuthCookie
|
||||
err error
|
||||
rows *sql.Rows
|
||||
user User
|
||||
)
|
||||
if err := json.Unmarshal(payload, &auth); err != nil {
|
||||
authError(w, "Invalid authentication cookie", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
query := database.
|
||||
Select(context.TODO(), []string{
|
||||
`u.id`, `u.username`,
|
||||
`u.created`, `u.updated`,
|
||||
`u.email`,
|
||||
`u.user_type`,
|
||||
`u.url`, `u.location`, `u.bio`,
|
||||
`u.suspension_notice`,
|
||||
}).
|
||||
From(`"user" u`).
|
||||
Where(`u.username = ?`, auth.Name)
|
||||
if rows, err = query.RunWith(db).Query(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
if err := rows.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
authError(w, "Invalid or expired OAuth token", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if err := rows.Scan(&user.ID, &user.Username, &user.Created, &user.Updated,
|
||||
&user.Email, &user.UserType, &user.URL, &user.Location, &user.Bio,
|
||||
&user.SuspensionNotice); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if rows.Next() {
|
||||
if err := rows.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(errors.New("Multiple matching user accounts; invariant broken"))
|
||||
}
|
||||
|
||||
if user.UserType == USER_SUSPENDED {
|
||||
authError(w, fmt.Sprintf("Account suspended with the following notice: %s\nContact support",
|
||||
user.SuspensionNotice), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, &user)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func Middleware(db *sql.DB) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/query" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := r.Cookie("sr.ht.unified-login.v1")
|
||||
if err == nil {
|
||||
cookieAuth(db, cookie, w, r, next)
|
||||
return
|
||||
}
|
||||
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
authError(w, `Authorization header is required.
|
||||
Expected 'Authorization: Bearer <token>'`, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
z := strings.SplitN(auth, " ", 2)
|
||||
if len(z) != 2 {
|
||||
authError(w, "Invalid Authorization header", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var bearer string
|
||||
switch z[0] {
|
||||
case "Bearer":
|
||||
token := []byte(z[1])
|
||||
if !bearerRegex.Match(token) {
|
||||
authError(w, "Invalid bearer token, expected 32-character haxadecimal string", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
hash := sha512.Sum512(token)
|
||||
bearer = hex.EncodeToString(hash[:])
|
||||
case "Internal":
|
||||
panic(errors.New("TODO"))
|
||||
default:
|
||||
authError(w, "Invalid Authorization header", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
expires time.Time
|
||||
rows *sql.Rows
|
||||
scopes string
|
||||
user User
|
||||
)
|
||||
query := database.
|
||||
Select(context.TODO(), []string{
|
||||
`ot.expires`,
|
||||
`ot.scopes`,
|
||||
`u.id`, `u.username`,
|
||||
`u.created`, `u.updated`,
|
||||
`u.email`,
|
||||
`u.user_type`,
|
||||
`u.url`, `u.location`, `u.bio`,
|
||||
`u.suspension_notice`,
|
||||
}).
|
||||
From(`oauthtoken ot`).
|
||||
Join(`"user" u ON u.id = ot.user_id`).
|
||||
Where(`ot.token_hash = ?`, bearer)
|
||||
if rows, err = query.RunWith(db).Query(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
if !rows.Next() {
|
||||
if err := rows.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
authError(w, "Invalid or expired OAuth token", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if err := rows.Scan(&expires, &scopes,
|
||||
&user.ID, &user.Username,
|
||||
&user.Created, &user.Updated,
|
||||
&user.Email,
|
||||
&user.UserType,
|
||||
&user.URL,
|
||||
&user.Location,
|
||||
&user.Bio,
|
||||
&user.SuspensionNotice); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if rows.Next() {
|
||||
if err := rows.Err(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
panic(errors.New("Multiple matching OAuth tokens; invariant broken"))
|
||||
}
|
||||
|
||||
if time.Now().UTC().After(expires) {
|
||||
authError(w, "Invalid or expired OAuth token", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if user.UserType == USER_SUSPENDED {
|
||||
authError(w, fmt.Sprintf("Account suspended with the following notice: %s\nContact support",
|
||||
user.SuspensionNotice), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
if scopes != "*" && scopes != "*:read" && scopes != "*:write" {
|
||||
authError(w, "Presently, OAuth authentication to the GraphQL API is only supported for OAuth tokens with all permissions, namely '*'.", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), userCtxKey, &user)
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ForContext(ctx context.Context) *User {
|
||||
raw, ok := ctx.Value(userCtxKey).(*User)
|
||||
if !ok {
|
||||
panic(errors.New("Invalid authentication context"))
|
||||
}
|
||||
return raw
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/fernet/fernet-go"
|
||||
"github.com/vaughan0/go-ini"
|
||||
)
|
||||
|
||||
var (
|
||||
privateKey ed25519.PrivateKey
|
||||
publicKey ed25519.PublicKey
|
||||
fernetKey *fernet.Key
|
||||
)
|
||||
|
||||
func InitCrypto(config ini.File) {
|
||||
b64key, ok := config.Get("webhooks", "private-key")
|
||||
if !ok {
|
||||
log.Fatalf("No webhook key configured")
|
||||
}
|
||||
seed, err := base64.StdEncoding.DecodeString(b64key)
|
||||
if err != nil {
|
||||
log.Fatalf("base64 decode webhooks private key: %v", err)
|
||||
}
|
||||
privateKey = ed25519.NewKeyFromSeed(seed)
|
||||
publicKey, _ = privateKey.Public().(ed25519.PublicKey)
|
||||
|
||||
b64fernet, ok := config.Get("sr.ht", "network-key")
|
||||
if !ok {
|
||||
log.Fatalf("No network key configured")
|
||||
}
|
||||
fernetKey, err = fernet.DecodeKey(b64fernet)
|
||||
if err != nil {
|
||||
log.Fatalf("Load Fernet network encryption key: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Sign(payload []byte) []byte {
|
||||
return ed25519.Sign(privateKey, payload)
|
||||
}
|
||||
|
||||
func Verify(payload, signature []byte) bool {
|
||||
return ed25519.Verify(publicKey, payload, signature)
|
||||
}
|
||||
|
||||
func Encrypt(payload []byte) []byte {
|
||||
msg, err := fernet.EncryptAndSign(payload, fernetKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Error encrypting payload: %v", err)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func Decrypt(payload []byte) []byte {
|
||||
return fernet.VerifyAndDecrypt(payload,
|
||||
time.Duration(0), []*fernet.Key{fernetKey})
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
)
|
||||
|
||||
func collectFields(ctx context.Context) []graphql.CollectedField {
|
||||
var fields []graphql.CollectedField
|
||||
if graphql.GetFieldContext(ctx) != nil {
|
||||
fields = graphql.CollectFieldsCtx(ctx, nil)
|
||||
|
||||
octx := graphql.GetOperationContext(ctx)
|
||||
for _, col := range fields {
|
||||
if col.Name == "results" {
|
||||
// This endpoint is using the cursor pattern; the columns we
|
||||
// actually need to filter with are nested into the results
|
||||
// field.
|
||||
fields = graphql.CollectFields(octx, col.SelectionSet, nil)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func ColumnsFor(ctx context.Context, alias string,
|
||||
colMap map[string]string) []string {
|
||||
|
||||
fields := collectFields(ctx)
|
||||
if len(fields) == 0 {
|
||||
// Collect all fields if we are not in an active graphql context
|
||||
for qlCol, _ := range colMap {
|
||||
fields = append(fields, graphql.CollectedField{
|
||||
&ast.Field{Name: qlCol}, nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(fields, func(a, b int) bool {
|
||||
return fields[a].Name < fields[b].Name
|
||||
})
|
||||
|
||||
var columns []string
|
||||
for _, qlCol := range fields {
|
||||
if sqlCol, ok := colMap[qlCol.Name]; ok {
|
||||
if alias != "" {
|
||||
columns = append(columns, pq.QuoteIdentifier(alias)+
|
||||
"."+pq.QuoteIdentifier(sqlCol))
|
||||
} else {
|
||||
columns = append(columns, pq.QuoteIdentifier(sqlCol))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return columns
|
||||
}
|
||||
|
||||
func FieldsFor(ctx context.Context,
|
||||
colMap map[string]interface{}) []interface{} {
|
||||
|
||||
qlFields := collectFields(ctx)
|
||||
if len(qlFields) == 0 {
|
||||
// Collect all fields if we are not in an active graphql context
|
||||
for qlCol, _ := range colMap {
|
||||
qlFields = append(qlFields, graphql.CollectedField{
|
||||
&ast.Field{Name: qlCol}, nil,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(qlFields, func(a, b int) bool {
|
||||
return qlFields[a].Name < qlFields[b].Name
|
||||
})
|
||||
|
||||
var fields []interface{}
|
||||
for _, qlField := range qlFields {
|
||||
if field, ok := colMap[qlField.Name]; ok {
|
||||
fields = append(fields, field)
|
||||
}
|
||||
}
|
||||
|
||||
return fields
|
||||
}
|
||||
|
||||
func WithAlias(alias, col string) string {
|
||||
if alias != "" {
|
||||
return alias + "." + col
|
||||
} else {
|
||||
return col
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
type Selectable interface {
|
||||
Select(ctx context.Context) []string
|
||||
Fields(ctx context.Context) []interface{}
|
||||
}
|
||||
|
||||
func Select(ctx context.Context, cols ...interface{}) sq.SelectBuilder {
|
||||
q := sq.Select().PlaceholderFormat(sq.Dollar)
|
||||
for _, col := range cols {
|
||||
switch col := col.(type) {
|
||||
case string:
|
||||
q = q.Columns(col)
|
||||
case []string:
|
||||
q = q.Columns(col...)
|
||||
case Selectable:
|
||||
q = q.Columns(col.Select(ctx)...)
|
||||
default:
|
||||
panic(fmt.Errorf("Unknown selectable type %T", col))
|
||||
}
|
||||
}
|
||||
return q
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package gql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/mail"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/martinlindhe/base36"
|
||||
"github.com/vaughan0/go-ini"
|
||||
gomail "gopkg.in/mail.v2"
|
||||
|
||||
"git.sr.ht/~sircmpwn/gql.sr.ht/auth"
|
||||
)
|
||||
|
||||
// Provides a graphql.RecoverFunc which will print the stack trace, and if
|
||||
// debug mode is not enabled, email it to the administrator.
|
||||
func EmailRecover(config ini.File, debug bool, srv string) graphql.RecoverFunc {
|
||||
return func (ctx context.Context, _origErr interface{}) error {
|
||||
var (
|
||||
ok bool
|
||||
origErr error
|
||||
)
|
||||
if origErr, ok = _origErr.(error); !ok {
|
||||
log.Printf("Unexpected error in recover: %v\n", origErr)
|
||||
return fmt.Errorf("internal system error")
|
||||
}
|
||||
|
||||
if errors.Is(origErr, context.Canceled) {
|
||||
return origErr
|
||||
}
|
||||
|
||||
if errors.Is(origErr, context.DeadlineExceeded) {
|
||||
return origErr
|
||||
}
|
||||
|
||||
if origErr.Error() == "pq: canceling statement due to user request" {
|
||||
return origErr
|
||||
}
|
||||
|
||||
stack := make([]byte, 32768) // 32 KiB
|
||||
i := runtime.Stack(stack, false)
|
||||
log.Println(string(stack[:i]))
|
||||
if debug {
|
||||
return fmt.Errorf("internal system error")
|
||||
}
|
||||
|
||||
to, ok := config.Get("mail", "error-to")
|
||||
if !ok {
|
||||
return fmt.Errorf("internal system error")
|
||||
}
|
||||
from, _ := config.Get("mail", "error-from")
|
||||
portStr, ok := config.Get("mail", "smtp-port")
|
||||
if !ok {
|
||||
return fmt.Errorf("internal system error")
|
||||
}
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
host, _ := config.Get("mail", "smtp-host")
|
||||
user, _ := config.Get("mail", "smtp-user")
|
||||
pass, _ := config.Get("mail", "smtp-password")
|
||||
|
||||
m := gomail.NewMessage()
|
||||
sender, err := mail.ParseAddress(from)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse sender address")
|
||||
}
|
||||
m.SetAddressHeader("From", sender.Address, sender.Name)
|
||||
recipient, err := mail.ParseAddress(to)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse recipient address")
|
||||
}
|
||||
m.SetAddressHeader("To", recipient.Address, recipient.Name)
|
||||
m.SetHeader("Message-ID", GenerateMessageID())
|
||||
m.SetHeader("Subject", fmt.Sprintf(
|
||||
"[%s] GraphQL query error: %v", srv, origErr))
|
||||
|
||||
quser := auth.ForContext(ctx)
|
||||
octx := graphql.GetOperationContext(ctx)
|
||||
|
||||
m.SetBody("text/plain", fmt.Sprintf(`Error occured processing GraphQL request:
|
||||
|
||||
%v
|
||||
|
||||
When running the following query on behalf of %s <%s>:
|
||||
|
||||
%s
|
||||
|
||||
The following stack trace was produced:
|
||||
|
||||
%s`, origErr, quser.Username, quser.Email, octx.RawQuery, string(stack[:i])))
|
||||
|
||||
d := gomail.NewDialer(host, port, user, pass)
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
log.Printf("Error sending email: %v\n", err)
|
||||
}
|
||||
return fmt.Errorf("internal system error")
|
||||
}
|
||||
}
|
||||
|
||||
// Generates an RFC 2822-compliant Message-Id based on the informational draft
|
||||
// "Recommendations for generating Message IDs", for lack of a better
|
||||
// authoritative source.
|
||||
func GenerateMessageID() string {
|
||||
var (
|
||||
now bytes.Buffer
|
||||
nonce []byte = make([]byte, 8)
|
||||
)
|
||||
binary.Write(&now, binary.BigEndian, time.Now().UnixNano())
|
||||
rand.Read(nonce)
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
hostname = "localhost"
|
||||
}
|
||||
return fmt.Sprintf("<%s.%s@%s>",
|
||||
base36.EncodeBytes(now.Bytes()),
|
||||
base36.EncodeBytes(nonce),
|
||||
hostname)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
module git.sr.ht/~sircmpwn/gql.sr.ht
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3
|
||||
github.com/99designs/gqlgen v0.11.4-0.20200512031635-40570d1b4d70
|
||||
github.com/Masterminds/squirrel v1.4.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001
|
||||
github.com/go-chi/chi v4.1.1+incompatible
|
||||
github.com/gorilla/websocket v1.4.2 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/lib/pq v1.5.2
|
||||
github.com/martinlindhe/base36 v1.0.0
|
||||
github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.6.0
|
||||
github.com/urfave/cli/v2 v2.2.0 // indirect
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec
|
||||
github.com/vektah/dataloaden v0.3.0 // indirect
|
||||
github.com/vektah/gqlparser v1.3.1
|
||||
github.com/vektah/gqlparser/v2 v2.0.1
|
||||
golang.org/x/mod v0.3.0 // indirect
|
||||
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a // indirect
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
)
|
|
@ -0,0 +1,217 @@
|
|||
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 h1:4wDp4BKF7NQqoh73VXpZsB/t1OEhDpz/zEpmdQfbjDk=
|
||||
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
|
||||
github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
|
||||
github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
|
||||
github.com/99designs/gqlgen v0.11.4-0.20200512031635-40570d1b4d70 h1:bZj4qAWfwu0SFTx5QsEI+y7JTji7L7ndm6snyDEIVLs=
|
||||
github.com/99designs/gqlgen v0.11.4-0.20200512031635-40570d1b4d70/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/squirrel v1.4.0 h1:he5i/EXixZxrBUWcxzDYMiju9WZ3ld/l7QBNuo/eN3w=
|
||||
github.com/Masterminds/squirrel v1.4.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA=
|
||||
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
|
||||
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0=
|
||||
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||
github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001 h1:/UMxx5lGDg30aioUL9e7xJnbJfJeX7vhcm57fa5udaI=
|
||||
github.com/fernet/fernet-go v0.0.0-20191111064656-eff2850e6001/go.mod h1:2H9hjfbpSMHwY503FclkV/lZTBh2YlOmLLSda12uL8c=
|
||||
github.com/go-chi/chi v1.0.0 h1:s/kv1cTXfivYjdKJdyUzNGyAWZ/2t7duW1gKn5ivu+c=
|
||||
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.1+incompatible h1:MmTgB0R8Bt/jccxp+t6S/1VGIKdJw5J74CK/c9tTfA4=
|
||||
github.com/go-chi/chi v4.1.1+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
|
||||
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
|
||||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
|
||||
github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
|
||||
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
|
||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg=
|
||||
github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54 h1:p8zN0Xu28xyEkPpqLbFXAnjdgBVvTJCpfOtoDf/+/RQ=
|
||||
github.com/matryer/moq v0.0.0-20200310130814-7721994d1b54/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.3.0 h1:iDwIio/3gk2QtLLEsqU5lInaMzos0hDTz8a6lazSFVw=
|
||||
github.com/mitchellh/mapstructure v1.3.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
|
||||
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.6.0 h1:YVPodQOcK15POxhgARIvnDRVpLcuK8mglnMrWfyrw6A=
|
||||
github.com/prometheus/client_golang v1.6.0/go.mod h1:ZLOG9ck3JLRdB5MgO8f+lLTe83AXG6ro35rLTxvnIl4=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
|
||||
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.11 h1:DhHlBtkHWPYi8O2y31JkK0TF+DGM+51OopZjH/Ia5qI=
|
||||
github.com/prometheus/procfs v0.0.11/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec h1:DGmKwyZwEB8dI7tbLt/I/gQuP559o/0FrAkHKlQM/Ks=
|
||||
github.com/vaughan0/go-ini v0.0.0-20130923145212-a98ad7ee00ec/go.mod h1:owBmyHYMLkxyrugmfwE/DLJyW8Ro9mkphwuVErQ0iUw=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg=
|
||||
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/dataloaden v0.3.0 h1:ZfVN2QD6swgvp+tDqdH/OIT/wu3Dhu0cus0k5gIZS84=
|
||||
github.com/vektah/dataloaden v0.3.0/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
|
||||
github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU=
|
||||
github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74=
|
||||
github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o=
|
||||
github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8=
|
||||
golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM=
|
||||
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a h1:gILuVKC+ZPD6g/tj6zBOdnOH1ZHI0zZ86+KLMogc6/s=
|
||||
golang.org/x/tools v0.0.0-20200519015757-0d0afa43d58a/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/mail.v2 v2.3.1 h1:WYFn/oANrAGP2C0dcV6/pbkPzv8yGzqTjPmTeO7qoXk=
|
||||
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
|
|
@ -0,0 +1,150 @@
|
|||
package gql
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.sr.ht/~sircmpwn/getopt"
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/99designs/gqlgen/handler"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/vaughan0/go-ini"
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"git.sr.ht/~sircmpwn/gql.sr.ht/auth"
|
||||
"git.sr.ht/~sircmpwn/gql.sr.ht/crypto"
|
||||
)
|
||||
|
||||
var (
|
||||
requestsProcessed = promauto.NewCounter(prometheus.CounterOpts{
|
||||
Name: "api_requests_processed_total",
|
||||
Help: "Total number of API requests processed",
|
||||
})
|
||||
requestDuration = promauto.NewHistogram(prometheus.HistogramOpts{
|
||||
Name: "api_request_duration_millis",
|
||||
Help: "Duration of processed HTTP requests in milliseconds",
|
||||
Buckets: []float64{10, 20, 40, 80, 120, 300, 600, 900, 1800},
|
||||
})
|
||||
)
|
||||
|
||||
var (
|
||||
debug bool
|
||||
addr string
|
||||
)
|
||||
|
||||
// Loads the application configuration, reads options from the command line,
|
||||
// and initializes some internals based on these results.
|
||||
func LoadConfig(defaultAddr string) ini.File {
|
||||
addr = defaultAddr
|
||||
var (
|
||||
config ini.File
|
||||
err error
|
||||
)
|
||||
opts, _, err := getopt.Getopts(os.Args, "b:d")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
switch opt.Option {
|
||||
case 'b':
|
||||
addr = opt.Value
|
||||
case 'd':
|
||||
debug = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range []string{"../config.ini", "/etc/sr.ht/config.ini"} {
|
||||
config, err = ini.LoadFile(path)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config file: %v", err)
|
||||
}
|
||||
|
||||
crypto.InitCrypto(config)
|
||||
return config
|
||||
}
|
||||
|
||||
// Prepares a router with the sr.ht API middleware pre-configured. This
|
||||
// connects to PostgreSQL to rig up the database middlewares.
|
||||
func MakeRouter(service string, config ini.File,
|
||||
schema graphql.ExecutableSchema) chi.Router {
|
||||
pgcs, ok := config.Get(service, "connection-string")
|
||||
if !ok {
|
||||
log.Fatalf("No connection string provided in config.ini")
|
||||
}
|
||||
|
||||
db, err := sql.Open("postgres", pgcs)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open a database connection: %v", err)
|
||||
}
|
||||
|
||||
apiconf := fmt.Sprintf("%s::api", service)
|
||||
|
||||
var timeout time.Duration
|
||||
if to, ok := config.Get(apiconf, "max-duration"); ok {
|
||||
timeout, err = time.ParseDuration(to)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
timeout = 3 * time.Second
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
router.Use(func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
next.ServeHTTP(w, r)
|
||||
end := time.Now()
|
||||
elapsed := end.Sub(start)
|
||||
requestDuration.Observe(float64(elapsed.Milliseconds()))
|
||||
requestsProcessed.Inc()
|
||||
})
|
||||
})
|
||||
router.Use(auth.Middleware(db))
|
||||
router.Use(middleware.Logger)
|
||||
router.Use(middleware.Timeout(timeout))
|
||||
|
||||
var complexity int
|
||||
if limit, ok := config.Get(apiconf, "max-complexity"); ok {
|
||||
complexity, err = strconv.Atoi(limit)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else {
|
||||
complexity = 100
|
||||
}
|
||||
|
||||
srv := handler.GraphQL(schema,
|
||||
handler.ComplexityLimit(complexity),
|
||||
handler.RecoverFunc(EmailRecover(config, debug, service)))
|
||||
|
||||
router.Handle("/query", srv)
|
||||
router.Handle("/query/metrics", promhttp.Handler())
|
||||
|
||||
if debug {
|
||||
router.Handle("/", playground.Handler("GraphQL playground", "/query"))
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Runs the API server.
|
||||
func ListenAndServe(router chi.Router) {
|
||||
log.Printf("running on %s", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, router))
|
||||
}
|
Loading…
Reference in New Issue