From 530439772d1da5e6a263d4a6253d44994052409f Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:00:21 -0500 Subject: [PATCH 01/31] Add Pagination to Tags Collection Mostly copied the logic for pagination from non tag collection --- collections.go | 9 +++++++++ routes.go | 1 + templates/collection-tags.tmpl | 11 +++++++++++ 3 files changed, 21 insertions(+) diff --git a/collections.go b/collections.go index e1ebe48..898b730 100644 --- a/collections.go +++ b/collections.go @@ -901,6 +901,15 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e coll := newDisplayCollection(c, cr, page) + coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(coll.Format.PostsPerPage()))) + if coll.TotalPages > 0 && page > coll.TotalPages { + redirURL := fmt.Sprintf("/page/%d", coll.TotalPages) + if !app.cfg.App.SingleUser { + redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL) + } + return impart.HTTPError{http.StatusFound, redirURL} + } + coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner) if coll.Posts != nil && len(*coll.Posts) == 0 { return ErrCollectionPageNotFound diff --git a/routes.go b/routes.go index bb1785f..6595949 100644 --- a/routes.go +++ b/routes.go @@ -206,6 +206,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) + r.HandleFunc("/tag:{tag}/page/{page:[0-9]+}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) r.HandleFunc("/feed/", handler.AllReader(ViewFeed)) diff --git a/templates/collection-tags.tmpl b/templates/collection-tags.tmpl index e2f8962..9fb130a 100644 --- a/templates/collection-tags.tmpl +++ b/templates/collection-tags.tmpl @@ -60,6 +60,17 @@ {{if .Posts}}
{{else}}
{{end}}

{{.Tag}}

{{template "posts" .}} + + {{if gt .TotalPages 1}}{{end}} + {{if .Posts}}
{{else}}{{end}} {{ if .Collection.ShowFooterBranding }} From 9ed268754318db276236ba71ff42c69dd5eedb70 Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:49:15 -0500 Subject: [PATCH 02/31] Added TagCollectionPage * Implements PrevPageURL and NextPageURL * This allows the collection-tag template to get proper urls for paginating using tags. --- collections.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/collections.go b/collections.go index 898b730..c926295 100644 --- a/collections.go +++ b/collections.go @@ -562,6 +562,30 @@ type CollectionPage struct { CanInvite bool } +type TagCollectionPage struct { + CollectionPage + Tag string +} + +func (tcp TagCollectionPage) PrevPageURL(prefix string, n int, tl bool) string { + u := fmt.Sprintf("/tag:%s", tcp.Tag) + if n > 2 { + u += fmt.Sprintf("/page/%d", n-1) + } + if tl { + return u + } + return "/" + prefix + tcp.Alias + u + +} + +func (tcp TagCollectionPage) NextPageURL(prefix string, n int, tl bool) string { + if tl { + return fmt.Sprintf("/tag:%s/page/%d", tcp.Tag, n+1) + } + return fmt.Sprintf("/%s%s/tag:%s/page/%d", prefix, tcp.Alias, tcp.Tag, n+1) +} + func NewCollectionObj(c *Collection) *CollectionObj { return &CollectionObj{ Collection: *c, @@ -916,10 +940,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e } // Serve collection - displayPage := struct { - CollectionPage - Tag string - }{ + displayPage := TagCollectionPage{ CollectionPage: CollectionPage{ DisplayCollection: coll, StaticPage: pageForReq(app, r), From 4c0fcdf7c69be84933453ebe43aef4dbe5595d31 Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:55:56 -0500 Subject: [PATCH 03/31] Setting activitypub.go back to master version --- activitypub.go | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/activitypub.go b/activitypub.go index 18307de..db42726 100644 --- a/activitypub.go +++ b/activitypub.go @@ -397,9 +397,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request go func() { if to == nil { - if debugging { - log.Error("No `to` value!") - } + log.Error("No to! %v", err) return } @@ -494,7 +492,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b)) r.Header.Add("Content-Type", "application/activity+json") - r.Header.Set("User-Agent", ServerUserAgent(hostName)) + r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") h := sha256.New() h.Write(b) r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) @@ -544,7 +542,7 @@ func resolveIRI(hostName, url string) ([]byte, error) { r, _ := http.NewRequest("GET", url, nil) r.Header.Add("Accept", "application/activity+json") - r.Header.Set("User-Agent", ServerUserAgent(hostName)) + r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") if debugging { dump, err := httputil.DumpRequestOut(r, true) @@ -631,17 +629,6 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { log.Info("Federating new post!") } } - - // If app is private, do not federate - if app.cfg.App.Private { - return nil - } - - // Do not federate posts from private or protected blogs - if p.Collection.Visibility == CollPrivate || p.Collection.Visibility == CollProtected { - return nil - } - actor := p.Collection.PersonObject(collID) na := p.ActivityObject(app) @@ -710,10 +697,6 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { // I don't believe we'd ever have too many mentions in a single post that this // could become a burden. remoteUser, err := getRemoteUser(app, tag.HRef) - if err != nil { - log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err) - continue - } err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity) if err != nil { log.Error("Couldn't post! %v", err) From ebdb9320903f30dd85ed2f65d4ae65c9f0ce5a20 Mon Sep 17 00:00:00 2001 From: Donald Feury Date: Wed, 24 Feb 2021 23:57:35 -0500 Subject: [PATCH 04/31] I meant develop, not master --- activitypub.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/activitypub.go b/activitypub.go index db42726..0e69075 100644 --- a/activitypub.go +++ b/activitypub.go @@ -397,7 +397,9 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request go func() { if to == nil { - log.Error("No to! %v", err) + if debugging { + log.Error("No `to` value!") + } return } @@ -492,7 +494,7 @@ func makeActivityPost(hostName string, p *activitystreams.Person, url string, m r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b)) r.Header.Add("Content-Type", "application/activity+json") - r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") + r.Header.Set("User-Agent", ServerUserAgent(hostName)) h := sha256.New() h.Write(b) r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil))) @@ -542,7 +544,7 @@ func resolveIRI(hostName, url string) ([]byte, error) { r, _ := http.NewRequest("GET", url, nil) r.Header.Add("Accept", "application/activity+json") - r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")") + r.Header.Set("User-Agent", ServerUserAgent(hostName)) if debugging { dump, err := httputil.DumpRequestOut(r, true) @@ -697,6 +699,10 @@ func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error { // I don't believe we'd ever have too many mentions in a single post that this // could become a burden. remoteUser, err := getRemoteUser(app, tag.HRef) + if err != nil { + log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err) + continue + } err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity) if err != nil { log.Error("Couldn't post! %v", err) From 276304d5b8614863d3f40f3fae3b053d6163380c Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 27 Jun 2021 10:35:36 -0400 Subject: [PATCH 05/31] Rearrange applyMarkdownSpecial parameters --- postrender.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/postrender.go b/postrender.go index 8e71109..1e265fa 100644 --- a/postrender.go +++ b/postrender.go @@ -119,7 +119,7 @@ func (p *PublicPost) augmentReadingDestination() { } func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string { - return applyMarkdownSpecial(data, false, baseURL, cfg) + return applyMarkdownSpecial(data, baseURL, cfg, false) } func disableYoutubeAutoplay(outHTML string) string { @@ -141,7 +141,7 @@ func disableYoutubeAutoplay(outHTML string) string { return outHTML } -func applyMarkdownSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *config.Config) string { +func applyMarkdownSpecial(data []byte, baseURL string, cfg *config.Config, skipNoFollow bool) string { mdExtensions := 0 | blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_FENCED_CODE | From cbc24274753454e6af486f8a8f532ab4228fda41 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 27 Jun 2021 10:51:53 -0400 Subject: [PATCH 06/31] Don't apply "nofollow" to links on single-user instances --- postrender.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postrender.go b/postrender.go index 1e265fa..4d1a24d 100644 --- a/postrender.go +++ b/postrender.go @@ -119,7 +119,7 @@ func (p *PublicPost) augmentReadingDestination() { } func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string { - return applyMarkdownSpecial(data, baseURL, cfg, false) + return applyMarkdownSpecial(data, baseURL, cfg, cfg.App.SingleUser) } func disableYoutubeAutoplay(outHTML string) string { From f4977c7a3484607a63ba32050c41ba206eeda1f6 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 13 Sep 2021 18:36:36 -0400 Subject: [PATCH 07/31] Support filtering blog posts by language Ref T805 --- collections.go | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++ database.go | 55 ++++++++++++++++++++++++++++ routes.go | 1 + 3 files changed, 153 insertions(+) diff --git a/collections.go b/collections.go index beadf19..a3889a0 100644 --- a/collections.go +++ b/collections.go @@ -28,6 +28,7 @@ import ( "github.com/writeas/web-core/activitystreams" "github.com/writeas/web-core/auth" "github.com/writeas/web-core/bots" + "github.com/writeas/web-core/i18n" "github.com/writeas/web-core/log" waposts "github.com/writeas/web-core/posts" "github.com/writefreely/writefreely/author" @@ -366,6 +367,16 @@ func (c CollectionPage) DisplayMonetization() string { return displayMonetization(c.Monetization, c.Alias) } +func (c *DisplayCollection) Direction() string { + if c.Language == "" { + return "auto" + } + if i18n.LangIsRTL(c.Language) { + return "rtl" + } + return "ltr" +} + func newCollection(app *App, w http.ResponseWriter, r *http.Request) error { reqJSON := IsJSON(r) alias := r.FormValue("alias") @@ -991,6 +1002,92 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e return nil } +func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) error { + vars := mux.Vars(r) + lang := vars["lang"] + + cr := &collectionReq{} + err := processCollectionRequest(cr, vars, w, r) + if err != nil { + return err + } + + u, err := checkUserForCollection(app, cr, r, false) + if err != nil { + return err + } + + page := getCollectionPage(vars) + + c, err := processCollectionPermissions(app, cr, u, w, r) + if c == nil || err != nil { + return err + } + + coll := newDisplayCollection(c, cr, page) + coll.Language = lang + + coll.Posts, _ = app.db.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner) + if err != nil { + return ErrCollectionPageNotFound + } + + // Serve collection + displayPage := struct { + CollectionPage + Tag string + }{ + CollectionPage: CollectionPage{ + DisplayCollection: coll, + StaticPage: pageForReq(app, r), + IsCustomDomain: cr.isCustomDomain, + }, + Tag: lang, + } + var owner *User + if u != nil { + displayPage.Username = u.Username + displayPage.IsOwner = u.ID == coll.OwnerID + if displayPage.IsOwner { + // Add in needed information for users viewing their own collection + owner = u + displayPage.CanPin = true + + pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host) + if err != nil { + log.Error("unable to fetch collections: %v", err) + } + displayPage.Collections = pubColls + } + } + isOwner := owner != nil + if !isOwner { + // Current user doesn't own collection; retrieve owner information + owner, err = app.db.GetUserByID(coll.OwnerID) + if err != nil { + // Log the error and just continue + log.Error("Error getting user for collection: %v", err) + } + if owner.IsSilenced() { + return ErrCollectionNotFound + } + } + displayPage.Silenced = owner != nil && owner.IsSilenced() + displayPage.Owner = owner + coll.Owner = displayPage.Owner + // Add more data + // TODO: fix this mess of collections inside collections + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner) + displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer") + + err = templates["collection"].ExecuteTemplate(w, "collection", displayPage) + if err != nil { + log.Error("Unable to render collection lang page: %v", err) + } + + return nil +} + func handleCollectionPostRedirect(app *App, w http.ResponseWriter, r *http.Request) error { vars := mux.Vars(r) slug := vars["slug"] diff --git a/database.go b/database.go index f474ae9..455a617 100644 --- a/database.go +++ b/database.go @@ -1260,6 +1260,61 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin return &posts, nil } +func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string, page int, includeFuture bool) (*[]PublicPost, error) { + collID := c.ID + + cf := c.NewFormat() + order := "DESC" + if cf.Ascending() { + order = "ASC" + } + + pagePosts := cf.PostsPerPage() + start := page*pagePosts - pagePosts + if page == 0 { + start = 0 + pagePosts = 1000 + } + + limitStr := "" + if page > 0 { + limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts) + } + timeCondition := "" + if !includeFuture { + timeCondition = "AND created <= " + db.now() + } + + rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND language = ? "+timeCondition+" ORDER BY created "+order+limitStr, collID, lang) + if err != nil { + log.Error("Failed selecting from posts: %v", err) + return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."} + } + defer rows.Close() + + // TODO: extract this common row scanning logic for queries using `postCols` + posts := []PublicPost{} + for rows.Next() { + p := &Post{} + err = rows.Scan(&p.ID, &p.Slug, &p.Font, &p.Language, &p.RTL, &p.Privacy, &p.OwnerID, &p.CollectionID, &p.PinnedPosition, &p.Created, &p.Updated, &p.ViewCount, &p.Title, &p.Content) + if err != nil { + log.Error("Failed scanning row: %v", err) + break + } + p.extractData() + p.augmentContent(c) + p.formatContent(cfg, c, includeFuture, false) + + posts = append(posts, p.processPost()) + } + err = rows.Err() + if err != nil { + log.Error("Error after Next() on rows: %v", err) + } + + return &posts, nil +} + func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) { rows, err := db.Query("SELECT actor_id, inbox, shared_inbox FROM remotefollows f INNER JOIN remoteusers u ON f.remote_user_id = u.id WHERE collection_id = ?", c.ID) if err != nil { diff --git a/routes.go b/routes.go index 213958d..22d2496 100644 --- a/routes.go +++ b/routes.go @@ -216,6 +216,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) + r.HandleFunc("/lang:{lang}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) From c4b124e37c9edab7a58e25fb1f300c9deaf4bc0d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 16 Sep 2021 14:05:52 -0400 Subject: [PATCH 08/31] Limit lang filter to 2 characters Ref T805 --- routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes.go b/routes.go index 22d2496..aa3b4f2 100644 --- a/routes.go +++ b/routes.go @@ -216,7 +216,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) - r.HandleFunc("/lang:{lang}", handler.Web(handleViewCollectionLang, UserLevelOptional)) + r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) From 414d5b0a1c7addd89ad95d9b20737e8b25459a8b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 16 Sep 2021 14:23:35 -0400 Subject: [PATCH 09/31] Add pagination routes on lang post filter Ref T805 --- collections.go | 14 ++++++++++++++ database.go | 15 ++++++++++++++- routes.go | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/collections.go b/collections.go index a3889a0..db71295 100644 --- a/collections.go +++ b/collections.go @@ -1027,6 +1027,20 @@ func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) coll := newDisplayCollection(c, cr, page) coll.Language = lang + ttlPosts, err := app.db.GetCollLangTotalPosts(coll.ID, lang) + if err != nil { + log.Error("Unable to getCollLangTotalPosts: %s", err) + } + pagePosts := coll.Format.PostsPerPage() + coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts))) + if coll.TotalPages > 0 && page > coll.TotalPages { + redirURL := fmt.Sprintf("/lang:%s/page/%d", lang, coll.TotalPages) + if !app.cfg.App.SingleUser { + redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL) + } + return impart.HTTPError{http.StatusFound, redirURL} + } + coll.Posts, _ = app.db.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner) if err != nil { return ErrCollectionPageNotFound diff --git a/database.go b/database.go index 455a617..227fc0e 100644 --- a/database.go +++ b/database.go @@ -1260,6 +1260,16 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin return &posts, nil } +func (db *datastore) GetCollLangTotalPosts(collID int64, lang string) (uint64, error) { + var articles uint64 + err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ?", collID, lang).Scan(&articles) + if err != nil && err != sql.ErrNoRows { + log.Error("Couldn't get total lang posts count for collection %d: %v", collID, err) + return 0, err + } + return articles, nil +} + func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string, page int, includeFuture bool) (*[]PublicPost, error) { collID := c.ID @@ -1285,7 +1295,10 @@ func (db *datastore) GetLangPosts(cfg *config.Config, c *Collection, lang string timeCondition = "AND created <= " + db.now() } - rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? AND language = ? "+timeCondition+" ORDER BY created "+order+limitStr, collID, lang) + rows, err := db.Query(`SELECT `+postCols+` +FROM posts +WHERE collection_id = ? AND language = ? `+timeCondition+` +ORDER BY created `+order+limitStr, collID, lang) if err != nil { log.Error("Failed selecting from posts: %v", err) return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve collection posts."} diff --git a/routes.go b/routes.go index aa3b4f2..bdc3d04 100644 --- a/routes.go +++ b/routes.go @@ -217,6 +217,7 @@ func RouteCollections(handler *Handler, r *mux.Router) { r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional)) r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader)) r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional)) + r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) From e91748c0bcc783916553fcc0cb40734c607dc469 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Thu, 16 Sep 2021 15:53:07 -0400 Subject: [PATCH 10/31] Return correct count of currently-published lang posts Previously, we'd include scheduled posts, too. Ref T805 --- database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database.go b/database.go index 227fc0e..efc1a10 100644 --- a/database.go +++ b/database.go @@ -1262,7 +1262,7 @@ func (db *datastore) GetPostsTagged(cfg *config.Config, c *Collection, tag strin func (db *datastore) GetCollLangTotalPosts(collID int64, lang string) (uint64, error) { var articles uint64 - err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ?", collID, lang).Scan(&articles) + err := db.QueryRow("SELECT COUNT(*) FROM posts WHERE collection_id = ? AND language = ? AND created <= "+db.now(), collID, lang).Scan(&articles) if err != nil && err != sql.ErrNoRows { log.Error("Couldn't get total lang posts count for collection %d: %v", collID, err) return 0, err From 54eb2db14d591f69f113a64417e0bede104cb6a7 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sat, 8 Jul 2023 00:31:02 -0400 Subject: [PATCH 11/31] Fix tagged posts falsely showing Older link --- collections.go | 9 ++++++- database.go | 46 ++++++++++++++++++++++++++++++++++ templates/collection-tags.tmpl | 4 +-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/collections.go b/collections.go index 9e9c34c..b78bb52 100644 --- a/collections.go +++ b/collections.go @@ -975,7 +975,14 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e coll := newDisplayCollection(c, cr, page) - coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(coll.Format.PostsPerPage()))) + taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner) + if err != nil { + return err + } + + ttlPosts := len(taggedPostIDs) + pagePosts := coll.Format.PostsPerPage() + coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts))) if coll.TotalPages > 0 && page > coll.TotalPages { redirURL := fmt.Sprintf("/page/%d", coll.TotalPages) if !app.cfg.App.SingleUser { diff --git a/database.go b/database.go index 8bd5a43..f2a87dc 100644 --- a/database.go +++ b/database.go @@ -113,6 +113,7 @@ type writestore interface { GetPostsCount(c *CollectionObj, includeFuture bool) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) + GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error) GetAPFollowers(c *Collection) (*[]RemoteUser, error) @@ -1195,6 +1196,51 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu return &posts, nil } +func (db *datastore) GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) { + collID := c.ID + + cf := c.NewFormat() + order := "DESC" + if cf.Ascending() { + order = "ASC" + } + + timeCondition := "" + if !includeFuture { + timeCondition = "AND created <= NOW()" + } + var rows *sql.Rows + var err error + if db.driverName == driverSQLite { + rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) regexp ? "+timeCondition+" ORDER BY created "+order, collID, `.*#`+strings.ToLower(tag)+`\b.*`) + } else { + rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) RLIKE ? "+timeCondition+" ORDER BY created "+order, collID, "#"+strings.ToLower(tag)+"[[:>:]]") + } + if err != nil { + log.Error("Failed selecting tagged posts: %v", err) + return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve tagged collection posts."} + } + defer rows.Close() + + ids := []string{} + for rows.Next() { + var id string + err = rows.Scan(&id) + if err != nil { + log.Error("Failed scanning row: %v", err) + break + } + + ids = append(ids, id) + } + err = rows.Err() + if err != nil { + log.Error("Error after Next() on rows: %v", err) + } + + return ids, nil +} + // GetPostsTagged retrieves all posts on the given Collection that contain the // given tag. // It will return future posts if `includeFuture` is true. diff --git a/templates/collection-tags.tmpl b/templates/collection-tags.tmpl index c164978..c8e8a12 100644 --- a/templates/collection-tags.tmpl +++ b/templates/collection-tags.tmpl @@ -63,8 +63,8 @@ {{template "posts" .}} {{if gt .TotalPages 1}}