Browse Source

Added File model (still missing edit/remove), fixed uploading to space always failing, added /upload endpoint to rpdata-grapihql server

1.0
Gisle Aune 6 years ago
parent
commit
1978883cd7
  1. 39
      cmd/rpdata-graphiql/main.go
  2. 9
      internal/store/space.go
  3. 138
      model/file/file.go

39
cmd/rpdata-graphiql/main.go

@ -1,12 +1,14 @@
package main
import (
"encoding/json"
"log"
"net/http"
"git.aiterp.net/rpdata/api/internal/session"
"git.aiterp.net/rpdata/api/internal/store"
"git.aiterp.net/rpdata/api/loader"
"git.aiterp.net/rpdata/api/model/file"
logModel "git.aiterp.net/rpdata/api/model/log"
"git.aiterp.net/rpdata/api/resolver"
"git.aiterp.net/rpdata/api/schema"
@ -50,6 +52,43 @@ func main() {
relayHandler.ServeHTTP(w, r)
})
http.HandleFunc("/upload", func(w http.ResponseWriter, r *http.Request) {
r = session.Load(w, r)
sess := session.FromContext(r.Context())
user := sess.User()
if user == nil || !user.Permitted("file.upload") {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
if err := r.ParseMultipartForm(16384); err != nil {
http.Error(w, "Internal (1): "+err.Error(), http.StatusInternalServerError)
return
}
formFile, header, err := r.FormFile("file")
if err != nil || header == nil || formFile == nil {
http.Error(w, "No file provided", http.StatusBadRequest)
return
}
file, err := file.Upload(r.Context(), header.Filename, header.Header.Get("Content-Type"), user.ID, header.Size, formFile)
if err != nil {
http.Error(w, "Internal (2): "+err.Error(), http.StatusInternalServerError)
return
}
json, err := json.Marshal(file)
if err != nil {
http.Error(w, "Internal (3): "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(json)
})
log.Fatal(http.ListenAndServe(":17000", nil))
}

9
internal/store/space.go

@ -59,7 +59,7 @@ func UploadFile(ctx context.Context, folder string, name string, mimeType string
return "", err
}
_, err = spaceClient.StatObject(spaceBucket, path, minio.StatObjectOptions{})
_, err = spaceClient.StatObject(spaceBucket, spaceRoot+"/"+path, minio.StatObjectOptions{})
if err != nil {
return "", err
}
@ -67,6 +67,13 @@ func UploadFile(ctx context.Context, folder string, name string, mimeType string
return path, nil
}
// RemoveFile removes a file from the space
func RemoveFile(folder string, name string) error {
path := folder + "/" + name
return spaceClient.RemoveObject(spaceBucket, spaceRoot+"/"+path)
}
// DownloadFile opens a file for download, using the same path format as the UploadFile function. Remember to Close it!
func DownloadFile(ctx context.Context, path string) (io.ReadCloser, error) {
return spaceClient.GetObjectWithContext(ctx, spaceBucket, spaceRoot+"/"+path, minio.GetObjectOptions{})

138
model/file/file.go

@ -0,0 +1,138 @@
package file
import (
"context"
"crypto/rand"
"encoding/binary"
"io"
"strconv"
"time"
"git.aiterp.net/rpdata/api/internal/store"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
)
var fileCollection *mgo.Collection
// 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"`
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"`
}
// Upload adds a file to the space.
func Upload(ctx context.Context, name, mimeType, author string, size int64, input io.Reader) (File, error) {
if name == "" {
date := time.Now().UTC().Format("Jan 02 2006 15:04:05 MST")
name = "Unnamed file (" + date + ")"
}
if mimeType == "" {
mimeType = "binary/octet-stream"
}
id := makeFileID()
path, err := store.UploadFile(ctx, "files", id, mimeType, input, size)
if err != nil {
return File{}, err
}
file := File{
ID: id,
Time: time.Now(),
Public: false,
Author: author,
Name: name,
MimeType: mimeType,
Size: size,
URL: store.URLFromPath(path),
}
err = fileCollection.Insert(file)
if err != nil {
return File{}, err
}
return file, nil
}
// FindID finds a file by ID
func FindID(id string) (File, error) {
file := File{}
err := fileCollection.FindId(id).One(&file)
if err != nil {
return File{}, err
}
return file, nil
}
// ListFiles 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.
func ListFiles(author string, public bool, mimeTypes []string) ([]File, error) {
query := bson.M{}
if public {
query["$or"] = []bson.M{
bson.M{"author": author},
bson.M{"public": true},
}
} else {
query["author"] = author
}
if len(mimeTypes) > 0 {
query["mimeTypes"] = bson.M{"$in": mimeTypes}
}
return listFiles(query)
}
func listFiles(query interface{}) ([]File, error) {
list := make([]File, 0, 32)
err := fileCollection.Find(query).All(&list)
if err != nil {
return nil, err
}
return list, nil
}
// makeFileID makes a random file ID that's 32 characters long
func makeFileID() 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) {
fileCollection = db.C("file.headers")
fileCollection.EnsureIndexKey("author")
})
}
Loading…
Cancel
Save