diff --git a/backend/blog.go b/backend/blog.go index 59ae230..764a1bc 100644 --- a/backend/blog.go +++ b/backend/blog.go @@ -155,6 +155,7 @@ func (h *ApiHandler) ServeBlogGet(writer http.ResponseWriter, request *http.Requ var err error var offset int var limit int + tags := make([]string, 0) offsetStr := query.Get("offset") if offsetStr == "" { @@ -178,13 +179,21 @@ func (h *ApiHandler) ServeBlogGet(writer http.ResponseWriter, request *http.Requ } } - articles, err := h.db.GetBlogArticles(IsAuthorized(request), offset, limit) + tagsStr := query.Get("tags") + if tagsStr != "" { + tags = strings.Split(tagsStr, ",") + } + + articles, total, err := h.db.GetBlogArticles(IsAuthorized(request), offset, limit, tags) if err != nil { WriteError(writer, http.StatusInternalServerError, "failed to query database", err) return } - WriteResponse(writer, http.StatusOK, articles) + WriteResponse(writer, http.StatusOK, map[string]interface{}{ + "articles": articles, + "total": total, + }) } func (h *ApiHandler) ServeBlogPut(writer http.ResponseWriter, request *http.Request) { diff --git a/backend/db.go b/backend/db.go index 8d2dfa5..e98d658 100644 --- a/backend/db.go +++ b/backend/db.go @@ -8,6 +8,7 @@ import ( "log" "slices" "strconv" + "strings" "time" _ "github.com/mattn/go-sqlite3" @@ -134,29 +135,53 @@ func (db *Database) CreateBlogArticle() (int64, func(*Article) error, error) { }, err } -func (db *Database) GetBlogArticles(showAll bool, offset int, limit int) ([]ArticleProperties, error) { - inner := "SELECT id FROM blog_article" +func (db *Database) GetBlogArticles(showAll bool, offset int, limit int, tags []string) ([]ArticleProperties, int64, error) { + filters := make([]string, 0) + filterArgs := make([]interface{}, 0) + if !showAll { - inner = inner + " WHERE status = " + strconv.Itoa(ArticleStatusPublished) + filters = append(filters, "blog_article.status = ?") + filterArgs = append(filterArgs, ArticleStatusPublished) } - inner = inner + " ORDER BY date DESC LIMIT ? OFFSET ?" - outer := "SELECT blog_article.id, blog_article.status, blog_article.title, blog_article.date, blog_article.modification_date, blog_tag.name" + - " FROM blog_article" + - " LEFT JOIN blog_article_to_tag ON blog_article.id = blog_article_to_tag.article_id" + - " LEFT JOIN blog_tag ON blog_article_to_tag.tag_id = blog_tag.id" + - " WHERE blog_article.id IN (" + inner + ")" + - " ORDER BY blog_article.date DESC, blog_article.id" + if len(tags) > 0 { + filters = append(filters, "blog_tag.name IN (?"+strings.Repeat(", ?", len(tags)-1)+") GROUP BY blog_article.id HAVING COUNT(DISTINCT blog_tag.id) = ?") + for _, tag := range tags { + filterArgs = append(filterArgs, tag) + } + filterArgs = append(filterArgs, len(tags)) + } + + filter := "" + if len(filters) > 0 { + filter = " WHERE " + strings.Join(filters, " AND ") + } + + joins := " LEFT JOIN blog_article_to_tag ON blog_article.id = blog_article_to_tag.article_id" + + " LEFT JOIN blog_tag ON blog_article_to_tag.tag_id = blog_tag.id" + + args := make([]interface{}, 0, len(filterArgs)*2+2) + args = append(args, filterArgs...) + args = append(args, filterArgs...) + args = append(args, limit) + args = append(args, offset) rows, err := db.db.Query( - outer, - limit, - offset, + "SELECT blog_article.id, blog_article.status, blog_article.title, blog_article.date, blog_article.modification_date, blog_tag.name,"+ + " (SELECT COUNT(DISTINCT blog_article.id) FROM blog_article"+joins+filter+")"+ + " FROM blog_article"+joins+ + " WHERE blog_article.id IN ("+ + " SELECT blog_article.id FROM blog_article"+joins+filter+" ORDER BY date DESC LIMIT ? OFFSET ?"+ + " )"+ + " ORDER BY blog_article.date DESC, blog_article.id", + args..., ) if err != nil { - return nil, err + return nil, 0, err } articles := make([]ArticleProperties, 0) + var total int64 + for rows.Next() { var id int64 var status ArticleStatus @@ -164,10 +189,10 @@ func (db *Database) GetBlogArticles(showAll bool, offset int, limit int) ([]Arti var dateStr string var modificationDateStr *string var tag *string - err := rows.Scan(&id, &status, &title, &dateStr, &modificationDateStr, &tag) + err := rows.Scan(&id, &status, &title, &dateStr, &modificationDateStr, &tag, &total) if err != nil { _ = rows.Close() - return nil, err + return nil, 0, err } if tag != nil && len(articles) > 0 && articles[len(articles)-1].Id == id { @@ -199,10 +224,10 @@ func (db *Database) GetBlogArticles(showAll bool, offset int, limit int) ([]Arti err = rows.Close() if err != nil { - return nil, err + return nil, 0, err } - return articles, nil + return articles, total, nil } func (db *Database) GetBlogArticle(showAll bool, id int64) (*Article, error) {