From bac57bc378bd92aec48136fad2578ccf5f3216a0 Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Sun, 16 Sep 2018 11:30:18 +0200 Subject: [PATCH] graph2: Added File type and queries. --- graph2/gqlgen.yml | 4 ++ graph2/graph.go | 4 ++ graph2/queries/file.go | 34 +++++++++++++++ graph2/schema/root.gql | 7 ++++ graph2/schema/types/File.gql | 52 +++++++++++++++++++++++ graph2/types/file.go | 16 +++++++ models/file.go | 16 +++++++ models/files/db.go | 81 ++++++++++++++++++++++++++++++++++++ models/files/find.go | 11 +++++ models/files/list.go | 36 ++++++++++++++++ 10 files changed, 261 insertions(+) create mode 100644 graph2/queries/file.go create mode 100644 graph2/schema/types/File.gql create mode 100644 graph2/types/file.go create mode 100644 models/file.go create mode 100644 models/files/db.go create mode 100644 models/files/find.go create mode 100644 models/files/list.go diff --git a/graph2/gqlgen.yml b/graph2/gqlgen.yml index 33e388c..2c8567e 100644 --- a/graph2/gqlgen.yml +++ b/graph2/gqlgen.yml @@ -34,5 +34,9 @@ models: fields: fictionalDate: resolver: true + File: + model: git.aiterp.net/rpdata/api/models.File + FilesFilter: + model: git.aiterp.net/rpdata/api/models/files.Filter Date: model: git.aiterp.net/rpdata/api/models/scalars.Date \ No newline at end of file diff --git a/graph2/graph.go b/graph2/graph.go index 220e580..1bad40b 100644 --- a/graph2/graph.go +++ b/graph2/graph.go @@ -29,3 +29,7 @@ func (r *rootResolver) Log() LogResolver { func (r *rootResolver) Chapter() ChapterResolver { return &types.ChapterResolver } + +func (r *rootResolver) File() FileResolver { + return &types.FileResolver +} diff --git a/graph2/queries/file.go b/graph2/queries/file.go new file mode 100644 index 0000000..67e521c --- /dev/null +++ b/graph2/queries/file.go @@ -0,0 +1,34 @@ +package queries + +import ( + "context" + + "git.aiterp.net/rpdata/api/internal/auth" + "git.aiterp.net/rpdata/api/models" + "git.aiterp.net/rpdata/api/models/files" +) + +func (r *resolver) File(ctx context.Context, id string) (models.File, error) { + return files.FindID(id) +} + +func (r *resolver) Files(ctx context.Context, filter *files.Filter) ([]models.File, error) { + token := auth.TokenFromContext(ctx) + + if filter == nil { + filter = &files.Filter{} + } + + // Only allow users to view public files that are not their own. + if token != nil { + if filter.Public == nil || *filter.Public == false { + filter.Author = &token.UserID + } + } else { + filter.Public = &trueValue + } + + return files.List(filter) +} + +var trueValue = true diff --git a/graph2/schema/root.gql b/graph2/schema/root.gql index 7d8dee7..a04b67e 100644 --- a/graph2/schema/root.gql +++ b/graph2/schema/root.gql @@ -37,6 +37,13 @@ type Query { # Find all distinct tags used in stories tags: [Tag!]! + + + # Find file by ID + file(id: String!): File! + + # Find files + files(filter: FilesFilter): [File!]! } # A Date represents a RFC3339 encoded date with up to millisecond precision. diff --git a/graph2/schema/types/File.gql b/graph2/schema/types/File.gql new file mode 100644 index 0000000..0eafcc3 --- /dev/null +++ b/graph2/schema/types/File.gql @@ -0,0 +1,52 @@ +# A File contains information about a file and where to download it. +type File { + # The file's unique ID + id: String! + + # The kind of file. Most will be "upload", but some that are ported from the wiki will have other values for this. + kind: String! + + # The time of uploading + time: Date! + + # Whether the file is publicly listable. Someone with knowledge of the ID + # will still be able to view it, however. + public: Boolean! + + # The file's name + name: String! + + # The MIME type of the file + mimeType: String! + + # The file's size in bytes + size: Int! + + # The uploader + author: String! + + # The URL where the file is hosted + url: String +} + +# Filter for the files quiery. +input FilesFilter { + # If set, this will limit the results to either public or private files. + public: Boolean + + # Limit the MIME types of the files. + mimeType: [String!] +} + +# Input for editFile mutation +input EditFileInput { + # The file's unique ID + id: String! + + # Whether the file is publicly listable. Someone with knowledge of the ID + # will still be able to view it, however. + public: Boolean + + # The file's name + name: String +} \ No newline at end of file diff --git a/graph2/types/file.go b/graph2/types/file.go new file mode 100644 index 0000000..3cc8c9e --- /dev/null +++ b/graph2/types/file.go @@ -0,0 +1,16 @@ +package types + +import ( + "context" + + "git.aiterp.net/rpdata/api/models" +) + +type fileResolver struct{} + +func (r *fileResolver) Size(ctx context.Context, file *models.File) (int, error) { + return int(file.Size), nil +} + +// FileResolver is a resolver +var FileResolver fileResolver diff --git a/models/file.go b/models/file.go new file mode 100644 index 0000000..97a9013 --- /dev/null +++ b/models/file.go @@ -0,0 +1,16 @@ +package models + +import "time" + +// A File is a record of a file stored in the Space. +type File struct { + ID string `bson:"_id" json:"id"` + Time time.Time `bson:"time" json:"time"` + Kind string `bson:"kind" json:"kind"` + Public bool `bson:"public" json:"public"` + Name string `bson:"name" json:"name"` + MimeType string `bson:"mimeType" json:"mimeType"` + Size int64 `bson:"size" json:"size"` + Author string `bson:"author" json:"author"` + URL string `bson:"url,omitempty" json:"url,omitempty"` +} diff --git a/models/files/db.go b/models/files/db.go new file mode 100644 index 0000000..7f0af53 --- /dev/null +++ b/models/files/db.go @@ -0,0 +1,81 @@ +package files + +import ( + "crypto/rand" + "encoding/binary" + "strconv" + "time" + + "git.aiterp.net/rpdata/api/internal/store" + "git.aiterp.net/rpdata/api/models" + "github.com/globalsign/mgo" +) + +var collection *mgo.Collection + +func find(query interface{}) (models.File, error) { + file := models.File{} + + err := collection.Find(query).One(&file) + if err != nil { + return models.File{}, err + } + + return file, nil +} + +func list(query interface{}) ([]models.File, error) { + list := make([]models.File, 0, 32) + + err := collection.Find(query).Sort("-time").All(&list) + if err != nil { + return nil, err + } + + return list, nil +} + +// makeID makes a random file ID that's 32 characters long +func makeID() string { + result := "F" + strconv.FormatInt(time.Now().UnixNano(), 36) + offset := 0 + data := make([]byte, 32) + + rand.Read(data) + for len(result) < 32 { + result += strconv.FormatUint(binary.LittleEndian.Uint64(data[offset:]), 36) + offset += 8 + + if offset >= 32 { + rand.Read(data) + offset = 0 + } + } + + return result[:32] +} + +func init() { + store.HandleInit(func(db *mgo.Database) { + collection = db.C("file.headers") + + collection.EnsureIndexKey("author") + collection.EnsureIndexKey("public") + collection.EnsureIndexKey("kind", "name", "author") + collection.EnsureIndexKey("author", "public") + collection.EnsureIndexKey("kind") + }) +} + +var allowdMimeTypes = map[string]bool{ + "": false, + "image/jpeg": true, + "image/png": true, + "image/gif": true, + "text/plain": true, + "application/json": true, + "application/pdf": false, + "binary/octet-stream": false, + "video/mp4": false, + "audio/mp3": false, +} diff --git a/models/files/find.go b/models/files/find.go new file mode 100644 index 0000000..6fe9d42 --- /dev/null +++ b/models/files/find.go @@ -0,0 +1,11 @@ +package files + +import ( + "git.aiterp.net/rpdata/api/models" + "github.com/globalsign/mgo/bson" +) + +// FindID finds a file by ID +func FindID(id string) (models.File, error) { + return find(bson.M{"_id": id}) +} diff --git a/models/files/list.go b/models/files/list.go new file mode 100644 index 0000000..aa5156c --- /dev/null +++ b/models/files/list.go @@ -0,0 +1,36 @@ +package files + +import ( + "git.aiterp.net/rpdata/api/models" + "github.com/globalsign/mgo/bson" +) + +// Filter for files.List +type Filter struct { + Author *string + Public *bool + MimeType []string +} + +// List lists files according to the standard lookup. By default it's just the author's own files, +// but if `public` is true it will alos include files made public by other authors. If `mimeTypes` contains +// any, it will limit the results to that. If `author` is empty, it will only list public files +func List(filter *Filter) ([]models.File, error) { + query := bson.M{} + + if filter != nil { + if filter.Author != nil { + query["author"] = *filter.Author + } + + if filter.Public != nil { + query["public"] = *filter.Public + } + + if len(filter.MimeType) > 0 { + query["mimeTypes"] = bson.M{"$in": filter.MimeType} + } + } + + return list(query) +}