extend blog articles endpoint to allow to query for tags

Signed-off-by: Tobias Erbshäußer <tobias@tesoft.dev>
This commit is contained in:
2026-05-24 09:22:52 +02:00
parent 996a538704
commit df6f9d692b
2 changed files with 54 additions and 20 deletions
+11 -2
View File
@@ -155,6 +155,7 @@ func (h *ApiHandler) ServeBlogGet(writer http.ResponseWriter, request *http.Requ
var err error var err error
var offset int var offset int
var limit int var limit int
tags := make([]string, 0)
offsetStr := query.Get("offset") offsetStr := query.Get("offset")
if offsetStr == "" { 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 { if err != nil {
WriteError(writer, http.StatusInternalServerError, "failed to query database", err) WriteError(writer, http.StatusInternalServerError, "failed to query database", err)
return 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) { func (h *ApiHandler) ServeBlogPut(writer http.ResponseWriter, request *http.Request) {
+43 -18
View File
@@ -8,6 +8,7 @@ import (
"log" "log"
"slices" "slices"
"strconv" "strconv"
"strings"
"time" "time"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
@@ -134,29 +135,53 @@ func (db *Database) CreateBlogArticle() (int64, func(*Article) error, error) {
}, err }, err
} }
func (db *Database) GetBlogArticles(showAll bool, offset int, limit int) ([]ArticleProperties, error) { func (db *Database) GetBlogArticles(showAll bool, offset int, limit int, tags []string) ([]ArticleProperties, int64, error) {
inner := "SELECT id FROM blog_article" filters := make([]string, 0)
filterArgs := make([]interface{}, 0)
if !showAll { 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 ?" if len(tags) > 0 {
outer := "SELECT blog_article.id, blog_article.status, blog_article.title, blog_article.date, blog_article.modification_date, blog_tag.name" + filters = append(filters, "blog_tag.name IN (?"+strings.Repeat(", ?", len(tags)-1)+") GROUP BY blog_article.id HAVING COUNT(DISTINCT blog_tag.id) = ?")
" FROM blog_article" + for _, tag := range tags {
" LEFT JOIN blog_article_to_tag ON blog_article.id = blog_article_to_tag.article_id" + filterArgs = append(filterArgs, tag)
" LEFT JOIN blog_tag ON blog_article_to_tag.tag_id = blog_tag.id" + }
" WHERE blog_article.id IN (" + inner + ")" + filterArgs = append(filterArgs, len(tags))
" ORDER BY blog_article.date DESC, blog_article.id" }
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( rows, err := db.db.Query(
outer, "SELECT blog_article.id, blog_article.status, blog_article.title, blog_article.date, blog_article.modification_date, blog_tag.name,"+
limit, " (SELECT COUNT(DISTINCT blog_article.id) FROM blog_article"+joins+filter+")"+
offset, " 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 { if err != nil {
return nil, err return nil, 0, err
} }
articles := make([]ArticleProperties, 0) articles := make([]ArticleProperties, 0)
var total int64
for rows.Next() { for rows.Next() {
var id int64 var id int64
var status ArticleStatus var status ArticleStatus
@@ -164,10 +189,10 @@ func (db *Database) GetBlogArticles(showAll bool, offset int, limit int) ([]Arti
var dateStr string var dateStr string
var modificationDateStr *string var modificationDateStr *string
var tag *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 { if err != nil {
_ = rows.Close() _ = rows.Close()
return nil, err return nil, 0, err
} }
if tag != nil && len(articles) > 0 && articles[len(articles)-1].Id == id { 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() err = rows.Close()
if err != nil { 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) { func (db *Database) GetBlogArticle(showAll bool, id int64) (*Article, error) {