API: Rig up query { pastes }

This commit is contained in:
Drew DeVault 2021-09-21 09:55:26 +02:00
parent 606a523585
commit 80518d74f4
4 changed files with 191 additions and 28 deletions

View File

@ -39,6 +39,7 @@ type Config struct {
type ResolverRoot interface {
Mutation() MutationResolver
Paste() PasteResolver
Query() QueryResolver
User() UserResolver
}
@ -105,6 +106,10 @@ type MutationResolver interface {
Update(ctx context.Context, id string, visibility model.Visibility) (*model.Paste, error)
Delete(ctx context.Context, id string) (*model.Paste, error)
}
type PasteResolver interface {
Files(ctx context.Context, obj *model.Paste) ([]*model.File, error)
User(ctx context.Context, obj *model.Paste) (model.Entity, error)
}
type QueryResolver interface {
Version(ctx context.Context) (*model.Version, error)
Me(ctx context.Context) (*model.User, error)
@ -1161,14 +1166,14 @@ func (ec *executionContext) _Paste_visibility(ctx context.Context, field graphql
Object: "Paste",
Field: field,
Args: nil,
IsMethod: false,
IsMethod: true,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Visibility, nil
return obj.Visibility(), nil
})
if err != nil {
ec.Error(ctx, err)
@ -1196,14 +1201,14 @@ func (ec *executionContext) _Paste_files(ctx context.Context, field graphql.Coll
Object: "Paste",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.Files, nil
return ec.resolvers.Paste().Files(rctx, obj)
})
if err != nil {
ec.Error(ctx, err)
@ -1231,15 +1236,15 @@ func (ec *executionContext) _Paste_user(ctx context.Context, field graphql.Colle
Object: "Paste",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
directive0 := func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.User, nil
return ec.resolvers.Paste().User(rctx, obj)
}
directive1 := func(ctx context.Context) (interface{}, error) {
scope, err := ec.unmarshalNAccessScope2gitᚗsrᚗhtᚋאsircmpwnᚋpasteᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐAccessScope(ctx, "PROFILE")
@ -3264,28 +3269,46 @@ func (ec *executionContext) _Paste(ctx context.Context, sel ast.SelectionSet, ob
case "id":
out.Values[i] = ec._Paste_id(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "created":
out.Values[i] = ec._Paste_created(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "visibility":
out.Values[i] = ec._Paste_visibility(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "files":
out.Values[i] = ec._Paste_files(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Paste_files(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
case "user":
out.Values[i] = ec._Paste_user(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
field := field
out.Concurrently(i, func() (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Paste_user(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}

View File

@ -21,14 +21,6 @@ type File struct {
Contents string `json:"contents"`
}
type Paste struct {
ID string `json:"id"`
Created time.Time `json:"created"`
Visibility Visibility `json:"visibility"`
Files []*File `json:"files"`
User Entity `json:"user"`
}
type PasteCursor struct {
Results []*Paste `json:"results"`
Cursor *model.Cursor `json:"cursor"`

114
api/graph/model/paste.go Normal file
View File

@ -0,0 +1,114 @@
package model
import (
"context"
"database/sql"
"fmt"
"strconv"
"strings"
"time"
sq "github.com/Masterminds/squirrel"
"git.sr.ht/~sircmpwn/core-go/database"
"git.sr.ht/~sircmpwn/core-go/model"
)
type Paste struct {
ID string `json:"id"`
Created time.Time `json:"created"`
UserID int
RawVisibility string
alias string
fields *database.ModelFields
}
func (paste *Paste) Visibility() Visibility {
vis := Visibility(strings.ToUpper(paste.RawVisibility))
if !vis.IsValid() {
panic(fmt.Sprintf("Database invariant broken: paste ID %s has invalid visibility", paste.ID))
}
return vis
}
func (paste *Paste) As(alias string) *Paste {
paste.alias = alias
return paste
}
func (paste *Paste) Alias() string {
return paste.alias
}
func (paste *Paste) Table() string {
return "paste"
}
func (paste *Paste) Fields() *database.ModelFields {
if paste.fields != nil {
return paste.fields
}
paste.fields = &database.ModelFields{
Fields: []*database.FieldMap{
{ "created", "created", &paste.Created },
{ "visibility", "visibility", &paste.RawVisibility },
// Always fetch:
{ "sha", "", &paste.ID },
{ "user_id", "", &paste.UserID },
},
}
return paste.fields
}
func (paste *Paste) QueryWithCursor(ctx context.Context,
runner sq.BaseRunner, q sq.SelectBuilder,
cur *model.Cursor) ([]*Paste, *model.Cursor) {
var (
err error
rows *sql.Rows
)
if cur.Next != "" {
next, _ := strconv.Atoi(cur.Next)
q = q.Where(database.WithAlias(paste.alias, "id") + "<= ?", next)
}
q = q.
Column(database.WithAlias(paste.alias, "id")).
OrderBy(database.WithAlias(paste.alias, "id") + " DESC").
Limit(uint64(cur.Count + 1))
if rows, err = q.RunWith(runner).QueryContext(ctx); err != nil {
panic(err)
}
defer rows.Close()
var (
pastes []*Paste
id int
)
for rows.Next() {
var paste Paste
if err := rows.Scan(append(
database.Scan(ctx, &paste),
&id)...); err != nil {
panic(err)
}
pastes = append(pastes, &paste)
}
if len(pastes) > cur.Count {
cur = &model.Cursor{
Count: cur.Count,
Next: strconv.Itoa(id),
Search: cur.Search,
}
pastes = pastes[:cur.Count]
} else {
cur = nil
}
return pastes, cur
}

View File

@ -5,9 +5,11 @@ package graph
import (
"context"
"database/sql"
"fmt"
"git.sr.ht/~sircmpwn/core-go/auth"
"git.sr.ht/~sircmpwn/core-go/database"
coremodel "git.sr.ht/~sircmpwn/core-go/model"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/api"
"git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model"
@ -27,6 +29,14 @@ func (r *mutationResolver) Delete(ctx context.Context, id string) (*model.Paste,
panic(fmt.Errorf("not implemented"))
}
func (r *pasteResolver) Files(ctx context.Context, obj *model.Paste) ([]*model.File, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *pasteResolver) User(ctx context.Context, obj *model.Paste) (model.Entity, error) {
panic(fmt.Errorf("not implemented"))
}
func (r *queryResolver) Version(ctx context.Context) (*model.Version, error) {
return &model.Version{
Major: 0,
@ -50,7 +60,27 @@ func (r *queryResolver) User(ctx context.Context, username string) (*model.User,
}
func (r *queryResolver) Pastes(ctx context.Context, cursor *coremodel.Cursor) (*model.PasteCursor, error) {
panic(fmt.Errorf("not implemented"))
if cursor == nil {
cursor = coremodel.NewCursor(nil)
}
var pastes []*model.Paste
if err := database.WithTx(ctx, &sql.TxOptions{
Isolation: 0,
ReadOnly: true,
}, func(tx *sql.Tx) error {
paste := (&model.Paste{}).As(`paste`)
query := database.
Select(ctx, paste).
From(`paste`).
Where(`paste.user_id = ?`, auth.ForContext(ctx).UserID)
pastes, cursor = paste.QueryWithCursor(ctx, tx, query, cursor)
return nil
}); err != nil {
return nil, err
}
return &model.PasteCursor{pastes, cursor}, nil
}
func (r *queryResolver) Paste(ctx context.Context, id string) (*model.Paste, error) {
@ -64,6 +94,9 @@ func (r *userResolver) Pastes(ctx context.Context, obj *model.User, cursor *core
// Mutation returns api.MutationResolver implementation.
func (r *Resolver) Mutation() api.MutationResolver { return &mutationResolver{r} }
// Paste returns api.PasteResolver implementation.
func (r *Resolver) Paste() api.PasteResolver { return &pasteResolver{r} }
// Query returns api.QueryResolver implementation.
func (r *Resolver) Query() api.QueryResolver { return &queryResolver{r} }
@ -71,5 +104,6 @@ func (r *Resolver) Query() api.QueryResolver { return &queryResolver{r} }
func (r *Resolver) User() api.UserResolver { return &userResolver{r} }
type mutationResolver struct{ *Resolver }
type pasteResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
type userResolver struct{ *Resolver }