Add requesting and downloading a file

This commit is contained in:
kolaente 2021-08-29 22:14:48 +02:00
parent 2c00b4976e
commit 279722d943
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
5 changed files with 79 additions and 47 deletions

View File

@ -21,6 +21,7 @@ import (
"os"
"strconv"
"time"
"xorm.io/xorm"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
@ -77,7 +78,18 @@ func Create(f io.Reader, realname string, realsize uint64, a web.Auth) (file *Fi
// CreateWithMime creates a new file from an FileHeader and sets its mime type
func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
s := db.NewSession()
defer s.Close()
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
if err != nil {
_ = s.Rollback()
return
}
return
}
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
// Get and parse the configured file size
var maxSize datasize.ByteSize
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
@ -96,21 +108,13 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
Mime: mime,
}
s := db.NewSession()
defer s.Close()
_, err = s.Insert(file)
if err != nil {
_ = s.Rollback()
return
}
// Save the file to storage with its new ID as path
err = file.Save(f)
if err != nil {
_ = s.Rollback()
return
}
return
}

View File

@ -42,8 +42,10 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
return err
}
tmpFilename := exportDir + strconv.FormatInt(u.ID, 10) + "_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
// Open zip
dumpFile, err := os.Create(exportDir + strconv.FormatInt(u.ID, 64) + "_" + time.Now().Format("2006-01-02_15-03-05") + ".zip")
dumpFile, err := os.Create(tmpFilename)
if err != nil {
return fmt.Errorf("error opening dump file: %s", err)
}
@ -78,12 +80,22 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
return err
}
stat, err := dumpFile.Stat()
// If we reuse the same file again, saving it as a file in Vikunja will save it as a file with 0 bytes in size.
// Closing and reopening does work.
dumpWriter.Close()
dumpFile.Close()
exported, err := os.Open(tmpFilename)
if err != nil {
return err
}
exportFile, err := files.Create(dumpFile, dumpFile.Name(), uint64(stat.Size()), u)
stat, err := exported.Stat()
if err != nil {
return err
}
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
if err != nil {
return err
}
@ -96,7 +108,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
}
// Remove the old file
err = os.Remove(dumpFile.Name())
err = os.Remove(exported.Name())
if err != nil {
return err
}

View File

@ -598,5 +598,6 @@ func (s *HandleUserDataExport) Handle(msg *message.Message) (err error) {
log.Debugf("Done exporting user data for user %d...", event.User.ID)
return sess.Commit()
err = sess.Commit()
return err
}

View File

@ -19,59 +19,47 @@ package v1
import (
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"net/http"
"xorm.io/xorm"
)
func exportHandler(c echo.Context, closure func(*user.User) error) error {
func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err error) {
var pass UserPasswordConfirmation
if err := c.Bind(&pass); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
}
err := c.Validate(pass)
err = c.Validate(pass)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err)
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, err)
}
s := db.NewSession()
s = db.NewSession()
defer s.Close()
err = s.Begin()
if err != nil {
return handler.HandleHTTPError(err, c)
return nil, nil, handler.HandleHTTPError(err, c)
}
u, err := user.GetCurrentUserFromDB(s, c)
u, err = user.GetCurrentUserFromDB(s, c)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
return nil, nil, handler.HandleHTTPError(err, c)
}
err = user.CheckUserPassword(u, pass.Password)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
return nil, nil, handler.HandleHTTPError(err, c)
}
err = closure(u)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
return
}
// RequestUserDataExport is the handler to request a user data export
@ -86,15 +74,25 @@ func exportHandler(c echo.Context, closure func(*user.User) error) error {
// @Failure 500 {object} models.Message "Internal server error."
// @Router /user/export/request [post]
func RequestUserDataExport(c echo.Context) error {
err := exportHandler(c, func(u *user.User) error {
return events.Dispatch(&models.UserDataExportRequestedEvent{
User: u,
})
})
s, u, err := checkExportRequest(c)
if err != nil {
return err
}
err = events.Dispatch(&models.UserDataExportRequestedEvent{
User: u,
})
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
}
@ -110,13 +108,30 @@ func RequestUserDataExport(c echo.Context) error {
// @Failure 500 {object} models.Message "Internal server error."
// @Router /user/export/download [post]
func DownloadUserDataExport(c echo.Context) error {
err := exportHandler(c, func(u *user.User) error {
})
s, u, err := checkExportRequest(c)
if err != nil {
return err
}
// TODO
err = s.Commit()
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
// Download
exportFile := &files.File{ID: u.ExportFileID}
err = exportFile.LoadFileByID()
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
var buf []byte
_, err = exportFile.File.Read(buf)
if err != nil {
return handler.HandleHTTPError(err, c)
}
return c.Blob(http.StatusOK, exportFile.Mime, buf)
}

View File

@ -304,7 +304,7 @@ func registerAPIRoutes(a *echo.Group) {
u.PUT("/settings/avatar/upload", apiv1.UploadAvatar)
u.POST("/settings/general", apiv1.UpdateGeneralUserSettings)
u.POST("/export/request", apiv1.RequestUserDataExport)
u.POST("/export/dowload", apiv1.DownloadUserDataExport)
u.POST("/export/download", apiv1.DownloadUserDataExport)
if config.ServiceEnableTotp.GetBool() {
u.GET("/settings/totp", apiv1.UserTOTP)