diff --git a/.github/workflows/_test.yml b/.github/workflows/_test.yml index a4271964..f9779e41 100644 --- a/.github/workflows/_test.yml +++ b/.github/workflows/_test.yml @@ -21,6 +21,21 @@ jobs: --health-retries 5 ports: - 5432:5432 + mariadb: + image: mariadb + env: + MYSQL_USER: shiori + MYSQL_PASSWORD: shiori + MYSQL_DATABASE: shiori + MYSQL_ROOT_PASSWORD: shiori + options: >- + --health-cmd="mysqladmin ping" + --health-interval=5s + --health-timeout=2s + --health-retries=3 + ports: + - 3306:3306 + name: Go ${{ matrix.go }} unit tests steps: - uses: actions/checkout@v2 @@ -40,4 +55,5 @@ jobs: - run: go test ./... env: SHIORI_TEST_PG_URL: "postgres://shiori:shiori@localhost:5432/shiori?sslmode=disable" + SHIORI_TEST_MYSQL_URL: "shiori:shiori@(localhost:3306)/shiori" - run: CGO_ENABLED=0 go build -tags osusergo,netgo -ldflags="-s -w -X main.version=$(git describe --tags) -X main.date=$(date --iso-8601=seconds)" diff --git a/Dockerfile.compose b/Dockerfile.compose new file mode 100644 index 00000000..f6d077cc --- /dev/null +++ b/Dockerfile.compose @@ -0,0 +1,6 @@ +FROM docker.io/golang:1.19-alpine3.16 + +WORKDIR /src/shiori + +ENTRYPOINT ["go", "run", "main.go"] +CMD ["server"] diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..657347cc --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,46 @@ +# Docker compose for development purposes only +version: "3" +services: + shiori: + build: + context: . + dockerfile: Dockerfile.compose + container_name: shiori + ports: + - "8080:8080" + volumes: + - "./dev-data:/srv/shiori" + - ".:/src/shiori" + restart: unless-stopped + links: + - "postgres" + - "mariadb" + environment: + SHIORI_DBMS: mysql + SHIORI_PG_USER: shiori + SHIORI_PG_PASS: shiori + SHIORI_PG_NAME: shiori + SHIORI_PG_HOST: postgres + SHIORI_PG_PORT: 5432 + SHIORI_MYSQL_USER: shiori + SHIORI_MYSQL_PASS: shiori + SHIORI_MYSQL_NAME: shiori + SHIORI_MYSQL_ADDRESS: (mariadb) + + postgres: + image: postgres:13 + environment: + POSTGRES_PASSWORD: shiori + POSTGRES_USER: shiori + ports: + - "5432:5432" + + mariadb: + image: mariadb:10.9 + environment: + MYSQL_ROOT_PASSWORD: toor + MYSQL_DATABASE: shiori + MYSQL_USER: shiori + MYSQL_PASSWORD: shiori + ports: + - "3306:3306" diff --git a/go.mod b/go.mod index 01b27917..ac08ce7c 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/tdewolff/parse v2.3.4+incompatible // indirect go.etcd.io/bbolt v1.3.6 // indirect - go.uber.org/atomic v1.9.0 // indirect + go.uber.org/atomic v1.10.0 // indirect golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9 // indirect golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect diff --git a/go.sum b/go.sum index 651bee73..e1a858c0 100644 --- a/go.sum +++ b/go.sum @@ -1170,8 +1170,8 @@ go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= diff --git a/internal/cmd/add.go b/internal/cmd/add.go index 2fbd9a86..a044bb7f 100644 --- a/internal/cmd/add.go +++ b/internal/cmd/add.go @@ -111,7 +111,7 @@ func addHandler(cmd *cobra.Command, args []string) { } // Save bookmark to database - _, err = db.SaveBookmarks(cmd.Context(), book) + _, err = db.SaveBookmarks(cmd.Context(), true, book) if err != nil { cError.Printf("Failed to save bookmark: %v\n", err) os.Exit(1) diff --git a/internal/cmd/import.go b/internal/cmd/import.go index dd4df2d7..ad55d31a 100644 --- a/internal/cmd/import.go +++ b/internal/cmd/import.go @@ -154,7 +154,7 @@ func importHandler(cmd *cobra.Command, args []string) { }) // Save bookmark to database - bookmarks, err = db.SaveBookmarks(cmd.Context(), bookmarks...) + bookmarks, err = db.SaveBookmarks(cmd.Context(), true, bookmarks...) if err != nil { cError.Printf("Failed to save bookmarks: %v\n", err) os.Exit(1) diff --git a/internal/cmd/pocket.go b/internal/cmd/pocket.go index 033105a6..794fa71e 100644 --- a/internal/cmd/pocket.go +++ b/internal/cmd/pocket.go @@ -112,7 +112,7 @@ func pocketHandler(cmd *cobra.Command, args []string) { }) // Save bookmark to database - bookmarks, err = db.SaveBookmarks(cmd.Context(), bookmarks...) + bookmarks, err = db.SaveBookmarks(cmd.Context(), true, bookmarks...) if err != nil { cError.Printf("Failed to save bookmarks: %v\n", err) os.Exit(1) diff --git a/internal/cmd/update.go b/internal/cmd/update.go index d289fd3a..9690bee4 100644 --- a/internal/cmd/update.go +++ b/internal/cmd/update.go @@ -285,7 +285,7 @@ func updateHandler(cmd *cobra.Command, args []string) { } // Save bookmarks to database - bookmarks, err = db.SaveBookmarks(cmd.Context(), bookmarks...) + bookmarks, err = db.SaveBookmarks(cmd.Context(), false, bookmarks...) if err != nil { cError.Printf("Failed to save bookmark: %v\n", err) os.Exit(1) diff --git a/internal/database/database.go b/internal/database/database.go index 58cf0dba..8221a4fd 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -49,7 +49,7 @@ type DB interface { Migrate() error // SaveBookmarks saves bookmarks data to database. - SaveBookmarks(ctx context.Context, bookmarks ...model.Bookmark) ([]model.Bookmark, error) + 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) diff --git a/internal/database/database_test.go b/internal/database/database_test.go new file mode 100644 index 00000000..d180757e --- /dev/null +++ b/internal/database/database_test.go @@ -0,0 +1,103 @@ +package database + +import ( + "context" + "testing" + + "github.com/go-shiori/shiori/internal/model" + "github.com/stretchr/testify/assert" +) + +type databaseTestCase func(t *testing.T, db DB) +type testDatabaseFactory func(ctx context.Context) (DB, error) + +func testDatabase(t *testing.T, dbFactory testDatabaseFactory) { + tests := map[string]databaseTestCase{ + "testCreateBookmark": testCreateBookmark, + "testCreateBookmarkTwice": testCreateBookmarkTwice, + "testCreateBookmarkWithTag": testCreateBookmarkWithTag, + "testUpdateBookmark": testUpdateBookmark, + } + + for testName, testCase := range tests { + t.Run(testName, func(tInner *testing.T) { + ctx := context.TODO() + db, err := dbFactory(ctx) + assert.NoError(tInner, err, "Error recreating database") + testCase(tInner, db) + }) + } +} + +func testCreateBookmark(t *testing.T, db DB) { + ctx := context.TODO() + + book := model.Bookmark{ + URL: "https://github.com/go-shiori/obelisk", + Title: "shiori", + } + + result, err := db.SaveBookmarks(ctx, true, book) + + assert.NoError(t, err, "Save bookmarks must not fail") + assert.Equal(t, 1, result[0].ID, "Saved bookmark must have an ID set") +} + +func testCreateBookmarkWithTag(t *testing.T, db DB) { + ctx := context.TODO() + + book := model.Bookmark{ + URL: "https://github.com/go-shiori/obelisk", + Title: "shiori", + Tags: []model.Tag{ + { + Name: "test-tag", + }, + }, + } + + result, err := db.SaveBookmarks(ctx, true, book) + + assert.NoError(t, err, "Save bookmarks must not fail") + assert.Equal(t, book.URL, result[0].URL) + assert.Equal(t, book.Tags[0].Name, result[0].Tags[0].Name) +} + +func testCreateBookmarkTwice(t *testing.T, db DB) { + ctx := context.TODO() + + book := model.Bookmark{ + URL: "https://github.com/go-shiori/shiori", + Title: "shiori", + } + + result, err := db.SaveBookmarks(ctx, true, book) + assert.NoError(t, err, "Save bookmarks must not fail") + + savedBookmark := result[0] + savedBookmark.Title = "modified" + + _, err = db.SaveBookmarks(ctx, true, savedBookmark) + assert.Error(t, err, "Save bookmarks must fail") +} + +func testUpdateBookmark(t *testing.T, db DB) { + ctx := context.TODO() + + book := model.Bookmark{ + URL: "https://github.com/go-shiori/shiori", + Title: "shiori", + } + + result, err := db.SaveBookmarks(ctx, true, book) + assert.NoError(t, err, "Save bookmarks must not fail") + + savedBookmark := result[0] + savedBookmark.Title = "modified" + + result, err = db.SaveBookmarks(ctx, false, savedBookmark) + assert.NoError(t, err, "Save bookmarks must not fail") + + assert.Equal(t, "modified", result[0].Title) + assert.Equal(t, savedBookmark.ID, result[0].ID) +} diff --git a/internal/database/mysql.go b/internal/database/mysql.go index 150ef54c..71ef3779 100644 --- a/internal/database/mysql.go +++ b/internal/database/mysql.go @@ -64,23 +64,27 @@ func (db *MySQLDatabase) Migrate() error { // SaveBookmarks saves new or updated bookmarks to database. // Returns the saved ID and error message if any happened. -func (db *MySQLDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.Bookmark) ([]model.Bookmark, error) { +func (db *MySQLDatabase) SaveBookmarks(ctx context.Context, create bool, bookmarks ...model.Bookmark) ([]model.Bookmark, error) { var result []model.Bookmark if err := db.withTx(ctx, func(tx *sqlx.Tx) error { // Prepare statement stmtInsertBook, err := tx.Preparex(`INSERT INTO bookmark - (id, url, title, excerpt, author, public, content, html, modified) - VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) - ON DUPLICATE KEY UPDATE - url = VALUES(url), - title = VALUES(title), - excerpt = VALUES(excerpt), - author = VALUES(author), - public = VALUES(public), - content = VALUES(content), - html = VALUES(html), - modified = VALUES(modified)`) + (url, title, excerpt, author, public, content, html, modified) + VALUES(?, ?, ?, ?, ?, ?, ?, ?)`) + if err != nil { + return errors.WithStack(err) + } + + stmtUpdateBook, err := tx.Preparex(`UPDATE bookmark + SET url = ?, + title = ?, + excerpt = ?, + author = ?, + public = ?, + content = ?, + html = ?, + modified = ?`) if err != nil { return errors.WithStack(err) } @@ -113,11 +117,7 @@ func (db *MySQLDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.B // Execute statements for _, book := range bookmarks { - // Check ID, URL and title - if book.ID == 0 { - return errors.New("ID must not be empty") - } - + // Check URL and title if book.URL == "" { return errors.New("URL must not be empty") } @@ -132,9 +132,25 @@ func (db *MySQLDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.B } // Save bookmark - _, err := stmtInsertBook.ExecContext(ctx, book.ID, - book.URL, book.Title, book.Excerpt, book.Author, - book.Public, book.Content, book.HTML, book.Modified) + var err error + if create { + var res sql.Result + res, err = stmtInsertBook.ExecContext(ctx, + book.URL, book.Title, book.Excerpt, book.Author, + book.Public, book.Content, book.HTML, book.Modified) + if err != nil { + return errors.WithStack(err) + } + bookID, err := res.LastInsertId() + if err != nil { + return errors.WithStack(err) + } + book.ID = int(bookID) + } else { + _, err = stmtUpdateBook.ExecContext(ctx, + book.URL, book.Title, book.Excerpt, book.Author, + book.Public, book.Content, book.HTML, book.Modified) + } if err != nil { return errors.WithStack(err) } @@ -158,8 +174,7 @@ func (db *MySQLDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.B // If tag doesn't have any ID, fetch it from database if tag.ID == 0 { - err = stmtGetTag.Get(&tag.ID, tagName) - if err != nil { + if err := stmtGetTag.GetContext(ctx, &tag.ID, tagName); err != nil && err != sql.ErrNoRows { return errors.WithStack(err) } diff --git a/internal/database/mysql_test.go b/internal/database/mysql_test.go new file mode 100644 index 00000000..b555a131 --- /dev/null +++ b/internal/database/mysql_test.go @@ -0,0 +1,60 @@ +package database + +import ( + "context" + "log" + "os" + "testing" + + "github.com/golang-migrate/migrate/v4" + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" +) + +func init() { + connString := os.Getenv("SHIORI_TEST_MYSQL_URL") + if connString == "" { + log.Fatal("mysql tests can't run without a MysQL database, set SHIORI_TEST_MYSQL_URL environment variable") + } +} + +func mysqlTestDatabaseFactory(ctx context.Context) (DB, error) { + connString := os.Getenv("SHIORI_TEST_MYSQL_URL") + db, err := OpenMySQLDatabase(ctx, connString) + if err != nil { + return nil, err + } + + var dbname string + err = db.withTx(ctx, func(tx *sqlx.Tx) error { + err := tx.QueryRow("SELECT DATABASE()").Scan(&dbname) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "DROP DATABASE IF EXISTS "+dbname) + if err != nil { + return err + } + + _, err = tx.ExecContext(ctx, "CREATE DATABASE "+dbname) + return err + }) + if err != nil { + return nil, err + } + + if _, err := db.Exec("USE " + dbname); err != nil { + return nil, err + } + + if err = db.Migrate(); err != nil && !errors.Is(migrate.ErrNoChange, err) { + return nil, err + } + + return db, err +} + +func TestMysqlsDatabase(t *testing.T) { + testDatabase(t, mysqlTestDatabaseFactory) +} diff --git a/internal/database/pg.go b/internal/database/pg.go index 9711b8ff..b67ff4d5 100644 --- a/internal/database/pg.go +++ b/internal/database/pg.go @@ -64,14 +64,19 @@ func (db *PGDatabase) Migrate() error { // SaveBookmarks saves new or updated bookmarks to database. // Returns the saved ID and error message if any happened. -func (db *PGDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.Bookmark) (result []model.Bookmark, err error) { +func (db *PGDatabase) SaveBookmarks(ctx context.Context, create bool, bookmarks ...model.Bookmark) (result []model.Bookmark, err error) { result = []model.Bookmark{} if err := db.withTx(ctx, func(tx *sqlx.Tx) error { // Prepare statement stmtInsertBook, err := tx.Preparex(`INSERT INTO bookmark (url, title, excerpt, author, public, content, html, modified) VALUES($1, $2, $3, $4, $5, $6, $7, $8) - ON CONFLICT(url) DO UPDATE SET + RETURNING id`) + if err != nil { + return errors.WithStack(err) + } + + stmtUpdateBook, err := tx.Preparex(`UPDATE bookmark SET url = $1, title = $2, excerpt = $3, @@ -79,8 +84,7 @@ func (db *PGDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.Book public = $5, content = $6, html = $7, - modified = $8 - RETURNING id`) + modified = $8`) if err != nil { return errors.WithStack(err) } @@ -128,9 +132,16 @@ func (db *PGDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.Book } // Save bookmark - err := stmtInsertBook.QueryRowContext(ctx, - book.URL, book.Title, book.Excerpt, book.Author, - book.Public, book.Content, book.HTML, book.Modified).Scan(&book.ID) + var err error + if create { + err = stmtInsertBook.QueryRowContext(ctx, + book.URL, book.Title, book.Excerpt, book.Author, + book.Public, book.Content, book.HTML, book.Modified).Scan(&book.ID) + } else { + _, err = stmtUpdateBook.ExecContext(ctx, + book.URL, book.Title, book.Excerpt, book.Author, + book.Public, book.Content, book.HTML, book.Modified) + } if err != nil { return errors.WithStack(err) } diff --git a/internal/database/pg_test.go b/internal/database/pg_test.go index 5fd2b537..9f1141b5 100644 --- a/internal/database/pg_test.go +++ b/internal/database/pg_test.go @@ -7,42 +7,34 @@ import ( "os" "testing" - "github.com/go-shiori/shiori/internal/model" "github.com/golang-migrate/migrate/v4" - "github.com/stretchr/testify/assert" ) func init() { - testPsqlURL := os.Getenv("SHIORI_TEST_PG_URL") - if testPsqlURL == "" { - log.Fatal("psql tests can't run without a PSQL database") + connString := os.Getenv("SHIORI_TEST_PG_URL") + if connString == "" { + log.Fatal("psql tests can't run without a PSQL database, set SHIORI_TEST_PG_URL environment variable") } } -func TestPsqlSaveBookmarkWithTag(t *testing.T) { - ctx := context.TODO() - pgDB, err := OpenPGDatabase(ctx, os.Getenv("SHIORI_TEST_PG_URL")) +func postgresqlTestDatabaseFactory(ctx context.Context) (DB, error) { + db, err := OpenPGDatabase(ctx, os.Getenv("SHIORI_TEST_PG_URL")) if err != nil { - t.Error(err) + return nil, err } - if err := pgDB.Migrate(); err != nil && !errors.Is(migrate.ErrNoChange, err) { - t.Error(err) + _, err = db.Exec("DROP SCHEMA public CASCADE; CREATE SCHEMA public;") + if err != nil { + return nil, err } - book := model.Bookmark{ - URL: "https://github.com/go-shiori/obelisk", - Title: "shiori", - Tags: []model.Tag{ - { - Name: "test-tag", - }, - }, + if err := db.Migrate(); err != nil && !errors.Is(migrate.ErrNoChange, err) { + return nil, err } - result, err := pgDB.SaveBookmarks(ctx, book) - - assert.NoError(t, err, "Save bookmarks must not fail") - assert.Equal(t, book.URL, result[0].URL) - assert.Equal(t, book.Tags[0].Name, result[0].Tags[0].Name) + return db, nil +} + +func TestPostgresDatabase(t *testing.T) { + testDatabase(t, postgresqlTestDatabaseFactory) } diff --git a/internal/database/sqlite.go b/internal/database/sqlite.go index 521b59c2..ed09c9e3 100644 --- a/internal/database/sqlite.go +++ b/internal/database/sqlite.go @@ -73,15 +73,20 @@ func (db *SQLiteDatabase) Migrate() error { // SaveBookmarks saves new or updated bookmarks to database. // Returns the saved ID and error message if any happened. -func (db *SQLiteDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model.Bookmark) ([]model.Bookmark, error) { +func (db *SQLiteDatabase) SaveBookmarks(ctx context.Context, create bool, bookmarks ...model.Bookmark) ([]model.Bookmark, error) { var result []model.Bookmark if err := db.withTx(ctx, func(tx *sqlx.Tx) error { // Prepare statement + stmtInsertBook, err := tx.PreparexContext(ctx, `INSERT INTO bookmark - (id, url, title, excerpt, author, public, modified, has_content) - VALUES(?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(id) DO UPDATE SET + (url, title, excerpt, author, public, modified, has_content) + VALUES(?, ?, ?, ?, ?, ?, ?) RETURNING id`) + if err != nil { + return errors.WithStack(err) + } + + stmtUpdateBook, err := tx.PreparexContext(ctx, `UPDATE bookmark SET url = ?, title = ?, excerpt = ?, author = ?, public = ?, modified = ?, has_content = ?`) if err != nil { @@ -130,11 +135,7 @@ func (db *SQLiteDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model. // Execute statements for _, book := range bookmarks { - // Check ID, URL and title - if book.ID == 0 { - return errors.New("ID must not be empty") - } - + // Check URL and title if book.URL == "" { return errors.New("URL must not be empty") } @@ -148,11 +149,18 @@ func (db *SQLiteDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model. book.Modified = modifiedTime } - // Save bookmark hasContent := book.Content != "" - _, err = stmtInsertBook.ExecContext(ctx, book.ID, - book.URL, book.Title, book.Excerpt, book.Author, book.Public, book.Modified, hasContent, - book.URL, book.Title, book.Excerpt, book.Author, book.Public, book.Modified, hasContent) + + // Create or update bookmark + var err error + if create { + err = stmtInsertBook.QueryRowContext(ctx, + book.URL, book.Title, book.Excerpt, book.Author, book.Public, book.Modified, hasContent).Scan(&book.ID) + } else { + _, err = stmtUpdateBook.ExecContext(ctx, + book.URL, book.Title, book.Excerpt, book.Author, book.Public, book.Modified, hasContent) + + } if err != nil { return errors.WithStack(err) } @@ -169,6 +177,13 @@ func (db *SQLiteDatabase) SaveBookmarks(ctx context.Context, bookmarks ...model. return errors.WithStack(err) } + bookID, err := res.LastInsertId() + if err != nil { + return errors.WithStack(err) + } + + book.ID = int(bookID) + if rows == 0 { _, err = stmtInsertBookContent.ExecContext(ctx, book.ID, book.Title, book.Content, book.HTML) if err != nil { diff --git a/internal/database/sqlite_test.go b/internal/database/sqlite_test.go new file mode 100644 index 00000000..e0f3e60a --- /dev/null +++ b/internal/database/sqlite_test.go @@ -0,0 +1,36 @@ +package database + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/golang-migrate/migrate/v4" + "github.com/pkg/errors" +) + +var sqliteDatabaseTestPath string + +func init() { + sqliteDatabaseTestPath = filepath.Join(os.TempDir(), "shiori.db") +} + +func sqliteTestDatabaseFactory(ctx context.Context) (DB, error) { + os.Remove(sqliteDatabaseTestPath) + + db, err := OpenSQLiteDatabase(ctx, sqliteDatabaseTestPath) + if err != nil { + return nil, err + } + + if err := db.Migrate(); err != nil && !errors.Is(migrate.ErrNoChange, err) { + return nil, err + } + + return db, nil +} + +func TestSqliteDatabase(t *testing.T) { + testDatabase(t, sqliteTestDatabaseFactory) +} diff --git a/internal/webserver/handler-api-ext.go b/internal/webserver/handler-api-ext.go index c6293588..919f8a5f 100644 --- a/internal/webserver/handler-api-ext.go +++ b/internal/webserver/handler-api-ext.go @@ -98,12 +98,12 @@ func (h *handler) apiInsertViaExtension(w http.ResponseWriter, r *http.Request, panic(fmt.Errorf("failed to process bookmark: %v", err)) } } - if _, err := h.DB.SaveBookmarks(ctx, book); err != nil { + 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, book) + results, err := h.DB.SaveBookmarks(ctx, false, book) if err != nil || len(results) == 0 { panic(fmt.Errorf("failed to save bookmark: %v", err)) } diff --git a/internal/webserver/handler-api.go b/internal/webserver/handler-api.go index 220bd3fc..b66bb538 100644 --- a/internal/webserver/handler-api.go +++ b/internal/webserver/handler-api.go @@ -316,18 +316,20 @@ func (h *handler) apiInsertBookmark(w http.ResponseWriter, r *http.Request, ps h } // Save bookmark to database - results, err := h.DB.SaveBookmarks(ctx, *book) + results, err := h.DB.SaveBookmarks(ctx, true, *book) if err != nil || len(results) == 0 { panic(fmt.Errorf("failed to save bookmark: %v", err)) } + book = &results[0] + if payload.Async { go func() { - bookmark, err := downloadBookmarkContent(&results[0], h.DataDir, r) + bookmark, err := downloadBookmarkContent(book, h.DataDir, r) if err != nil { log.Printf("error downloading boorkmark: %s", err) } - if _, err := h.DB.SaveBookmarks(context.Background(), *bookmark); err != nil { + if _, err := h.DB.SaveBookmarks(context.Background(), false, *bookmark); err != nil { log.Printf("failed to save bookmark: %s", err) } }() @@ -338,7 +340,7 @@ func (h *handler) apiInsertBookmark(w http.ResponseWriter, r *http.Request, ps h if err != nil { log.Printf("error downloading boorkmark: %s", err) } - if _, err := h.DB.SaveBookmarks(ctx, *book); err != nil { + if _, err := h.DB.SaveBookmarks(ctx, false, *book); err != nil { log.Printf("failed to save bookmark: %s", err) } } @@ -442,7 +444,7 @@ func (h *handler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, ps h } // Update database - res, err := h.DB.SaveBookmarks(ctx, book) + res, err := h.DB.SaveBookmarks(ctx, false, book) checkError(err) // Add thumbnail image to the saved bookmarks again @@ -566,7 +568,7 @@ func (h *handler) apiUpdateCache(w http.ResponseWriter, r *http.Request, ps http close(chDone) // Update database - _, err = h.DB.SaveBookmarks(ctx, bookmarks...) + _, err = h.DB.SaveBookmarks(ctx, false, bookmarks...) checkError(err) // Return new saved result @@ -628,7 +630,7 @@ func (h *handler) apiUpdateBookmarkTags(w http.ResponseWriter, r *http.Request, } // Update database - bookmarks, err = h.DB.SaveBookmarks(ctx, bookmarks...) + bookmarks, err = h.DB.SaveBookmarks(ctx, false, bookmarks...) checkError(err) // Get image URL for each bookmark