A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

195 lines
5.3 KiB

  1. package writefreely
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "html/template"
  6. "io"
  7. "net/http"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/hashicorp/go-multierror"
  13. "github.com/writeas/impart"
  14. wfimport "github.com/writeas/import"
  15. "github.com/writeas/web-core/log"
  16. )
  17. func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
  18. // Fetch extra user data
  19. p := NewUserPage(app, r, u, "Import Posts", nil)
  20. c, err := app.db.GetCollections(u, app.Config().App.Host)
  21. if err != nil {
  22. return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
  23. }
  24. d := struct {
  25. *UserPage
  26. Collections *[]Collection
  27. Flashes []template.HTML
  28. Message string
  29. InfoMsg bool
  30. }{
  31. UserPage: p,
  32. Collections: c,
  33. Flashes: []template.HTML{},
  34. }
  35. flashes, _ := getSessionFlashes(app, w, r, nil)
  36. for _, flash := range flashes {
  37. if strings.HasPrefix(flash, "SUCCESS: ") {
  38. d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
  39. } else if strings.HasPrefix(flash, "INFO: ") {
  40. d.Message = strings.TrimPrefix(flash, "INFO: ")
  41. d.InfoMsg = true
  42. } else {
  43. d.Flashes = append(d.Flashes, template.HTML(flash))
  44. }
  45. }
  46. showUserPage(w, "import", d)
  47. return nil
  48. }
  49. func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
  50. // limit 10MB per submission
  51. r.ParseMultipartForm(10 << 20)
  52. collAlias := r.PostFormValue("collection")
  53. coll := &Collection{
  54. ID: 0,
  55. }
  56. var err error
  57. if collAlias != "" {
  58. coll, err = app.db.GetCollection(collAlias)
  59. if err != nil {
  60. log.Error("Unable to get collection for import: %s", err)
  61. return err
  62. }
  63. // Only allow uploading to collection if current user is owner
  64. if coll.OwnerID != u.ID {
  65. err := ErrUnauthorizedGeneral
  66. _ = addSessionFlash(app, w, r, err.Message, nil)
  67. return err
  68. }
  69. coll.hostName = app.cfg.App.Host
  70. }
  71. fileDates := make(map[string]int64)
  72. err = json.Unmarshal([]byte(r.FormValue("fileDates")), &fileDates)
  73. if err != nil {
  74. log.Error("invalid form data for file dates: %v", err)
  75. return impart.HTTPError{http.StatusBadRequest, "form data for file dates was invalid"}
  76. }
  77. files := r.MultipartForm.File["files"]
  78. var fileErrs []error
  79. filesSubmitted := len(files)
  80. var filesImported int
  81. for _, formFile := range files {
  82. fname := ""
  83. ok := func() bool {
  84. file, err := formFile.Open()
  85. if err != nil {
  86. fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename))
  87. log.Error("import file: open from form: %v", err)
  88. return false
  89. }
  90. defer file.Close()
  91. tempFile, err := os.CreateTemp("", "post-upload-*.txt")
  92. if err != nil {
  93. fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
  94. log.Error("import file: create temp file %s: %v", formFile.Filename, err)
  95. return false
  96. }
  97. defer tempFile.Close()
  98. _, err = io.Copy(tempFile, file)
  99. if err != nil {
  100. fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
  101. log.Error("import file: copy to temp location %s: %v", formFile.Filename, err)
  102. return false
  103. }
  104. info, err := tempFile.Stat()
  105. if err != nil {
  106. fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
  107. log.Error("import file: stat temp file %s: %v", formFile.Filename, err)
  108. return false
  109. }
  110. fname = info.Name()
  111. return true
  112. }()
  113. if !ok {
  114. continue
  115. }
  116. post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname))
  117. if err == wfimport.ErrEmptyFile {
  118. // not a real error so don't log
  119. _ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
  120. continue
  121. } else if err == wfimport.ErrInvalidContentType {
  122. // same as above
  123. _ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
  124. continue
  125. } else if err != nil {
  126. fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
  127. log.Error("import textfile: file to post: %v", err)
  128. continue
  129. }
  130. if collAlias != "" {
  131. post.Collection = collAlias
  132. }
  133. dateTime := time.Unix(fileDates[formFile.Filename], 0)
  134. post.Created = &dateTime
  135. created := post.Created.Format("2006-01-02T15:04:05Z")
  136. submittedPost := SubmittedPost{
  137. Title: &post.Title,
  138. Content: &post.Content,
  139. Font: "norm",
  140. Created: &created,
  141. }
  142. rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
  143. if err != nil {
  144. fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
  145. log.Error("import textfile: create db post: %v", err)
  146. continue
  147. }
  148. // Federate post, if necessary
  149. if app.cfg.App.Federation && coll.ID > 0 {
  150. go federatePost(
  151. app,
  152. &PublicPost{
  153. Post: rp,
  154. Collection: &CollectionObj{
  155. Collection: *coll,
  156. },
  157. },
  158. coll.ID,
  159. false,
  160. )
  161. }
  162. filesImported++
  163. }
  164. if len(fileErrs) != 0 {
  165. _ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil)
  166. }
  167. if filesImported == filesSubmitted {
  168. verb := "posts"
  169. if filesSubmitted == 1 {
  170. verb = "post"
  171. }
  172. _ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, verb), nil)
  173. } else if filesImported > 0 {
  174. _ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
  175. }
  176. return impart.HTTPError{http.StatusFound, "/me/import"}
  177. }