Skip to content
Permalink
Browse files

Merge branch 'develop' into acme-v2

  • Loading branch information
thebaer committed Feb 9, 2020
2 parents 80cffbb + ab2b8df commit 42467fc9c1cd25e79ddd35f04c7d5256b9807f54
Showing with 1,092 additions and 289 deletions.
  1. +16 −6 Makefile
  2. +21 −21 account.go
  3. +195 −0 account_import.go
  4. +87 −22 activitypub.go
  5. +6 −2 admin.go
  6. +47 −1 app.go
  7. +2 −1 author/author.go
  8. +11 −2 cmd/writefreely/main.go
  9. +28 −14 collections.go
  10. +13 −1 database-no-sqlite.go
  11. +12 −0 database-sqlite.go
  12. +138 −53 database.go
  13. +5 −4 errors.go
  14. +2 −2 feed.go
  15. +8 −1 go.mod
  16. +29 −4 go.sum
  17. +9 −2 invites.go
  18. +18 −0 less/core.less
  19. +6 −5 migrations/migrations.go
  20. +29 −0 migrations/v6.go
  21. +5 −0 oauth.go
  22. +33 −21 oauth_signup.go
  23. +11 −1 oauth_slack.go
  24. +9 −9 pad.go
  25. +66 −10 pages/signup-oauth.tmpl
  26. +3 −0 postrender.go
  27. +59 −32 posts.go
  28. +10 −5 routes.go
  29. +17 −5 scripts/upgrade-server.sh
  30. +16 −0 static/js/localdate.js
  31. +3 −3 templates.go
  32. +4 −3 templates/chorus-collection-post.tmpl
  33. +3 −2 templates/chorus-collection.tmpl
  34. +4 −3 templates/collection-post.tmpl
  35. +3 −2 templates/collection-tags.tmpl
  36. +3 −2 templates/collection.tmpl
  37. +1 −1 templates/edit-meta.tmpl
  38. +2 −2 templates/include/posts.tmpl
  39. +2 −2 templates/pad.tmpl
  40. +2 −2 templates/post.tmpl
  41. +3 −3 templates/read.tmpl
  42. +5 −5 templates/user/admin/view-user.tmpl
  43. +10 −6 templates/user/articles.tmpl
  44. +2 −2 templates/user/collection.tmpl
  45. +2 −2 templates/user/collections.tmpl
  46. +64 −0 templates/user/import.tmpl
  47. +2 −0 templates/user/include/header.tmpl
  48. +1 −1 templates/user/include/{suspended.tmpl → silenced.tmpl}
  49. +8 −16 templates/user/invite.tmpl
  50. +2 −2 templates/user/settings.tmpl
  51. +2 −2 templates/user/stats.tmpl
  52. +53 −4 webfinger.go
@@ -25,31 +25,37 @@ build-no-sqlite: assets-no-sqlite deps-no-sqlite

build-linux: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/karalabe/xgo; \
$(GOGET) -u src.techknowlogick.com/xgo; \
fi
xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely

build-windows: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/karalabe/xgo; \
$(GOGET) -u src.techknowlogick.com/xgo; \
fi
xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely

build-darwin: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/karalabe/xgo; \
$(GOGET) -u src.techknowlogick.com/xgo; \
fi
xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely

build-arm6: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \
fi
xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely

build-arm7: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/karalabe/xgo; \
$(GOGET) -u src.techknowlogick.com/xgo; \
fi
xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely

build-arm64: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/karalabe/xgo; \
$(GOGET) -u src.techknowlogick.com/xgo; \
fi
xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='sqlite' -out writefreely ./cmd/writefreely

@@ -85,6 +91,10 @@ release : clean ui assets
mv build/$(BINARY_NAME)-linux-amd64 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_amd64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-arm6
mv build/$(BINARY_NAME)-linux-arm-6 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_arm6.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-arm7
mv build/$(BINARY_NAME)-linux-arm-7 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_arm7.tar.gz -C build $(BINARY_NAME)
@@ -145,7 +155,7 @@ $(TMPBIN)/go-bindata: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/go-bindata github.com/jteeuwen/go-bindata/go-bindata

$(TMPBIN)/xgo: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/xgo github.com/karalabe/xgo
$(GOBUILD) -o $(TMPBIN)/xgo src.techknowlogick.com/xgo

ci-assets : $(TMPBIN)/go-bindata
$(TMPBIN)/go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql sqlite.sql
@@ -746,20 +746,20 @@ func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) err
log.Error("unable to fetch collections: %v", err)
}

suspended, err := app.db.IsUserSuspended(u.ID)
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
log.Error("view articles: %v", err)
}
d := struct {
*UserPage
AnonymousPosts *[]PublicPost
Collections *[]Collection
Suspended bool
Silenced bool
}{
UserPage: NewUserPage(app, r, u, u.Username+"'s Posts", f),
AnonymousPosts: p,
Collections: c,
Suspended: suspended,
Silenced: silenced,
}
d.UserPage.SetMessaging(u)
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
@@ -781,7 +781,7 @@ func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request)
uc, _ := app.db.GetUserCollectionCount(u.ID)
// TODO: handle any errors

suspended, err := app.db.IsUserSuspended(u.ID)
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
log.Error("view collections %v", err)
return fmt.Errorf("view collections: %v", err)
@@ -793,13 +793,13 @@ func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request)
UsedCollections, TotalCollections int

NewBlogsDisabled bool
Suspended bool
Silenced bool
}{
UserPage: NewUserPage(app, r, u, u.Username+"'s Blogs", f),
Collections: c,
UsedCollections: int(uc),
NewBlogsDisabled: !app.cfg.App.CanCreateBlogs(uc),
Suspended: suspended,
Silenced: silenced,
}
d.UserPage.SetMessaging(u)
showUserPage(w, "collections", d)
@@ -817,7 +817,7 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
return ErrCollectionNotFound
}

suspended, err := app.db.IsUserSuspended(u.ID)
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
log.Error("view edit collection %v", err)
return fmt.Errorf("view edit collection: %v", err)
@@ -826,11 +826,11 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
obj := struct {
*UserPage
*Collection
Suspended bool
Silenced bool
}{
UserPage: NewUserPage(app, r, u, "Edit "+c.DisplayTitle(), flashes),
Collection: c,
Suspended: suspended,
Silenced: silenced,
}

showUserPage(w, "collection", obj)
@@ -992,7 +992,7 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
titleStats = c.DisplayTitle() + " "
}

suspended, err := app.db.IsUserSuspended(u.ID)
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
log.Error("view stats: %v", err)
return err
@@ -1003,13 +1003,13 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
Collection *Collection
TopPosts *[]PublicPost
APFollowers int
Suspended bool
Silenced bool
}{
UserPage: NewUserPage(app, r, u, titleStats+"Stats", flashes),
VisitsBlog: alias,
Collection: c,
TopPosts: topPosts,
Suspended: suspended,
Silenced: silenced,
}
if app.cfg.App.Federation {
folls, err := app.db.GetAPFollowers(c)
@@ -1040,16 +1040,16 @@ func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) err

obj := struct {
*UserPage
Email string
HasPass bool
IsLogOut bool
Suspended bool
Email string
HasPass bool
IsLogOut bool
Silenced bool
}{
UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
Email: fullUser.EmailClear(app.keys),
HasPass: passIsSet,
IsLogOut: r.FormValue("logout") == "1",
Suspended: fullUser.IsSilenced(),
UserPage: NewUserPage(app, r, u, "Account Settings", flashes),
Email: fullUser.EmailClear(app.keys),
HasPass: passIsSet,
IsLogOut: r.FormValue("logout") == "1",
Silenced: fullUser.IsSilenced(),
}

showUserPage(w, "settings", obj)
@@ -0,0 +1,195 @@
package writefreely

import (
"encoding/json"
"fmt"
"html/template"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"strings"
"time"

"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
p := NewUserPage(app, r, u, "Import Posts", nil)

c, err := app.db.GetCollections(u, app.Config().App.Host)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
}

d := struct {
*UserPage
Collections *[]Collection
Flashes []template.HTML
Message string
InfoMsg bool
}{
UserPage: p,
Collections: c,
Flashes: []template.HTML{},
}

flashes, _ := getSessionFlashes(app, w, r, nil)
for _, flash := range flashes {
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))
}
}

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)

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
}

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"}
}
files := r.MultipartForm.File["files"]
var fileErrs []error
filesSubmitted := len(files)
var filesImported int
for _, formFile := range files {
fname := ""
ok := func() bool {
file, err := formFile.Open()
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename))
log.Error("import file: open from form: %v", err)
return false
}
defer file.Close()

tempFile, err := ioutil.TempFile("", "post-upload-*.txt")
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: create temp file %s: %v", formFile.Filename, err)
return false
}
defer tempFile.Close()

_, err = io.Copy(tempFile, file)
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: copy to temp location %s: %v", formFile.Filename, err)
return false
}

info, err := tempFile.Stat()
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: stat temp file %s: %v", formFile.Filename, err)
return false
}
fname = info.Name()
return true
}()
if !ok {
continue
}

post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname))
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
} 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
} 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
}

if collAlias != "" {
post.Collection = collAlias
}
dateTime := time.Unix(fileDates[formFile.Filename], 0)
post.Created = &dateTime
created := post.Created.Format("2006-01-02T15:04:05Z")
submittedPost := SubmittedPost{
Title: &post.Title,
Content: &post.Content,
Font: "norm",
Created: &created,
}
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
}

// Federate post, if necessary
if app.cfg.App.Federation && coll.ID > 0 {
go federatePost(
app,
&PublicPost{
Post: rp,
Collection: &CollectionObj{
Collection: *coll,
},
},
coll.ID,
false,
)
}
filesImported++
}
if len(fileErrs) != 0 {
_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil)
}

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)
}
return impart.HTTPError{http.StatusFound, "/me/import"}
}

0 comments on commit 42467fc

Please sign in to comment.
You can’t perform that action at this time.