mirror of
https://github.com/go-shiori/shiori.git
synced 2025-03-10 06:50:28 +08:00
feat: sqlite migrations (#398)
- Moved migrations from code to SQL files - Using golang-migrate/v4 - Added a new CLI command: migrate
This commit is contained in:
parent
ca3cc11b2d
commit
81d52a2e24
10 changed files with 895 additions and 121 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -6,9 +6,13 @@
|
|||
/shiori*
|
||||
|
||||
# Exclude development data
|
||||
/dev-data
|
||||
/dev-data*
|
||||
|
||||
# Coverage data
|
||||
/coverage.out
|
||||
|
||||
# Dist files
|
||||
dist/
|
||||
|
||||
# macOS trash files
|
||||
.DS_Store
|
||||
|
|
10
go.mod
10
go.mod
|
@ -11,6 +11,7 @@ require (
|
|||
github.com/go-shiori/warc v0.0.0-20200621032813-359908319d1d
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/gofrs/uuid v4.2.0+incompatible
|
||||
github.com/golang-migrate/migrate/v4 v4.15.1
|
||||
github.com/jmoiron/sqlx v1.3.4
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/lib/pq v1.10.4
|
||||
|
@ -20,19 +21,19 @@ require (
|
|||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/spf13/cobra v1.3.0
|
||||
golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
modernc.org/sqlite v1.14.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible // indirect
|
||||
github.com/cpuguy83/go-md2man v1.0.10 // indirect
|
||||
github.com/go-shiori/dom v0.0.0-20210627111528-4e4722cd0d65 // indirect
|
||||
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
|
@ -40,13 +41,12 @@ require (
|
|||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.9 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
|
|
21
internal/cmd/migrate.go
Normal file
21
internal/cmd/migrate.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func migrateCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Migrates the database to the latest version",
|
||||
Run: migrateHandler,
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func migrateHandler(cmd *cobra.Command, args []string) {
|
||||
if err := db.Migrate(); err != nil {
|
||||
cError.Printf("Error during migration: %s", err)
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ func ShioriCmd() *cobra.Command {
|
|||
pocketCmd(),
|
||||
serveCmd(),
|
||||
checkCmd(),
|
||||
migrateCmd(),
|
||||
)
|
||||
|
||||
return rootCmd
|
||||
|
|
|
@ -38,6 +38,9 @@ type GetAccountsOptions struct {
|
|||
|
||||
// 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(bookmarks ...model.Bookmark) ([]model.Bookmark, error)
|
||||
|
||||
|
|
38
internal/database/migrations/sqlite/0001_initial.up.sql
Normal file
38
internal/database/migrations/sqlite/0001_initial.up.sql
Normal file
|
@ -0,0 +1,38 @@
|
|||
CREATE TABLE IF NOT EXISTS account(
|
||||
id INTEGER NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
owner INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT account_PK PRIMARY KEY(id),
|
||||
CONSTRAINT account_username_UNIQUE UNIQUE(username)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bookmark(
|
||||
id INTEGER NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
excerpt TEXT NOT NULL DEFAULT "",
|
||||
author TEXT NOT NULL DEFAULT "",
|
||||
public INTEGER NOT NULL DEFAULT 0,
|
||||
modified TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT bookmark_PK PRIMARY KEY(id),
|
||||
CONSTRAINT bookmark_url_UNIQUE UNIQUE(url)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tag(
|
||||
id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
CONSTRAINT tag_PK PRIMARY KEY(id),
|
||||
CONSTRAINT tag_name_UNIQUE UNIQUE(name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bookmark_tag(
|
||||
bookmark_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
CONSTRAINT bookmark_tag_PK PRIMARY KEY(bookmark_id, tag_id),
|
||||
CONSTRAINT bookmark_id_FK FOREIGN KEY(bookmark_id) REFERENCES bookmark(id),
|
||||
CONSTRAINT tag_id_FK FOREIGN KEY(tag_id) REFERENCES tag(id)
|
||||
);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS bookmark_content
|
||||
USING fts5(title, content, html, docid);
|
|
@ -92,6 +92,11 @@ func OpenMySQLDatabase(connString string) (mysqlDB *MySQLDatabase, err error) {
|
|||
return mysqlDB, err
|
||||
}
|
||||
|
||||
// Migrate runs migrations for this database engine
|
||||
func (db *MySQLDatabase) Migrate() error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// SaveBookmarks saves new or updated bookmarks to database.
|
||||
// Returns the saved ID and error message if any happened.
|
||||
func (db *MySQLDatabase) SaveBookmarks(bookmarks ...model.Bookmark) (result []model.Bookmark, err error) {
|
||||
|
|
|
@ -87,6 +87,11 @@ func OpenPGDatabase(connString string) (pgDB *PGDatabase, err error) {
|
|||
return pgDB, err
|
||||
}
|
||||
|
||||
// Migrate runs migrations for this database engine
|
||||
func (db *PGDatabase) Migrate() error {
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// SaveBookmarks saves new or updated bookmarks to database.
|
||||
// Returns the saved ID and error message if any happened.
|
||||
func (db *PGDatabase) SaveBookmarks(bookmarks ...model.Bookmark) (result []model.Bookmark, err error) {
|
||||
|
|
|
@ -2,16 +2,23 @@ package database
|
|||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-shiori/shiori/internal/model"
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
//go:embed migrations/sqlite/*
|
||||
var migrations embed.FS
|
||||
|
||||
// SQLiteDatabase is implementation of Database interface
|
||||
// for connecting to SQLite3 database.
|
||||
type SQLiteDatabase struct {
|
||||
|
@ -20,69 +27,32 @@ type SQLiteDatabase struct {
|
|||
|
||||
// OpenSQLiteDatabase creates and open connection to new SQLite3 database.
|
||||
func OpenSQLiteDatabase(databasePath string) (sqliteDB *SQLiteDatabase, err error) {
|
||||
// Open database and start transaction
|
||||
// Open database
|
||||
db := sqlx.MustConnect("sqlite", databasePath)
|
||||
|
||||
tx, err := db.Beginx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Make sure to rollback if panic ever happened
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panicErr, _ := r.(error)
|
||||
if err := tx.Rollback(); err != nil {
|
||||
log.Printf("error during rollback: %s", err)
|
||||
}
|
||||
sqliteDB = nil
|
||||
err = panicErr
|
||||
}
|
||||
}()
|
||||
|
||||
// Create tables
|
||||
tx.MustExec(`CREATE TABLE IF NOT EXISTS account(
|
||||
id INTEGER NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
owner INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT account_PK PRIMARY KEY(id),
|
||||
CONSTRAINT account_username_UNIQUE UNIQUE(username))`)
|
||||
|
||||
tx.MustExec(`CREATE TABLE IF NOT EXISTS bookmark(
|
||||
id INTEGER NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
excerpt TEXT NOT NULL DEFAULT "",
|
||||
author TEXT NOT NULL DEFAULT "",
|
||||
public INTEGER NOT NULL DEFAULT 0,
|
||||
modified TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT bookmark_PK PRIMARY KEY(id),
|
||||
CONSTRAINT bookmark_url_UNIQUE UNIQUE(url))`)
|
||||
|
||||
tx.MustExec(`CREATE TABLE IF NOT EXISTS tag(
|
||||
id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
CONSTRAINT tag_PK PRIMARY KEY(id),
|
||||
CONSTRAINT tag_name_UNIQUE UNIQUE(name))`)
|
||||
|
||||
tx.MustExec(`CREATE TABLE IF NOT EXISTS bookmark_tag(
|
||||
bookmark_id INTEGER NOT NULL,
|
||||
tag_id INTEGER NOT NULL,
|
||||
CONSTRAINT bookmark_tag_PK PRIMARY KEY(bookmark_id, tag_id),
|
||||
CONSTRAINT bookmark_id_FK FOREIGN KEY(bookmark_id) REFERENCES bookmark(id),
|
||||
CONSTRAINT tag_id_FK FOREIGN KEY(tag_id) REFERENCES tag(id))`)
|
||||
|
||||
tx.MustExec(`CREATE VIRTUAL TABLE IF NOT EXISTS bookmark_content
|
||||
USING fts5(title, content, html, docid)`)
|
||||
|
||||
err = tx.Commit()
|
||||
checkError(err)
|
||||
|
||||
sqliteDB = &SQLiteDatabase{*db}
|
||||
return sqliteDB, err
|
||||
}
|
||||
|
||||
// Migrate runs migrations for this database engine
|
||||
func (db *SQLiteDatabase) Migrate() error {
|
||||
sourceDriver, err := iofs.New(migrations, "migrations/sqlite")
|
||||
checkError(err)
|
||||
|
||||
dbDriver, err := sqlite.WithInstance(db.DB.DB, &sqlite.Config{})
|
||||
checkError(err)
|
||||
|
||||
migration, err := migrate.NewWithInstance(
|
||||
"iofs",
|
||||
sourceDriver,
|
||||
"sqlite",
|
||||
dbDriver,
|
||||
)
|
||||
|
||||
checkError(err)
|
||||
|
||||
return migration.Up()
|
||||
}
|
||||
|
||||
// SaveBookmarks saves new or updated bookmarks to database.
|
||||
// Returns the saved ID and error message if any happened.
|
||||
func (db *SQLiteDatabase) SaveBookmarks(bookmarks ...model.Bookmark) (result []model.Bookmark, err error) {
|
||||
|
|
Loading…
Reference in a new issue