294 lines
7.3 KiB
Go
294 lines
7.3 KiB
Go
package module
|
|
|
|
// import (
|
|
// "context"
|
|
// "fmt"
|
|
// "io"
|
|
// "mime/multipart"
|
|
// "os"
|
|
// "path/filepath"
|
|
// "time"
|
|
|
|
// "forge.cadoles.com/arcad/edge/pkg/app"
|
|
// "forge.cadoles.com/arcad/edge/pkg/bus"
|
|
// "github.com/dop251/goja"
|
|
// "github.com/genjidb/genji"
|
|
// "github.com/genjidb/genji/database"
|
|
// "github.com/genjidb/genji/document"
|
|
// "github.com/google/uuid"
|
|
// "github.com/pkg/errors"
|
|
// "github.com/spf13/afero"
|
|
// "gitlab.com/wpetit/goweb/logger"
|
|
// )
|
|
|
|
// const (
|
|
// fileKeyID = "id"
|
|
// collectionFiles = "files"
|
|
// )
|
|
|
|
// type FileEntry struct {
|
|
// ID string
|
|
// Filename string
|
|
// Size int64
|
|
// ContentType string
|
|
// CreatedAt string
|
|
// Metadata map[string]interface{}
|
|
// }
|
|
|
|
// type FileModule struct {
|
|
// appID app.ID
|
|
// backend *app.Server
|
|
// bus bus.Bus
|
|
// db *AppDatabaseMixin
|
|
// dataDir string
|
|
// fs afero.Fs
|
|
// }
|
|
|
|
// func (m *FileModule) Name() string {
|
|
// return "file"
|
|
// }
|
|
|
|
// func (m *FileModule) Export(export *goja.Object) {
|
|
// // if err := export.Set("ls", m.ls); err != nil {
|
|
// // panic(errors.Wrap(err, "could not set 'save' function"))
|
|
// // }
|
|
// }
|
|
|
|
// func (m *FileModule) logAndPanic(msg string, err error) {
|
|
// err = errors.Wrap(err, msg)
|
|
// logger.Error(context.Background(), msg, logger.E(err))
|
|
// panic(errors.WithStack(err))
|
|
// }
|
|
|
|
// func (m *FileModule) handleMessages() {
|
|
// ctx := context.Background()
|
|
|
|
// ns := createAppMessageNamespace(m.appID)
|
|
|
|
// go func() {
|
|
// err := m.bus.Reply(ctx, ns, MessageTypeUploadRequest, func(msg bus.Message) (bus.Message, error) {
|
|
// uploadRequest, ok := msg.(*MessageUploadRequest)
|
|
// if !ok {
|
|
// return nil, errors.Wrapf(bus.ErrUnexpectedMessage, "expected message upload request, got '%s'", msg.MessageType())
|
|
// }
|
|
|
|
// res, err := m.handleUploadRequest(uploadRequest)
|
|
// if err != nil {
|
|
// logger.Error(ctx, "could not handle upload request", logger.E(errors.WithStack(err)))
|
|
|
|
// return nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// logger.Debug(ctx, "upload request response", logger.F("response", res))
|
|
|
|
// return res, nil
|
|
// })
|
|
// if err != nil {
|
|
// panic(errors.WithStack(err))
|
|
// }
|
|
// }()
|
|
|
|
// err := m.bus.Reply(ctx, ns, MessageTypeDownloadRequest, func(msg bus.Message) (bus.Message, error) {
|
|
// downloadRequest, ok := msg.(*MessageDownloadRequest)
|
|
// if !ok {
|
|
// return nil, errors.Wrapf(bus.ErrUnexpectedMessage, "expected message download request, got '%s'", msg.MessageType())
|
|
// }
|
|
|
|
// res, err := m.handleDownloadRequest(downloadRequest)
|
|
// if err != nil {
|
|
// logger.Error(ctx, "could not handle download request", logger.E(errors.WithStack(err)))
|
|
|
|
// return nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// return res, nil
|
|
// })
|
|
// if err != nil {
|
|
// panic(errors.WithStack(err))
|
|
// }
|
|
// }
|
|
|
|
// func (m *FileModule) handleUploadRequest(req *MessageUploadRequest) (*MessageUploadResponse, error) {
|
|
// fileInfo := map[string]interface{}{
|
|
// "filename": req.Header.Filename,
|
|
// "contentType": req.Header.Header.Get("Content-Type"),
|
|
// "size": req.Header.Size,
|
|
// "metadata": req.Metadata,
|
|
// }
|
|
|
|
// res := NewMessageUploadResponse(req.AppID, req.UserID, req.RequestID)
|
|
// fileID := uuid.New().String()
|
|
|
|
// result, err := m.backend.ExecFuncByName("onFileUpload", req.UserID, fileID, fileInfo)
|
|
// if err != nil {
|
|
// if errors.Is(err, app.ErrFuncDoesNotExist) {
|
|
// res.Allow = false
|
|
|
|
// return res, nil
|
|
// }
|
|
|
|
// return nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// res.Allow = result.ToBoolean()
|
|
|
|
// if res.Allow {
|
|
// if err := m.saveFile(fileID, req.UserID, req.Header, req.File, req.Metadata); err != nil {
|
|
// return nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// res.FileID = fileID
|
|
// }
|
|
|
|
// return res, nil
|
|
// }
|
|
|
|
// func (m *FileModule) saveFile(fileID string, header *multipart.FileHeader, file multipart.File, metadata map[string]interface{}) error {
|
|
// err := m.db.WithCollectionTx(collectionFiles, func(tx *genji.Tx) error {
|
|
// entry := &FileEntry{
|
|
// ID: fileID,
|
|
// Filename: header.Filename,
|
|
// Size: header.Size,
|
|
// ContentType: header.Header.Get("Content-Type"),
|
|
// CreatedAt: time.Now().UTC().String(),
|
|
// Metadata: metadata,
|
|
// }
|
|
|
|
// insertQuery := fmt.Sprintf("INSERT INTO `%s` VALUES ?", collectionFiles)
|
|
// if err := tx.Exec(insertQuery, &entry); err != nil {
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// fileDir := m.getFileDir(fileID)
|
|
|
|
// if err := m.fs.MkdirAll(fileDir, 0o755); err != nil {
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// filePath := filepath.Join(fileDir, fileID)
|
|
|
|
// newFile, err := m.fs.Create(filePath)
|
|
// if err != nil {
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// defer newFile.Close()
|
|
|
|
// if _, err := io.Copy(newFile, file); err != nil {
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// return nil
|
|
// })
|
|
// if err != nil {
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// return nil
|
|
// }
|
|
|
|
// func (m *FileModule) handleDownloadRequest(req *MessageDownloadRequest) (*MessageDownloadResponse, error) {
|
|
// res := NewMessageDownloadResponse(req.AppID, req.UserID, req.RequestID)
|
|
|
|
// result, err := m.backend.ExecFuncByName("onFileDownload", req.UserID, req.FileID)
|
|
// if err != nil {
|
|
// if errors.Is(err, app.ErrFuncDoesNotExist) {
|
|
// res.Allow = false
|
|
|
|
// return res, nil
|
|
// }
|
|
|
|
// return nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// res.Allow = result.ToBoolean()
|
|
|
|
// file, fileEntry, err := m.openFile(req.FileID)
|
|
// if err != nil && !os.IsNotExist(errors.Cause(err)) {
|
|
// return nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// if file != nil {
|
|
// res.File = file
|
|
// }
|
|
|
|
// if fileEntry != nil {
|
|
// res.Filename = fileEntry.Filename
|
|
// res.ContentType = fileEntry.ContentType
|
|
// res.Size = fileEntry.Size
|
|
// }
|
|
|
|
// return res, nil
|
|
// }
|
|
|
|
// func (m *FileModule) openFile(fileID string) (afero.File, *FileEntry, error) {
|
|
// var (
|
|
// fileEntry *FileEntry
|
|
// file afero.File
|
|
// )
|
|
|
|
// err := m.db.WithCollectionTx(collectionFiles, func(tx *genji.Tx) error {
|
|
// selectQuery := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ?", collectionFiles)
|
|
|
|
// doc, err := tx.QueryDocument(selectQuery, fileID)
|
|
// if err != nil {
|
|
// if errors.Is(err, database.ErrDocumentNotFound) {
|
|
// return nil
|
|
// }
|
|
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// fileEntry = &FileEntry{}
|
|
|
|
// if err := document.StructScan(doc, fileEntry); err != nil {
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// fileDir := m.getFileDir(fileID)
|
|
// filePath := filepath.Join(fileDir, fileID)
|
|
|
|
// file, err = m.fs.Open(filePath)
|
|
// if err != nil {
|
|
// file = nil
|
|
|
|
// return errors.WithStack(err)
|
|
// }
|
|
|
|
// return nil
|
|
// })
|
|
// if err != nil {
|
|
// return nil, nil, errors.WithStack(err)
|
|
// }
|
|
|
|
// return file, fileEntry, nil
|
|
// }
|
|
|
|
// func (m *FileModule) getFileDir(fileID string) string {
|
|
// return filepath.Join(m.dataDir, string(m.appID), "files", fileID[0:2], fileID[2:4], fileID[4:6])
|
|
// }
|
|
|
|
// func FileModuleFactory(dataDir string, bus bus.Bus) app.ServerModuleFactory {
|
|
// return func(appID app.ID, backend *app.Server) app.ServerModule {
|
|
// var fs afero.Fs
|
|
// if dataDir == inMemory {
|
|
// fs = afero.NewMemMapFs()
|
|
// } else {
|
|
// fs = afero.NewOsFs()
|
|
// }
|
|
|
|
// mod := &FileModule{
|
|
// dataDir: dataDir,
|
|
// appID: appID,
|
|
// bus: bus,
|
|
// backend: backend,
|
|
// db: NewAppDatabaseMixin(dataDir, "file.db", appID),
|
|
// fs: fs,
|
|
// }
|
|
|
|
// go mod.handleMessages()
|
|
|
|
// return mod
|
|
// }
|
|
// }
|