mirror of https://github.com/writeas/writefreely
parent
af872127c6
commit
ebeacff43c
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,114 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/zip" |
||||||
|
"bytes" |
||||||
|
"encoding/csv" |
||||||
|
"github.com/writeas/web-core/log" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func exportPostsCSV(u *User, posts *[]PublicPost) []byte { |
||||||
|
var b bytes.Buffer |
||||||
|
|
||||||
|
r := [][]string{ |
||||||
|
{"id", "slug", "blog", "url", "created", "title", "body"}, |
||||||
|
} |
||||||
|
for _, p := range *posts { |
||||||
|
var blog string |
||||||
|
if p.Collection != nil { |
||||||
|
blog = p.Collection.Alias |
||||||
|
} |
||||||
|
f := []string{p.ID, p.Slug.String, blog, p.CanonicalURL(), p.Created8601(), p.Title.String, strings.Replace(p.Content, "\n", "\\n", -1)} |
||||||
|
r = append(r, f) |
||||||
|
} |
||||||
|
|
||||||
|
w := csv.NewWriter(&b) |
||||||
|
w.WriteAll(r) // calls Flush internally
|
||||||
|
if err := w.Error(); err != nil { |
||||||
|
log.Info("error writing csv:", err) |
||||||
|
} |
||||||
|
|
||||||
|
return b.Bytes() |
||||||
|
} |
||||||
|
|
||||||
|
type exportedTxt struct { |
||||||
|
Name, Body string |
||||||
|
Mod time.Time |
||||||
|
} |
||||||
|
|
||||||
|
func exportPostsZip(u *User, posts *[]PublicPost) []byte { |
||||||
|
// Create a buffer to write our archive to.
|
||||||
|
b := new(bytes.Buffer) |
||||||
|
|
||||||
|
// Create a new zip archive.
|
||||||
|
w := zip.NewWriter(b) |
||||||
|
|
||||||
|
// Add some files to the archive.
|
||||||
|
var filename string |
||||||
|
files := []exportedTxt{} |
||||||
|
for _, p := range *posts { |
||||||
|
filename = "" |
||||||
|
if p.Collection != nil { |
||||||
|
filename += p.Collection.Alias + "/" |
||||||
|
} |
||||||
|
if p.Slug.String != "" { |
||||||
|
filename += p.Slug.String + "_" |
||||||
|
} |
||||||
|
filename += p.ID + ".txt" |
||||||
|
files = append(files, exportedTxt{filename, p.Content, p.Created}) |
||||||
|
} |
||||||
|
|
||||||
|
for _, file := range files { |
||||||
|
head := &zip.FileHeader{Name: file.Name} |
||||||
|
head.SetModTime(file.Mod) |
||||||
|
f, err := w.CreateHeader(head) |
||||||
|
if err != nil { |
||||||
|
log.Error("export zip header: %v", err) |
||||||
|
} |
||||||
|
_, err = f.Write([]byte(file.Body)) |
||||||
|
if err != nil { |
||||||
|
log.Error("export zip write: %v", err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Make sure to check the error on Close.
|
||||||
|
err := w.Close() |
||||||
|
if err != nil { |
||||||
|
log.Error("export zip close: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
return b.Bytes() |
||||||
|
} |
||||||
|
|
||||||
|
func compileFullExport(app *app, u *User) *ExportUser { |
||||||
|
exportUser := &ExportUser{ |
||||||
|
User: u, |
||||||
|
} |
||||||
|
|
||||||
|
colls, err := app.db.GetCollections(u) |
||||||
|
if err != nil { |
||||||
|
log.Error("unable to fetch collections: %v", err) |
||||||
|
} |
||||||
|
|
||||||
|
posts, err := app.db.GetAnonymousPosts(u) |
||||||
|
if err != nil { |
||||||
|
log.Error("unable to fetch anon posts: %v", err) |
||||||
|
} |
||||||
|
exportUser.AnonymousPosts = *posts |
||||||
|
|
||||||
|
var collObjs []CollectionObj |
||||||
|
for _, c := range *colls { |
||||||
|
co := &CollectionObj{Collection: c} |
||||||
|
co.Posts, err = app.db.GetPosts(&c, 0, true) |
||||||
|
if err != nil { |
||||||
|
log.Error("unable to get collection posts: %v", err) |
||||||
|
} |
||||||
|
app.db.GetPostsCount(co, true) |
||||||
|
collObjs = append(collObjs, *co) |
||||||
|
} |
||||||
|
exportUser.Collections = &collObjs |
||||||
|
|
||||||
|
return exportUser |
||||||
|
} |
@ -0,0 +1,100 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
. "github.com/gorilla/feeds" |
||||||
|
"github.com/gorilla/mux" |
||||||
|
stripmd "github.com/writeas/go-strip-markdown" |
||||||
|
"github.com/writeas/web-core/log" |
||||||
|
"net/http" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func ViewFeed(app *app, w http.ResponseWriter, req *http.Request) error { |
||||||
|
alias := collectionAliasFromReq(req) |
||||||
|
|
||||||
|
// Display collection if this is a collection
|
||||||
|
var c *Collection |
||||||
|
var err error |
||||||
|
if app.cfg.App.SingleUser { |
||||||
|
c, err = app.db.GetCollection(alias) |
||||||
|
} else { |
||||||
|
c, err = app.db.GetCollectionByID(1) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if c.IsPrivate() || c.IsProtected() { |
||||||
|
return ErrCollectionNotFound |
||||||
|
} |
||||||
|
|
||||||
|
// Fetch extra data about the Collection
|
||||||
|
// TODO: refactor out this logic, shared in collection.go:fetchCollection()
|
||||||
|
coll := &DisplayCollection{CollectionObj: &CollectionObj{Collection: *c}} |
||||||
|
if c.PublicOwner { |
||||||
|
u, err := app.db.GetUserByID(coll.OwnerID) |
||||||
|
if err != nil { |
||||||
|
// Log the error and just continue
|
||||||
|
log.Error("Error getting user for collection: %v", err) |
||||||
|
} else { |
||||||
|
coll.Owner = u |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
tag := mux.Vars(req)["tag"] |
||||||
|
if tag != "" { |
||||||
|
coll.Posts, _ = app.db.GetPostsTagged(c, tag, 1, false) |
||||||
|
} else { |
||||||
|
coll.Posts, _ = app.db.GetPosts(c, 1, false) |
||||||
|
} |
||||||
|
|
||||||
|
author := "" |
||||||
|
if coll.Owner != nil { |
||||||
|
author = coll.Owner.Username |
||||||
|
} |
||||||
|
|
||||||
|
collectionTitle := coll.DisplayTitle() |
||||||
|
if tag != "" { |
||||||
|
collectionTitle = tag + " — " + collectionTitle |
||||||
|
} |
||||||
|
|
||||||
|
baseUrl := coll.CanonicalURL() |
||||||
|
basePermalinkUrl := baseUrl |
||||||
|
siteURL := baseUrl |
||||||
|
if tag != "" { |
||||||
|
siteURL += "tag:" + tag |
||||||
|
} |
||||||
|
|
||||||
|
feed := &Feed{ |
||||||
|
Title: collectionTitle, |
||||||
|
Link: &Link{Href: siteURL}, |
||||||
|
Description: coll.Description, |
||||||
|
Author: &Author{author, ""}, |
||||||
|
Created: time.Now(), |
||||||
|
} |
||||||
|
|
||||||
|
var title, permalink string |
||||||
|
for _, p := range *coll.Posts { |
||||||
|
title = p.PlainDisplayTitle() |
||||||
|
permalink = fmt.Sprintf("%s%s", baseUrl, p.Slug.String) |
||||||
|
feed.Items = append(feed.Items, &Item{ |
||||||
|
Id: fmt.Sprintf("%s%s", basePermalinkUrl, p.Slug.String), |
||||||
|
Title: title, |
||||||
|
Link: &Link{Href: permalink}, |
||||||
|
Description: "<![CDATA[" + stripmd.Strip(p.Content) + "]]>", |
||||||
|
Content: applyMarkdown([]byte(p.Content)), |
||||||
|
Author: &Author{author, ""}, |
||||||
|
Created: p.Created, |
||||||
|
Updated: p.Updated, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
rss, err := feed.ToRss() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
fmt.Fprint(w, rss) |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import "mime" |
||||||
|
|
||||||
|
func IsJSON(h string) bool { |
||||||
|
ct, _, _ := mime.ParseMediaType(h) |
||||||
|
return ct == "application/json" |
||||||
|
} |
@ -0,0 +1,94 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"github.com/gorilla/mux" |
||||||
|
"github.com/ikeikeikeike/go-sitemap-generator/stm" |
||||||
|
"github.com/writeas/web-core/log" |
||||||
|
"net/http" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func buildSitemap(host, alias string) *stm.Sitemap { |
||||||
|
sm := stm.NewSitemap() |
||||||
|
sm.SetDefaultHost(host) |
||||||
|
if alias != "/" { |
||||||
|
sm.SetSitemapsPath(alias) |
||||||
|
} |
||||||
|
|
||||||
|
sm.Create() |
||||||
|
|
||||||
|
// Note: Do not call `sm.Finalize()` because it flushes
|
||||||
|
// the underlying datastructure from memory to disk.
|
||||||
|
|
||||||
|
return sm |
||||||
|
} |
||||||
|
|
||||||
|
func handleViewSitemap(app *app, w http.ResponseWriter, r *http.Request) error { |
||||||
|
vars := mux.Vars(r) |
||||||
|
|
||||||
|
// Determine canonical blog URL
|
||||||
|
alias := vars["collection"] |
||||||
|
subdomain := vars["subdomain"] |
||||||
|
isSubdomain := subdomain != "" |
||||||
|
if isSubdomain { |
||||||
|
alias = subdomain |
||||||
|
} |
||||||
|
|
||||||
|
host := fmt.Sprintf("%s/%s/", app.cfg.App.Host, alias) |
||||||
|
var c *Collection |
||||||
|
var err error |
||||||
|
pre := "/" |
||||||
|
if app.cfg.App.SingleUser { |
||||||
|
c, err = app.db.GetCollectionByID(1) |
||||||
|
} else { |
||||||
|
c, err = app.db.GetCollection(alias) |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if !isSubdomain { |
||||||
|
pre += alias + "/" |
||||||
|
} |
||||||
|
host = c.CanonicalURL() |
||||||
|
|
||||||
|
sm := buildSitemap(host, pre) |
||||||
|
posts, err := app.db.GetPosts(c, 0, false) |
||||||
|
if err != nil { |
||||||
|
log.Error("Error getting posts: %v", err) |
||||||
|
return err |
||||||
|
} |
||||||
|
lastSiteMod := time.Now() |
||||||
|
for i, p := range *posts { |
||||||
|
if i == 0 { |
||||||
|
lastSiteMod = p.Updated |
||||||
|
} |
||||||
|
u := stm.URL{ |
||||||
|
"loc": p.Slug.String, |
||||||
|
"changefreq": "weekly", |
||||||
|
"mobile": true, |
||||||
|
"lastmod": p.Updated, |
||||||
|
} |
||||||
|
if len(p.Images) > 0 { |
||||||
|
imgs := []stm.URL{} |
||||||
|
for _, i := range p.Images { |
||||||
|
imgs = append(imgs, stm.URL{"loc": i, "title": ""}) |
||||||
|
} |
||||||
|
u["image"] = imgs |
||||||
|
} |
||||||
|
sm.Add(u) |
||||||
|
} |
||||||
|
|
||||||
|
// Add top URL
|
||||||
|
sm.Add(stm.URL{ |
||||||
|
"loc": pre, |
||||||
|
"changefreq": "daily", |
||||||
|
"priority": "1.0", |
||||||
|
"lastmod": lastSiteMod, |
||||||
|
}) |
||||||
|
|
||||||
|
w.Write(sm.XMLContent()) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,121 @@ |
|||||||
|
package writefreely |
||||||
|
|
||||||
|
import ( |
||||||
|
"database/sql" |
||||||
|
"encoding/json" |
||||||
|
"github.com/writeas/impart" |
||||||
|
"github.com/writeas/web-core/log" |
||||||
|
"net/http" |
||||||
|
) |
||||||
|
|
||||||
|
func handleWebSignup(app *app, w http.ResponseWriter, r *http.Request) error { |
||||||
|
reqJSON := IsJSON(r.Header.Get("Content-Type")) |
||||||
|
|
||||||
|
// Get params
|
||||||
|
var ur userRegistration |
||||||
|
if reqJSON { |
||||||
|
decoder := json.NewDecoder(r.Body) |
||||||
|
err := decoder.Decode(&ur) |
||||||
|
if err != nil { |
||||||
|
log.Error("Couldn't parse signup JSON request: %v\n", err) |
||||||
|
return ErrBadJSON |
||||||
|
} |
||||||
|
} else { |
||||||
|
err := r.ParseForm() |
||||||
|
if err != nil { |
||||||
|
log.Error("Couldn't parse signup form request: %v\n", err) |
||||||
|
return ErrBadFormData |
||||||
|
} |
||||||
|
|
||||||
|
err = app.formDecoder.Decode(&ur, r.PostForm) |
||||||
|
if err != nil { |
||||||
|
log.Error("Couldn't decode signup form request: %v\n", err) |
||||||
|
return ErrBadFormData |
||||||
|
} |
||||||
|
} |
||||||
|
ur.Web = true |
||||||
|
|
||||||
|
_, err := signupWithRegistration(app, ur, w, r) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
return impart.HTTPError{http.StatusFound, "/"} |
||||||
|
} |
||||||
|
|
||||||
|
// { "username": "asdf" }
|
||||||
|
// result: { code: 204 }
|
||||||
|
func handleUsernameCheck(app *app, w http.ResponseWriter, r *http.Request) error { |
||||||
|
reqJSON := IsJSON(r.Header.Get("Content-Type")) |
||||||
|
|
||||||
|
// Get params
|
||||||
|
var d struct { |
||||||
|
Username string `json:"username"` |
||||||
|
} |
||||||
|
if reqJSON { |
||||||
|
decoder := json.NewDecoder(r.Body) |
||||||
|
err := decoder.Decode(&d) |
||||||
|
if err != nil { |
||||||
|
log.Error("Couldn't decode username check: %v\n", err) |
||||||
|
return ErrBadFormData |
||||||
|
} |
||||||
|
} else { |
||||||
|
return impart.HTTPError{http.StatusNotAcceptable, "Must be JSON request"} |
||||||
|
} |
||||||
|
|
||||||
|
// Check if username is okay
|
||||||
|
finalUsername := getSlug(d.Username, "") |
||||||
|
if finalUsername == "" { |
||||||
|
errMsg := "Invalid username" |
||||||
|
if d.Username != "" { |
||||||
|
// Username was provided, but didn't convert into valid latin characters
|
||||||
|
errMsg += " - must have at least 2 letters or numbers" |
||||||
|
} |
||||||
|
return impart.HTTPError{http.StatusBadRequest, errMsg + "."} |
||||||
|
} |
||||||
|
if app.db.PostIDExists(finalUsername) { |
||||||
|
return impart.HTTPError{http.StatusConflict, "Username is already taken."} |
||||||
|
} |
||||||
|
var un string |
||||||
|
err := app.db.QueryRow("SELECT username FROM users WHERE username = ?", finalUsername).Scan(&un) |
||||||
|
switch { |
||||||
|
case err == sql.ErrNoRows: |
||||||
|
return impart.WriteSuccess(w, finalUsername, http.StatusOK) |
||||||
|
case err != nil: |
||||||
|
log.Error("Couldn't SELECT username: %v", err) |
||||||
|
return impart.HTTPError{http.StatusInternalServerError, "We messed up."} |
||||||
|
} |
||||||
|
|
||||||
|
// Username was found, so it's taken
|
||||||
|
return impart.HTTPError{http.StatusConflict, "Username is already taken."} |
||||||
|
} |
||||||
|
|
||||||
|
func getValidUsername(app *app, reqName, prevName string) (string, *impart.HTTPError) { |
||||||
|
// Check if username is okay
|
||||||
|
finalUsername := getSlug(reqName, "") |
||||||
|
if finalUsername == "" { |
||||||
|
errMsg := "Invalid username" |
||||||
|
if reqName != "" { |
||||||
|
// Username was provided, but didn't convert into valid latin characters
|
||||||
|
errMsg += " - must have at least 2 letters or numbers" |
||||||
|
} |
||||||
|
return "", &impart.HTTPError{http.StatusBadRequest, errMsg + "."} |
||||||
|
} |
||||||
|
if finalUsername == prevName { |
||||||
|
return "", &impart.HTTPError{http.StatusNotModified, "Username unchanged."} |
||||||
|
} |
||||||
|
if app.db.PostIDExists(finalUsername) { |
||||||
|
return "", &impart.HTTPError{http.StatusConflict, "Username is already taken."} |
||||||
|
} |
||||||
|
var un string |
||||||
|
err := app.db.QueryRow("SELECT username FROM users WHERE username = ?", finalUsername).Scan(&un) |
||||||
|
switch { |
||||||
|
case err == sql.ErrNoRows: |
||||||
|
return finalUsername, nil |
||||||
|
case err != nil: |
||||||
|
log.Error("Couldn't SELECT username: %v", err) |
||||||
|
return "", &impart.HTTPError{http.StatusInternalServerError, "We messed up."} |
||||||
|
} |
||||||
|
|
||||||
|
// Username was found, so it's taken
|
||||||
|
return "", &impart.HTTPError{http.StatusConflict, "Username is already taken."} |
||||||
|
} |
Loading…
Reference in new issue