Merge branch 'develop' into chorus

This commit is contained in:
Matt Baer 2019-08-07 10:58:34 -04:00
commit 3cc397ad76
6 changed files with 71 additions and 22 deletions

6
app.go
View File

@ -120,6 +120,8 @@ type Apper interface {
SaveConfig(*config.Config) error SaveConfig(*config.Config) error
LoadKeys() error LoadKeys() error
ReqLog(r *http.Request, status int, timeSince time.Duration) string
} }
// App returns the App // App returns the App
@ -179,6 +181,10 @@ func (app *App) LoadKeys() error {
return nil return nil
} }
func (app *App) ReqLog(r *http.Request, status int, timeSince time.Duration) string {
return fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, timeSince, r.UserAgent())
}
// handleViewHome shows page at root path. Will be the Pad if logged in and the // handleViewHome shows page at root path. Will be the Pad if logged in and the
// catch-all landing page otherwise. // catch-all landing page otherwise.
func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error { func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error {

View File

@ -211,6 +211,10 @@ func (c *Collection) DisplayCanonicalURL() string {
} }
func (c *Collection) RedirectingCanonicalURL(isRedir bool) string { func (c *Collection) RedirectingCanonicalURL(isRedir bool) string {
if c.hostName == "" {
// If this is true, the human programmers screwed up. So ask for a bug report and fail, fail, fail
log.Error("[PROGRAMMER ERROR] WARNING: Collection.hostName is empty! Federation and many other things will fail! If you're seeing this in the wild, please report this bug and let us know what you were doing just before this: https://github.com/writeas/writefreely/issues/new?template=bug_report.md")
}
if isSingleUser { if isSingleUser {
return c.hostName + "/" return c.hostName + "/"
} }

View File

@ -74,7 +74,7 @@ type writestore interface {
GetAnonymousPosts(u *User) (*[]PublicPost, error) GetAnonymousPosts(u *User) (*[]PublicPost, error)
GetUserPosts(u *User) (*[]PublicPost, error) GetUserPosts(u *User) (*[]PublicPost, error)
CreateOwnedPost(post *SubmittedPost, accessToken, collAlias string) (*PublicPost, error) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias, hostName string) (*PublicPost, error)
CreatePost(userID, collID int64, post *SubmittedPost) (*Post, error) CreatePost(userID, collID int64, post *SubmittedPost) (*Post, error)
UpdateOwnedPost(post *AuthenticatedPost, userID int64) error UpdateOwnedPost(post *AuthenticatedPost, userID int64) error
GetEditablePost(id, editToken string) (*PublicPost, error) GetEditablePost(id, editToken string) (*PublicPost, error)
@ -542,7 +542,7 @@ func (db *datastore) GetTemporaryOneTimeAccessToken(userID int64, validSecs int,
return u.String(), nil return u.String(), nil
} }
func (db *datastore) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias string) (*PublicPost, error) { func (db *datastore) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias, hostName string) (*PublicPost, error) {
var userID, collID int64 = -1, -1 var userID, collID int64 = -1, -1
var coll *Collection var coll *Collection
var err error var err error
@ -556,6 +556,7 @@ func (db *datastore) CreateOwnedPost(post *SubmittedPost, accessToken, collAlias
if err != nil { if err != nil {
return nil, err return nil, err
} }
coll.hostName = hostName
if coll.OwnerID != userID { if coll.OwnerID != userID {
return nil, ErrForbiddenCollection return nil, ErrForbiddenCollection
} }

View File

@ -137,7 +137,7 @@ func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
status = http.StatusInternalServerError status = http.StatusInternalServerError
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
u := getUserSession(h.app.App(), r) u := getUserSession(h.app.App(), r)
@ -175,7 +175,7 @@ func (h *Handler) Admin(f userHandlerFunc) http.HandlerFunc {
status = http.StatusInternalServerError status = http.StatusInternalServerError
} }
log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
u := getUserSession(h.app.App(), r) u := getUserSession(h.app.App(), r)
@ -213,7 +213,7 @@ func (h *Handler) AdminApper(f userApperHandlerFunc) http.HandlerFunc {
status = http.StatusInternalServerError status = http.StatusInternalServerError
} }
log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent())) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
u := getUserSession(h.app.App(), r) u := getUserSession(h.app.App(), r)
@ -295,7 +295,7 @@ func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerF
status = 500 status = 500
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
u, err := a(h.app.App(), r) u, err := a(h.app.App(), r)
@ -381,7 +381,7 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
status = 500 status = 500
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
var session *sessions.Session var session *sessions.Session
@ -440,7 +440,7 @@ func (h *Handler) CollectionPostOrStatic(w http.ResponseWriter, r *http.Request)
start := time.Now() start := time.Now()
status := 200 status := 200
defer func() { defer func() {
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
// Serve static file // Serve static file
@ -472,7 +472,7 @@ func (h *Handler) Web(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
status = 500 status = 500
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
if ul(h.app.App().cfg) != UserLevelNoneType { if ul(h.app.App().cfg) != UserLevelNoneType {
@ -530,7 +530,7 @@ func (h *Handler) All(f handlerFunc) http.HandlerFunc {
status = 500 status = 500
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
// TODO: do any needed authentication // TODO: do any needed authentication
@ -562,7 +562,7 @@ func (h *Handler) AllReader(f handlerFunc) http.HandlerFunc {
status = 500 status = 500
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
if h.app.App().cfg.App.Private { if h.app.App().cfg.App.Private {
@ -619,7 +619,7 @@ func (h *Handler) Download(f dataHandlerFunc, ul UserLevelFunc) http.HandlerFunc
status = 500 status = 500
} }
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
data, filename, err := f(h.app.App(), w, r) data, filename, err := f(h.app.App(), w, r)
@ -682,7 +682,7 @@ func (h *Handler) Redirect(url string, ul UserLevelFunc) http.HandlerFunc {
status = sendRedirect(w, http.StatusFound, url) status = sendRedirect(w, http.StatusFound, url)
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
return nil return nil
}()) }())
@ -721,6 +721,10 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er
return return
} else if err.Status == http.StatusNotFound { } else if err.Status == http.StatusNotFound {
w.WriteHeader(err.Status) w.WriteHeader(err.Status)
if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
// This is a fediverse request; simply return the header
return
}
h.errors.NotFound.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r)) h.errors.NotFound.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
return return
} else if err.Status == http.StatusInternalServerError { } else if err.Status == http.StatusInternalServerError {
@ -799,7 +803,7 @@ func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
} }
// TODO: log actual status code returned // TODO: log actual status code returned
log.Info("\"%s %s\" %d %s \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent()) log.Info(h.app.ReqLog(r, status, time.Since(start)))
}() }()
if h.app.App().cfg.App.Private { if h.app.App().cfg.App.Private {

View File

@ -557,7 +557,7 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error {
var coll *Collection var coll *Collection
var err error var err error
if accessToken != "" { if accessToken != "" {
newPost, err = app.db.CreateOwnedPost(p, accessToken, collAlias) newPost, err = app.db.CreateOwnedPost(p, accessToken, collAlias, app.cfg.App.Host)
} else { } else {
//return ErrNotLoggedIn //return ErrNotLoggedIn
// TODO: verify user is logged in // TODO: verify user is logged in
@ -1300,14 +1300,32 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
coll.Owner = owner coll.Owner = owner
} }
postFound := true
p, err := app.db.GetPost(slug, coll.ID) p, err := app.db.GetPost(slug, coll.ID)
if err != nil { if err != nil {
if err == ErrCollectionPageNotFound && slug == "feed" { if err == ErrCollectionPageNotFound {
// User tried to access blog feed without a trailing slash, and postFound = false
// there's no post with a slug "feed"
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "/feed/"} if slug == "feed" {
// User tried to access blog feed without a trailing slash, and
// there's no post with a slug "feed"
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "/feed/"}
}
po := &Post{
Slug: null.NewString(slug, true),
Font: "norm",
Language: zero.NewString("en", true),
RTL: zero.NewBool(false, true),
Content: `<p class="msg">This page is missing.</p>
Are you sure it was ever here?`,
}
pp := po.processPost()
p = &pp
} else {
return err
} }
return err
} }
p.IsOwner = owner != nil && p.OwnerID.Valid && owner.ID == p.OwnerID.Int64 p.IsOwner = owner != nil && p.OwnerID.Valid && owner.ID == p.OwnerID.Int64
p.Collection = coll p.Collection = coll
@ -1329,11 +1347,20 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
contentType = "text/markdown" contentType = "text/markdown"
} }
w.Header().Set("Content-Type", fmt.Sprintf("%s; charset=utf-8", contentType)) w.Header().Set("Content-Type", fmt.Sprintf("%s; charset=utf-8", contentType))
if !postFound {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Post not found.")
// TODO: return error instead, so status is correctly reflected in logs
return nil
}
if isMarkdown && p.Title.String != "" { if isMarkdown && p.Title.String != "" {
fmt.Fprintf(w, "# %s\n\n", p.Title.String) fmt.Fprintf(w, "# %s\n\n", p.Title.String)
} }
fmt.Fprint(w, p.Content) fmt.Fprint(w, p.Content)
} else if strings.Contains(r.Header.Get("Accept"), "application/activity+json") { } else if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
if !postFound {
return ErrCollectionPageNotFound
}
p.extractData() p.extractData()
ap := p.ActivityObject(app.cfg) ap := p.ActivityObject(app.cfg)
ap.Context = []interface{}{activitystreams.Namespace} ap.Context = []interface{}{activitystreams.Namespace}
@ -1350,6 +1377,7 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
IsPinned bool IsPinned bool
IsCustomDomain bool IsCustomDomain bool
PinnedPosts *[]PublicPost PinnedPosts *[]PublicPost
IsFound bool
IsAdmin bool IsAdmin bool
CanInvite bool CanInvite bool
}{ }{
@ -1357,12 +1385,16 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
StaticPage: pageForReq(app, r), StaticPage: pageForReq(app, r),
IsOwner: cr.isCollOwner, IsOwner: cr.isCollOwner,
IsCustomDomain: cr.isCustomDomain, IsCustomDomain: cr.isCustomDomain,
IsFound: postFound,
} }
tp.IsAdmin = u != nil && u.IsAdmin() tp.IsAdmin = u != nil && u.IsAdmin()
tp.CanInvite = canUserInvite(app.cfg, tp.IsAdmin) tp.CanInvite = canUserInvite(app.cfg, tp.IsAdmin)
tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll) tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll)
tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p) tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p)
if !postFound {
w.WriteHeader(http.StatusNotFound)
}
postTmpl := "collection-post" postTmpl := "collection-post"
if app.cfg.App.Chorus { if app.cfg.App.Chorus {
postTmpl = "chorus-collection-post" postTmpl = "chorus-collection-post"

View File

@ -8,6 +8,7 @@
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{ if .IsFound }}
<link rel="canonical" href="{{.CanonicalURL}}" /> <link rel="canonical" href="{{.CanonicalURL}}" />
<meta name="generator" content="WriteFreely"> <meta name="generator" content="WriteFreely">
<meta name="title" content="{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{if .Collection.Title}}{{.Collection.Title}}{{else}}{{.Collection.Alias}}{{end}}"> <meta name="title" content="{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{if .Collection.Title}}{{.Collection.Title}}{{else}}{{.Collection.Alias}}{{end}}">
@ -29,6 +30,7 @@
<meta property="og:updated_time" content="{{.Created8601}}" /> <meta property="og:updated_time" content="{{.Created8601}}" />
{{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="{{.Collection.AvatarURL}}">{{end}} {{range .Images}}<meta property="og:image" content="{{.}}" />{{else}}<meta property="og:image" content="{{.Collection.AvatarURL}}">{{end}}
<meta property="article:published_time" content="{{.Created8601}}"> <meta property="article:published_time" content="{{.Created8601}}">
{{ end }}
{{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}} {{if .Collection.StyleSheet}}<style type="text/css">{{.Collection.StyleSheetDisplay}}</style>{{end}}
{{if .Collection.RenderMathJax}} {{if .Collection.RenderMathJax}}
@ -50,14 +52,14 @@
{{if .PinnedPosts}} {{if .PinnedPosts}}
{{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}} {{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}
{{end}} {{end}}
{{ if .IsOwner }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span> {{ if and .IsOwner .IsFound }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
<a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a> <a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
{{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}} {{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}}
{{ end }} {{ end }}
</nav> </nav>
</header> </header>
<article id="post-body" class="{{.Font}} h-entry">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name">{{.FormattedDisplayTitle}}</h2>{{end}}<div class="e-content">{{.HTMLContent}}</div></article> <article id="post-body" class="{{.Font}} h-entry {{if not .IsFound}}error-page{{end}}">{{if .IsScheduled}}<p class="badge">Scheduled</p>{{end}}{{if .Title.String}}<h2 id="title" class="p-name">{{.FormattedDisplayTitle}}</h2>{{end}}<div class="e-content">{{.HTMLContent}}</div></article>
{{ if .Collection.ShowFooterBranding }} {{ if .Collection.ShowFooterBranding }}
<footer dir="ltr"><hr><nav><p style="font-size: 0.9em">{{localhtml "published with write.as" .Language.String}}</p></nav></footer> <footer dir="ltr"><hr><nav><p style="font-size: 0.9em">{{localhtml "published with write.as" .Language.String}}</p></nav></footer>