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