From b22d328c7585bd223c81b3cd674d80e4c991929f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Erbsh=C3=A4u=C3=9Fer?= Date: Sun, 24 May 2026 16:06:52 +0200 Subject: [PATCH] add endpoint to update blog article --- backend/blog.go | 45 +++++++++++++++- backend/db.go | 138 ++++++++++++++++++++++++++++++++++++------------ backend/main.go | 3 ++ 3 files changed, 151 insertions(+), 35 deletions(-) diff --git a/backend/blog.go b/backend/blog.go index 01cf3fd..5c17b0a 100644 --- a/backend/blog.go +++ b/backend/blog.go @@ -41,6 +41,8 @@ type ArticleFile struct { Data []byte } +const maxArticleFormSize = 10 * 1024 * 1024 + func ParseArticle(reader io.Reader, filePrefix string) (*Article, error) { tarFiles := make(map[string][]byte) tarReader := tar.NewReader(reader) @@ -197,7 +199,7 @@ func (h *ApiHandler) ServeBlogGet(writer http.ResponseWriter, request *http.Requ } func (h *ApiHandler) ServeBlogPut(writer http.ResponseWriter, request *http.Request) { - err := request.ParseMultipartForm(10 * 1024 * 1024) + err := request.ParseMultipartForm(maxArticleFormSize) if err != nil { WriteError(writer, http.StatusBadRequest, "failed to parse multipart form", nil) return @@ -259,6 +261,47 @@ func (h *ApiHandler) ServeBlogGetSingle(writer http.ResponseWriter, request *htt WriteResponse(writer, http.StatusOK, article) } +func (h *ApiHandler) ServeBlogPatch(writer http.ResponseWriter, request *http.Request) { + idStr := request.PathValue("id") + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + WriteError(writer, http.StatusBadRequest, "invalid id", err) + return + } + + err = request.ParseMultipartForm(maxArticleFormSize) + if err != nil { + WriteError(writer, http.StatusBadRequest, "failed to parse multipart form", nil) + return + } + + file, _, err := request.FormFile("file") + if err != nil { + WriteError(writer, http.StatusBadRequest, "failed to parse file", nil) + return + } + defer func() { + _ = file.Close() + }() + + article, err := ParseArticle(file, "/api/blog/"+strconv.FormatInt(id, 10)+"/file/") + if err != nil { + WriteError(writer, http.StatusInternalServerError, "failed to parse article: "+err.Error(), nil) + return + } + + article.Id = id + err = h.db.UpdateBlogArticle(article) + if err != nil { + WriteError(writer, http.StatusInternalServerError, "failed to write database", err) + return + } + + log.Printf("updated blog article '%s' with id %d", article.Title, article.Id) + + WriteResponse(writer, http.StatusOK, map[string]interface{}{}) +} + func (h *ApiHandler) ServeBlogDelete(writer http.ResponseWriter, request *http.Request) { idStr := request.PathValue("id") id, err := strconv.ParseInt(idStr, 10, 64) diff --git a/backend/db.go b/backend/db.go index 19b0a96..9155eb5 100644 --- a/backend/db.go +++ b/backend/db.go @@ -55,6 +55,38 @@ func (db *Database) ValidateRootPassword(password string) (bool, error) { return slices.Compare(passwordHash[:], rootPasswordHash) == 0, nil } +func (db *Database) createBlogArticleSupplements(tx *sql.Tx, id int64, tags []string, files []ArticleFile) error { + for _, tag := range tags { + tagId, err := createOrGetTag(tx, tag) + if err != nil { + return err + } + + _, err = tx.Exec( + "INSERT INTO blog_article_to_tag(tag_id, article_id) VALUES (?, ?)", + tagId, + id, + ) + if err != nil { + return err + } + } + + for _, file := range files { + _, err := tx.Exec( + "INSERT INTO blog_file(id, article_id, data) VALUES (?, ?, ?)", + file.Id, + id, + file.Data, + ) + if err != nil { + return err + } + } + + return nil +} + func (db *Database) CreateBlogArticle() (int64, func(*Article) error, error) { tx, err := db.db.Begin() if err != nil { @@ -69,11 +101,13 @@ func (db *Database) CreateBlogArticle() (int64, func(*Article) error, error) { "", ) if err != nil { + _ = tx.Rollback() return -1, nil, err } id, err := res.LastInsertId() if err != nil { + _ = tx.Rollback() return -1, nil, err } @@ -101,33 +135,10 @@ func (db *Database) CreateBlogArticle() (int64, func(*Article) error, error) { return err } - for _, tag := range article.Tags { - tagId, err := createOrGetTag(tx, tag) - if err != nil { - _ = tx.Rollback() - return err - } - - _, err = tx.Exec( - "INSERT INTO blog_article_to_tag(tag_id, article_id) VALUES (?, ?)", - tagId, - id, - ) - if err != nil { - return err - } - } - - for _, file := range article.Files { - _, err = tx.Exec( - "INSERT INTO blog_file(id, article_id, data) VALUES (?, ?, ?)", - file.Id, - id, - file.Data, - ) - if err != nil { - return err - } + err = db.createBlogArticleSupplements(tx, id, article.Tags, article.Files) + if err != nil { + _ = tx.Rollback() + return err } return tx.Commit() @@ -354,19 +365,78 @@ func (db *Database) SetBlogArticleStatus(id int64, status ArticleStatus) error { return nil } +func (db *Database) UpdateBlogArticle(article *Article) error { + tx, err := db.db.Begin() + if err != nil { + return err + } + + err = db.deleteBlogArticleSupplements(tx, article.Id) + if err != nil { + _ = tx.Rollback() + return err + } + + var modificationDate *string + if article.ModificationDate != nil { + tmp := article.ModificationDate.Format(time.DateOnly) + modificationDate = &tmp + } + + res, err := tx.Exec( + "UPDATE blog_article SET title = ?, date = ?, modification_date = ?, content = ? WHERE id = ?", + article.Title, + article.ReleaseDate.Format(time.DateOnly), + modificationDate, + article.Content, + article.Id, + ) + if err != nil { + _ = tx.Rollback() + return err + } + + affectedCount, err := res.RowsAffected() + if err != nil { + _ = tx.Rollback() + return err + } + + if affectedCount == 0 { + _ = tx.Rollback() + return ErrNotFound + } + + err = db.createBlogArticleSupplements(tx, article.Id, article.Tags, article.Files) + if err != nil { + _ = tx.Rollback() + return err + } + + return tx.Commit() +} + +func (db *Database) deleteBlogArticleSupplements(tx *sql.Tx, id int64) error { + _, err := tx.Exec("DELETE FROM blog_file WHERE article_id = ?", id) + if err != nil { + return err + } + + _, err = tx.Exec("DELETE FROM blog_article_to_tag WHERE article_id = ?", id) + if err != nil { + return err + } + + return nil +} + func (db *Database) DeleteBlogArticle(id int64) error { tx, err := db.db.Begin() if err != nil { return err } - _, err = tx.Exec("DELETE FROM blog_file WHERE article_id = ?", id) - if err != nil { - _ = tx.Rollback() - return err - } - - _, err = tx.Exec("DELETE FROM blog_article_to_tag WHERE article_id = ?", id) + err = db.deleteBlogArticleSupplements(tx, id) if err != nil { _ = tx.Rollback() return err diff --git a/backend/main.go b/backend/main.go index 40ba3f1..d63763b 100644 --- a/backend/main.go +++ b/backend/main.go @@ -54,6 +54,9 @@ func main() { mux.Handle("GET /api/blog/{id}", apiHandler.ProcessAuth(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { apiHandler.ServeBlogGetSingle(writer, request) }), false)) + mux.Handle("PATCH /api/blog/{id}", apiHandler.ProcessAuth(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { + apiHandler.ServeBlogPatch(writer, request) + }), true)) mux.Handle("DELETE /api/blog/{id}", apiHandler.ProcessAuth(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { apiHandler.ServeBlogDelete(writer, request) }), true))