2019-08-16 21:27:24 +00:00
|
|
|
package writefreely
|
|
|
|
|
|
|
|
import (
|
2020-01-14 16:59:30 +00:00
|
|
|
"encoding/json"
|
2019-08-16 21:27:24 +00:00
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2019-08-17 23:18:40 +00:00
|
|
|
"strings"
|
2020-01-14 16:59:30 +00:00
|
|
|
"time"
|
2019-08-16 21:27:24 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/writeas/impart"
|
|
|
|
wfimport "github.com/writeas/import"
|
|
|
|
"github.com/writeas/web-core/log"
|
|
|
|
)
|
|
|
|
|
|
|
|
func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
|
|
|
// Fetch extra user data
|
2020-01-09 16:16:26 +00:00
|
|
|
p := NewUserPage(app, r, u, "Import Posts", nil)
|
2019-08-16 21:27:24 +00:00
|
|
|
|
2020-01-07 21:51:40 +00:00
|
|
|
c, err := app.db.GetCollections(u, app.Config().App.Host)
|
2019-08-16 21:27:24 +00:00
|
|
|
if err != nil {
|
|
|
|
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
|
|
|
|
}
|
|
|
|
|
|
|
|
d := struct {
|
|
|
|
*UserPage
|
|
|
|
Collections *[]Collection
|
|
|
|
Flashes []template.HTML
|
2019-08-17 23:18:40 +00:00
|
|
|
Message string
|
|
|
|
InfoMsg bool
|
2019-08-16 21:27:24 +00:00
|
|
|
}{
|
|
|
|
UserPage: p,
|
|
|
|
Collections: c,
|
|
|
|
Flashes: []template.HTML{},
|
|
|
|
}
|
|
|
|
|
|
|
|
flashes, _ := getSessionFlashes(app, w, r, nil)
|
|
|
|
for _, flash := range flashes {
|
2019-08-17 23:18:40 +00:00
|
|
|
if strings.HasPrefix(flash, "SUCCESS: ") {
|
|
|
|
d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
|
|
|
|
} else if strings.HasPrefix(flash, "INFO: ") {
|
|
|
|
d.Message = strings.TrimPrefix(flash, "INFO: ")
|
|
|
|
d.InfoMsg = true
|
|
|
|
} else {
|
|
|
|
d.Flashes = append(d.Flashes, template.HTML(flash))
|
|
|
|
}
|
2019-08-16 21:27:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
showUserPage(w, "import", d)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
|
|
|
// limit 10MB per submission
|
|
|
|
r.ParseMultipartForm(10 << 20)
|
2020-01-09 17:08:06 +00:00
|
|
|
|
|
|
|
collAlias := r.PostFormValue("collection")
|
|
|
|
coll := &Collection{
|
|
|
|
ID: 0,
|
|
|
|
}
|
|
|
|
var err error
|
|
|
|
if collAlias != "" {
|
|
|
|
coll, err = app.db.GetCollection(collAlias)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Unable to get collection for import: %s", err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Only allow uploading to collection if current user is owner
|
|
|
|
if coll.OwnerID != u.ID {
|
|
|
|
err := ErrUnauthorizedGeneral
|
|
|
|
_ = addSessionFlash(app, w, r, err.Message, nil)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
coll.hostName = app.cfg.App.Host
|
|
|
|
}
|
|
|
|
|
2020-01-14 16:59:30 +00:00
|
|
|
fileDates := make(map[string]int64)
|
|
|
|
err = json.Unmarshal([]byte(r.FormValue("fileDates")), &fileDates)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("invalid form data for file dates: %v", err)
|
|
|
|
return impart.HTTPError{http.StatusBadRequest, "form data for file dates was invalid"}
|
|
|
|
}
|
2019-08-16 21:27:24 +00:00
|
|
|
files := r.MultipartForm.File["files"]
|
|
|
|
var fileErrs []error
|
2019-08-17 23:18:40 +00:00
|
|
|
filesSubmitted := len(files)
|
|
|
|
var filesImported int
|
2019-08-16 21:27:24 +00:00
|
|
|
for _, formFile := range files {
|
2020-01-09 17:36:58 +00:00
|
|
|
fname := ""
|
|
|
|
ok := func() bool {
|
|
|
|
file, err := formFile.Open()
|
|
|
|
if err != nil {
|
2020-01-09 18:29:07 +00:00
|
|
|
fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename))
|
|
|
|
log.Error("import file: open from form: %v", err)
|
2020-01-09 17:36:58 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
defer file.Close()
|
2019-08-16 21:27:24 +00:00
|
|
|
|
2023-07-10 09:55:04 +00:00
|
|
|
tempFile, err := os.CreateTemp("", "post-upload-*.txt")
|
2020-01-09 17:36:58 +00:00
|
|
|
if err != nil {
|
2020-01-09 18:29:07 +00:00
|
|
|
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
|
|
|
|
log.Error("import file: create temp file %s: %v", formFile.Filename, err)
|
2020-01-09 17:36:58 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
defer tempFile.Close()
|
2019-08-16 21:27:24 +00:00
|
|
|
|
2020-01-09 17:36:58 +00:00
|
|
|
_, err = io.Copy(tempFile, file)
|
|
|
|
if err != nil {
|
2020-01-09 18:29:07 +00:00
|
|
|
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
|
|
|
|
log.Error("import file: copy to temp location %s: %v", formFile.Filename, err)
|
2020-01-09 17:36:58 +00:00
|
|
|
return false
|
|
|
|
}
|
2019-08-16 21:27:24 +00:00
|
|
|
|
2020-01-09 17:36:58 +00:00
|
|
|
info, err := tempFile.Stat()
|
|
|
|
if err != nil {
|
2020-01-09 18:29:07 +00:00
|
|
|
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
|
|
|
|
log.Error("import file: stat temp file %s: %v", formFile.Filename, err)
|
2020-01-09 17:36:58 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
fname = info.Name()
|
|
|
|
return true
|
|
|
|
}()
|
|
|
|
if !ok {
|
2019-08-16 21:27:24 +00:00
|
|
|
continue
|
|
|
|
}
|
2020-01-09 17:36:58 +00:00
|
|
|
|
|
|
|
post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname))
|
2019-08-16 21:27:24 +00:00
|
|
|
if err == wfimport.ErrEmptyFile {
|
|
|
|
// not a real error so don't log
|
|
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
|
|
|
|
continue
|
2019-08-26 21:53:05 +00:00
|
|
|
} else if err == wfimport.ErrInvalidContentType {
|
|
|
|
// same as above
|
|
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
|
|
|
|
continue
|
2019-08-16 21:27:24 +00:00
|
|
|
} else if err != nil {
|
|
|
|
fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
|
|
|
|
log.Error("import textfile: file to post: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-01-09 17:08:06 +00:00
|
|
|
if collAlias != "" {
|
|
|
|
post.Collection = collAlias
|
2019-08-16 21:27:24 +00:00
|
|
|
}
|
2020-01-14 17:24:57 +00:00
|
|
|
dateTime := time.Unix(fileDates[formFile.Filename], 0)
|
2020-01-14 16:59:30 +00:00
|
|
|
post.Created = &dateTime
|
2019-08-21 21:43:05 +00:00
|
|
|
created := post.Created.Format("2006-01-02T15:04:05Z")
|
2019-08-16 21:27:24 +00:00
|
|
|
submittedPost := SubmittedPost{
|
|
|
|
Title: &post.Title,
|
|
|
|
Content: &post.Content,
|
|
|
|
Font: "norm",
|
2019-08-21 21:43:05 +00:00
|
|
|
Created: &created,
|
2019-08-16 21:27:24 +00:00
|
|
|
}
|
2019-08-19 16:06:56 +00:00
|
|
|
rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
|
|
|
|
if err != nil {
|
|
|
|
fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
|
|
|
|
log.Error("import textfile: create db post: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-01-09 18:29:30 +00:00
|
|
|
// Federate post, if necessary
|
|
|
|
if app.cfg.App.Federation && coll.ID > 0 {
|
2019-08-19 16:06:56 +00:00
|
|
|
go federatePost(
|
|
|
|
app,
|
|
|
|
&PublicPost{
|
|
|
|
Post: rp,
|
|
|
|
Collection: &CollectionObj{
|
|
|
|
Collection: *coll,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
coll.ID,
|
|
|
|
false,
|
|
|
|
)
|
2019-08-16 21:27:24 +00:00
|
|
|
}
|
2019-08-17 23:18:40 +00:00
|
|
|
filesImported++
|
2019-08-16 21:27:24 +00:00
|
|
|
}
|
|
|
|
if len(fileErrs) != 0 {
|
|
|
|
_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil)
|
|
|
|
}
|
|
|
|
|
2019-08-17 23:18:40 +00:00
|
|
|
if filesImported == filesSubmitted {
|
|
|
|
verb := "posts"
|
|
|
|
if filesSubmitted == 1 {
|
|
|
|
verb = "post"
|
|
|
|
}
|
|
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, verb), nil)
|
|
|
|
} else if filesImported > 0 {
|
|
|
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
|
|
|
|
}
|
2019-08-16 21:27:24 +00:00
|
|
|
return impart.HTTPError{http.StatusFound, "/me/import"}
|
|
|
|
}
|