shiori/internal/database/database.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

113 lines
3.1 KiB
Go

package database
import (
"context"
"embed"
"log"
"github.com/go-shiori/shiori/internal/model"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
//go:embed migrations/*
var migrations embed.FS
// OrderMethod is the order method for getting bookmarks
type OrderMethod int
const (
// DefaultOrder is oldest to newest.
DefaultOrder OrderMethod = iota
// ByLastAdded is from newest addition to the oldest.
ByLastAdded
// ByLastModified is from latest modified to the oldest.
ByLastModified
)
// GetBookmarksOptions is options for fetching bookmarks from database.
type GetBookmarksOptions struct {
IDs []int
Tags []string
ExcludedTags []string
Keyword string
WithContent bool
OrderMethod OrderMethod
Limit int
Offset int
}
// GetAccountsOptions is options for fetching accounts from database.
type GetAccountsOptions struct {
Keyword string
Owner bool
}
// DB is interface for accessing and manipulating data in database.
type DB interface {
// Migrate runs migrations for this database
Migrate() error
// SaveBookmarks saves bookmarks data to database.
SaveBookmarks(ctx context.Context, create bool, bookmarks ...model.Bookmark) ([]model.Bookmark, error)
// GetBookmarks fetch list of bookmarks based on submitted options.
GetBookmarks(ctx context.Context, opts GetBookmarksOptions) ([]model.Bookmark, error)
// GetBookmarksCount get count of bookmarks in database.
GetBookmarksCount(ctx context.Context, opts GetBookmarksOptions) (int, error)
// DeleteBookmarks removes all record with matching ids from database.
DeleteBookmarks(ctx context.Context, ids ...int) error
// GetBookmark fetchs bookmark based on its ID or URL.
GetBookmark(ctx context.Context, id int, url string) (model.Bookmark, bool, error)
// SaveAccount saves new account in database
SaveAccount(ctx context.Context, a model.Account) error
// GetAccounts fetch list of account (without its password) with matching keyword.
GetAccounts(ctx context.Context, opts GetAccountsOptions) ([]model.Account, error)
// GetAccount fetch account with matching username.
GetAccount(ctx context.Context, username string) (model.Account, bool, error)
// DeleteAccounts removes all record with matching usernames
DeleteAccounts(ctx context.Context, usernames ...string) error
// GetTags fetch list of tags and its frequency from database.
GetTags(ctx context.Context) ([]model.Tag, error)
// RenameTag change the name of a tag.
RenameTag(ctx context.Context, id int, newName string) error
// CreateNewID creates new id for specified table.
CreateNewID(ctx context.Context, table string) (int, error)
}
type dbbase struct {
sqlx.DB
}
func (db *dbbase) withTx(ctx context.Context, fn func(tx *sqlx.Tx) error) error {
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
return errors.WithStack(err)
}
defer func() {
if err := tx.Commit(); err != nil {
log.Printf("error during commit: %s", err)
}
}()
err = fn(tx)
if err != nil {
if err := tx.Rollback(); err != nil {
log.Printf("error during rollback: %s", err)
}
return errors.WithStack(err)
}
return err
}