API: Rig up paste { files }
This commit is contained in:
parent
fb6d90961a
commit
dd5dbf3240
|
@ -57,3 +57,6 @@ models:
|
|||
Cursor:
|
||||
model:
|
||||
- git.sr.ht/~sircmpwn/core-go/model.Cursor
|
||||
URL:
|
||||
model:
|
||||
- git.sr.ht/~sircmpwn/paste.sr.ht/api/graph/model.URL
|
||||
|
|
|
@ -38,6 +38,7 @@ type Config struct {
|
|||
}
|
||||
|
||||
type ResolverRoot interface {
|
||||
File() FileResolver
|
||||
Mutation() MutationResolver
|
||||
Paste() PasteResolver
|
||||
Query() QueryResolver
|
||||
|
@ -101,6 +102,10 @@ type ComplexityRoot struct {
|
|||
}
|
||||
}
|
||||
|
||||
type FileResolver interface {
|
||||
Sha(ctx context.Context, obj *model.File) (string, error)
|
||||
Contents(ctx context.Context, obj *model.File) (*model.URL, error)
|
||||
}
|
||||
type MutationResolver interface {
|
||||
Create(ctx context.Context, files []*graphql.Upload, visibility model.Visibility) (*model.Paste, error)
|
||||
Update(ctx context.Context, id string, visibility model.Visibility) (*model.Paste, error)
|
||||
|
@ -429,6 +434,12 @@ var sources = []*ast.Source{
|
|||
scalar Cursor
|
||||
scalar Time
|
||||
scalar Upload
|
||||
# URL from which some secondary data may be retrieved. You must provide the
|
||||
# same Authentication header to this address as you did to the GraphQL resolver
|
||||
# which provided it. The URL is not guaranteed to be consistent for an extended
|
||||
# length of time; applications should submit a new GraphQL query each time they
|
||||
# wish to access the data at the provided URL.
|
||||
scalar URL
|
||||
|
||||
# This is used to decorate fields which are only accessible with a personal
|
||||
# access token, and are not available to clients using OAuth 2.0 access tokens.
|
||||
|
@ -509,7 +520,7 @@ type Paste {
|
|||
type File {
|
||||
filename: String!
|
||||
sha: String!
|
||||
contents: String!
|
||||
contents: URL!
|
||||
}
|
||||
|
||||
# A cursor for enumerating pastes
|
||||
|
@ -822,14 +833,14 @@ func (ec *executionContext) _File_sha(ctx context.Context, field graphql.Collect
|
|||
Object: "File",
|
||||
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.Sha, nil
|
||||
return ec.resolvers.File().Sha(rctx, obj)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
@ -857,14 +868,14 @@ func (ec *executionContext) _File_contents(ctx context.Context, field graphql.Co
|
|||
Object: "File",
|
||||
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.Contents, nil
|
||||
return ec.resolvers.File().Contents(rctx, obj)
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
|
@ -876,9 +887,9 @@ func (ec *executionContext) _File_contents(ctx context.Context, field graphql.Co
|
|||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(string)
|
||||
res := resTmp.(*model.URL)
|
||||
fc.Result = res
|
||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
return ec.marshalNURL2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋpasteᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐURL(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_create(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
|
@ -3197,18 +3208,36 @@ func (ec *executionContext) _File(ctx context.Context, sel ast.SelectionSet, obj
|
|||
case "filename":
|
||||
out.Values[i] = ec._File_filename(ctx, field, obj)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
case "sha":
|
||||
out.Values[i] = ec._File_sha(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._File_sha(ctx, field, obj)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
})
|
||||
case "contents":
|
||||
out.Values[i] = ec._File_contents(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._File_contents(ctx, field, obj)
|
||||
if res == graphql.Null {
|
||||
atomic.AddUint32(&invalids, 1)
|
||||
}
|
||||
return res
|
||||
})
|
||||
default:
|
||||
panic("unknown field " + strconv.Quote(field.Name))
|
||||
}
|
||||
|
@ -3972,6 +4001,32 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as
|
|||
return res
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNURL2gitᚗsrᚗhtᚋאsircmpwnᚋpasteᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐURL(ctx context.Context, v interface{}) (model.URL, error) {
|
||||
var res model.URL
|
||||
err := res.UnmarshalGQL(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNURL2gitᚗsrᚗhtᚋאsircmpwnᚋpasteᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐURL(ctx context.Context, sel ast.SelectionSet, v model.URL) graphql.Marshaler {
|
||||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNURL2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋpasteᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐURL(ctx context.Context, v interface{}) (*model.URL, error) {
|
||||
var res = new(model.URL)
|
||||
err := res.UnmarshalGQL(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNURL2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋpasteᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐURL(ctx context.Context, sel ast.SelectionSet, v *model.URL) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
ec.Errorf(ctx, "must not be null")
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNUpload2ᚕᚖgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, v interface{}) ([]*graphql.Upload, error) {
|
||||
var vSlice []interface{}
|
||||
if v != nil {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"git.sr.ht/~sircmpwn/core-go/database"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Filename string `json:"filename"`
|
||||
|
||||
PasteID int
|
||||
BlobID int
|
||||
|
||||
alias string
|
||||
fields *database.ModelFields
|
||||
}
|
||||
|
||||
func (file *File) As(alias string) *File {
|
||||
file.alias = alias
|
||||
return file
|
||||
}
|
||||
|
||||
func (file *File) Alias() string {
|
||||
return file.alias
|
||||
}
|
||||
|
||||
func (file *File) Table() string {
|
||||
return "paste_file"
|
||||
}
|
||||
|
||||
func (file *File) Fields() *database.ModelFields {
|
||||
if file.fields != nil {
|
||||
return file.fields
|
||||
}
|
||||
file.fields = &database.ModelFields{
|
||||
Fields: []*database.FieldMap{
|
||||
{ "filename", "filename", &file.Filename },
|
||||
|
||||
// Always fetch:
|
||||
{ "paste_id", "", &file.PasteID },
|
||||
{ "blob_id", "", &file.BlobID },
|
||||
},
|
||||
}
|
||||
return file.fields
|
||||
}
|
|
@ -15,12 +15,6 @@ type Entity interface {
|
|||
IsEntity()
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Filename string `json:"filename"`
|
||||
Sha string `json:"sha"`
|
||||
Contents string `json:"contents"`
|
||||
}
|
||||
|
||||
type PasteCursor struct {
|
||||
Results []*Paste `json:"results"`
|
||||
Cursor *model.Cursor `json:"cursor"`
|
||||
|
|
|
@ -18,6 +18,7 @@ type Paste struct {
|
|||
ID string `json:"id"`
|
||||
Created time.Time `json:"created"`
|
||||
|
||||
PKID int
|
||||
UserID int
|
||||
RawVisibility string
|
||||
|
||||
|
@ -56,6 +57,7 @@ func (paste *Paste) Fields() *database.ModelFields {
|
|||
{ "visibility", "visibility", &paste.RawVisibility },
|
||||
|
||||
// Always fetch:
|
||||
{ "id", "", &paste.PKID },
|
||||
{ "sha", "", &paste.ID },
|
||||
{ "user_id", "", &paste.UserID },
|
||||
},
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// XXX: gqlgen bug prevents us from using type URL *url.URL
|
||||
type URL struct {
|
||||
Url *url.URL
|
||||
}
|
||||
|
||||
func (u *URL) UnmarshalGQL(v interface{}) error {
|
||||
raw, ok := v.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("Mail format is a base64-encoded string")
|
||||
}
|
||||
parsed, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Url = parsed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u URL) MarshalGQL(w io.Writer) {
|
||||
data, err := json.Marshal(u.Url.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
w.Write(data)
|
||||
}
|
|
@ -3,6 +3,12 @@
|
|||
scalar Cursor
|
||||
scalar Time
|
||||
scalar Upload
|
||||
# URL from which some secondary data may be retrieved. You must provide the
|
||||
# same Authentication header to this address as you did to the GraphQL resolver
|
||||
# which provided it. The URL is not guaranteed to be consistent for an extended
|
||||
# length of time; applications should submit a new GraphQL query each time they
|
||||
# wish to access the data at the provided URL.
|
||||
scalar URL
|
||||
|
||||
# This is used to decorate fields which are only accessible with a personal
|
||||
# access token, and are not available to clients using OAuth 2.0 access tokens.
|
||||
|
@ -83,7 +89,7 @@ type Paste {
|
|||
type File {
|
||||
filename: String!
|
||||
sha: String!
|
||||
contents: String!
|
||||
contents: URL!
|
||||
}
|
||||
|
||||
# A cursor for enumerating pastes
|
||||
|
|
|
@ -18,6 +18,14 @@ import (
|
|||
sq "github.com/Masterminds/squirrel"
|
||||
)
|
||||
|
||||
func (r *fileResolver) Sha(ctx context.Context, obj *model.File) (string, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
||||
func (r *fileResolver) Contents(ctx context.Context, obj *model.File) (*model.URL, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
||||
func (r *mutationResolver) Create(ctx context.Context, files []*graphql.Upload, visibility model.Visibility) (*model.Paste, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
}
|
||||
|
@ -31,7 +39,38 @@ func (r *mutationResolver) Delete(ctx context.Context, id string) (*model.Paste,
|
|||
}
|
||||
|
||||
func (r *pasteResolver) Files(ctx context.Context, obj *model.Paste) ([]*model.File, error) {
|
||||
panic(fmt.Errorf("not implemented"))
|
||||
var files []*model.File
|
||||
|
||||
if err := database.WithTx(ctx, &sql.TxOptions{
|
||||
Isolation: 0,
|
||||
ReadOnly: true,
|
||||
}, func(tx *sql.Tx) error {
|
||||
file := (&model.File{}).As(`file`)
|
||||
query := database.
|
||||
Select(ctx, file).
|
||||
From(`paste_file file`).
|
||||
Where(`file.paste_id = ?`, obj.PKID)
|
||||
rows, err := query.RunWith(tx).QueryContext(ctx)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var file model.File
|
||||
if err := rows.Scan(database.Scan(ctx, &file)...); err != nil {
|
||||
return err
|
||||
}
|
||||
files = append(files, &file)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (r *pasteResolver) User(ctx context.Context, obj *model.Paste) (model.Entity, error) {
|
||||
|
@ -118,6 +157,9 @@ func (r *userResolver) Pastes(ctx context.Context, obj *model.User, cursor *core
|
|||
return &model.PasteCursor{pastes, cursor}, nil
|
||||
}
|
||||
|
||||
// File returns api.FileResolver implementation.
|
||||
func (r *Resolver) File() api.FileResolver { return &fileResolver{r} }
|
||||
|
||||
// Mutation returns api.MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() api.MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
|
@ -130,6 +172,7 @@ func (r *Resolver) Query() api.QueryResolver { return &queryResolver{r} }
|
|||
// User returns api.UserResolver implementation.
|
||||
func (r *Resolver) User() api.UserResolver { return &userResolver{r} }
|
||||
|
||||
type fileResolver struct{ *Resolver }
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type pasteResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
|
|
Loading…
Reference in New Issue