Added File model (still missing edit/remove), fixed uploading to space always failing, added /upload endpoint to rpdata-grapihql server
This commit is contained in:
@@ -1,12 +1,14 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.aiterp.net/rpdata/api/internal/session"
|
"git.aiterp.net/rpdata/api/internal/session"
|
||||||
"git.aiterp.net/rpdata/api/internal/store"
|
"git.aiterp.net/rpdata/api/internal/store"
|
||||||
"git.aiterp.net/rpdata/api/loader"
|
"git.aiterp.net/rpdata/api/loader"
|
||||||
|
"git.aiterp.net/rpdata/api/model/file"
|
||||||
logModel "git.aiterp.net/rpdata/api/model/log"
|
logModel "git.aiterp.net/rpdata/api/model/log"
|
||||||
"git.aiterp.net/rpdata/api/resolver"
|
"git.aiterp.net/rpdata/api/resolver"
|
||||||
"git.aiterp.net/rpdata/api/schema"
|
"git.aiterp.net/rpdata/api/schema"
|
||||||
@@ -50,6 +52,43 @@ func main() {
|
|||||||
relayHandler.ServeHTTP(w, r)
|
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))
|
log.Fatal(http.ListenAndServe(":17000", nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func UploadFile(ctx context.Context, folder string, name string, mimeType string
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = spaceClient.StatObject(spaceBucket, path, minio.StatObjectOptions{})
|
_, err = spaceClient.StatObject(spaceBucket, spaceRoot+"/"+path, minio.StatObjectOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,13 @@ func UploadFile(ctx context.Context, folder string, name string, mimeType string
|
|||||||
return path, nil
|
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!
|
// 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) {
|
func DownloadFile(ctx context.Context, path string) (io.ReadCloser, error) {
|
||||||
return spaceClient.GetObjectWithContext(ctx, spaceBucket, spaceRoot+"/"+path, minio.GetObjectOptions{})
|
return spaceClient.GetObjectWithContext(ctx, spaceBucket, spaceRoot+"/"+path, minio.GetObjectOptions{})
|
||||||
|
|||||||
@@ -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")
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user