shiori/internal/webserver/handler-api-ext.go
Felipe Martin Garcia 05fee53bd0
fix: saving bookmarks inconsistencies (#500)
* chore: updated go-migrate dependencies

* fix: specify if we're saving bookmarks expecting a creation

up until now the SaveBookmarks method was doing some "magic" to do
"upserts" on the databases, but consistency between engines was scarce
and not knowing if we were expecting saving a new bookmark or updating
an existing one was leading to errors and inconsistencies in logic all
around the place. Now we need to specify a creation boolean when
saving and a differnt query will be make (INSERT vs UPDATE).

* fix(api): using incorrect bookmark for content downlaod

* test(db): added test pipeline for databases

Added functions that will share logic among the engines and will be
called on fresh databases on each test run

* dev: added basic docker-compose for development

* chore: uncommented tests

* ci(test): added mysql service

* typo

* test(mysql): select database after reset

* fix(mysql): ignore empty row errors when parsing tags

* fix(mysql): handle insert errors

* chore: added mysql variables to compose

* ci: explicit mysql service port exposed
2022-10-11 23:47:38 +02:00

150 lines
3.7 KiB
Go

package webserver
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
fp "path/filepath"
"strconv"
"github.com/go-shiori/shiori/internal/core"
"github.com/go-shiori/shiori/internal/model"
"github.com/julienschmidt/httprouter"
)
// apiInsertViaExtension is handler for POST /api/bookmarks/ext
func (h *handler) apiInsertViaExtension(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
ctx := r.Context()
// Make sure session still valid
err := h.validateSession(r)
checkError(err)
// Decode request
request := model.Bookmark{}
err = json.NewDecoder(r.Body).Decode(&request)
checkError(err)
// Clean up bookmark URL
request.URL, err = core.RemoveUTMParams(request.URL)
if err != nil {
panic(fmt.Errorf("failed to clean URL: %v", err))
}
// Check if bookmark already exists.
book, exist, err := h.DB.GetBookmark(ctx, 0, request.URL)
if err != nil {
panic(fmt.Errorf("failed to get bookmark, URL: %v", err))
}
// If it already exists, we need to set ID and tags.
if exist {
book.HTML = request.HTML
mapOldTags := map[string]model.Tag{}
for _, oldTag := range book.Tags {
mapOldTags[oldTag.Name] = oldTag
}
for _, newTag := range request.Tags {
if _, tagExist := mapOldTags[newTag.Name]; !tagExist {
book.Tags = append(book.Tags, newTag)
}
}
} else {
book = request
book.ID, err = h.DB.CreateNewID(ctx, "bookmark")
if err != nil {
panic(fmt.Errorf("failed to create ID: %v", err))
}
}
// Since we are using extension, the extension might send the HTML content
// so no need to download it again here. However, if it's empty, it might be not HTML file
// so we download it here.
var contentType string
var contentBuffer io.Reader
if book.HTML == "" {
contentBuffer, contentType, _ = core.DownloadBookmark(book.URL)
} else {
contentType = "text/html; charset=UTF-8"
contentBuffer = bytes.NewBufferString(book.HTML)
}
// At this point the web page already downloaded.
// Time to process it.
if contentBuffer != nil {
book.CreateArchive = true
request := core.ProcessRequest{
DataDir: h.DataDir,
Bookmark: book,
Content: contentBuffer,
ContentType: contentType,
}
var isFatalErr bool
book, isFatalErr, err = core.ProcessBookmark(request)
if tmp, ok := contentBuffer.(io.ReadCloser); ok {
tmp.Close()
}
if err != nil && isFatalErr {
panic(fmt.Errorf("failed to process bookmark: %v", err))
}
}
if _, err := h.DB.SaveBookmarks(ctx, true, book); err != nil {
log.Printf("error saving bookmark after downloading content: %s", err)
}
// Save bookmark to database
results, err := h.DB.SaveBookmarks(ctx, false, book)
if err != nil || len(results) == 0 {
panic(fmt.Errorf("failed to save bookmark: %v", err))
}
book = results[0]
// Return the new bookmark
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(&book)
checkError(err)
}
// apiDeleteViaExtension is handler for DELETE /api/bookmark/ext
func (h *handler) apiDeleteViaExtension(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
ctx := r.Context()
// Make sure session still valid
err := h.validateSession(r)
checkError(err)
// Decode request
request := model.Bookmark{}
err = json.NewDecoder(r.Body).Decode(&request)
checkError(err)
// Check if bookmark already exists.
book, exist, err := h.DB.GetBookmark(ctx, 0, request.URL)
checkError(err)
if exist {
// Delete bookmarks
err = h.DB.DeleteBookmarks(ctx, book.ID)
checkError(err)
// Delete thumbnail image and archives from local disk
strID := strconv.Itoa(book.ID)
imgPath := fp.Join(h.DataDir, "thumb", strID)
archivePath := fp.Join(h.DataDir, "archive", strID)
os.Remove(imgPath)
os.Remove(archivePath)
}
fmt.Fprint(w, 1)
}