mirror of https://github.com/usememos/memos
refactor: store migrator
parent
1ae3afc0ba
commit
6e901fc940
@ -1,167 +0,0 @@
|
|||||||
package mysql
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/usememos/memos/server/version"
|
|
||||||
"github.com/usememos/memos/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed migration
|
|
||||||
var migrationFS embed.FS
|
|
||||||
|
|
||||||
const (
|
|
||||||
latestSchemaFileName = "LATEST__SCHEMA.sql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DB) Migrate(ctx context.Context) error {
|
|
||||||
if d.profile.IsDev() {
|
|
||||||
return d.nonProdMigrate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.prodMigrate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) nonProdMigrate(ctx context.Context) error {
|
|
||||||
rows, err := d.db.QueryContext(ctx, "SHOW TABLES")
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to query database tables: %s", err)
|
|
||||||
}
|
|
||||||
if rows.Err() != nil {
|
|
||||||
return errors.Errorf("failed to query database tables: %s", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var tables []string
|
|
||||||
for rows.Next() {
|
|
||||||
var table string
|
|
||||||
err := rows.Scan(&table)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to scan table name: %s", err)
|
|
||||||
}
|
|
||||||
tables = append(tables, table)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tables) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := migrationFS.ReadFile("migration/dev/" + latestSchemaFileName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt := string(buf)
|
|
||||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
|
||||||
return errors.Errorf("failed to exec SQL %s: %s", stmt, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) prodMigrate(ctx context.Context) error {
|
|
||||||
currentVersion := version.GetCurrentVersion(d.profile.Mode)
|
|
||||||
migrationHistoryList, err := d.FindMigrationHistoryList(ctx, &store.FindMigrationHistory{})
|
|
||||||
// If there is no migration history, we should apply the latest schema.
|
|
||||||
if err != nil || len(migrationHistoryList) == 0 {
|
|
||||||
buf, err := migrationFS.ReadFile("migration/prod/" + latestSchemaFileName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
|
||||||
}
|
|
||||||
if _, err := d.db.ExecContext(ctx, string(buf)); err != nil {
|
|
||||||
return errors.Errorf("failed to exec latest schema: %s", err)
|
|
||||||
}
|
|
||||||
if _, err := d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{
|
|
||||||
Version: currentVersion,
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to upsert migration history")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationHistoryVersionList := []string{}
|
|
||||||
for _, migrationHistory := range migrationHistoryList {
|
|
||||||
migrationHistoryVersionList = append(migrationHistoryVersionList, migrationHistory.Version)
|
|
||||||
}
|
|
||||||
sort.Sort(version.SortVersion(migrationHistoryVersionList))
|
|
||||||
latestMigrationHistoryVersion := migrationHistoryVersionList[len(migrationHistoryVersionList)-1]
|
|
||||||
if !version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), latestMigrationHistoryVersion) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("start to migrate database schema")
|
|
||||||
for _, minorVersion := range getMinorVersionList() {
|
|
||||||
normalizedVersion := minorVersion + ".0"
|
|
||||||
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
|
||||||
fmt.Println("applying migration of", normalizedVersion)
|
|
||||||
if err := d.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to apply minor version migration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("end migrate")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
|
||||||
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("migration/prod/%s/*.sql", minorVersion))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to read ddl files")
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(filenames)
|
|
||||||
// Loop over all migration files and execute them in order.
|
|
||||||
for _, filename := range filenames {
|
|
||||||
buf, err := migrationFS.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to read minor version migration file, filename=%s", filename)
|
|
||||||
}
|
|
||||||
for _, stmt := range strings.Split(string(buf), ";") {
|
|
||||||
if strings.TrimSpace(stmt) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
|
||||||
return errors.Wrapf(err, "migrate error: %s", stmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upsert the newest version to migration_history.
|
|
||||||
version := minorVersion + ".0"
|
|
||||||
if _, err = d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{Version: version}); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to upsert migration history with version: %s", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// minorDirRegexp is a regular expression for minor version directory.
|
|
||||||
var minorDirRegexp = regexp.MustCompile(`^migration/prod/[0-9]+\.[0-9]+$`)
|
|
||||||
|
|
||||||
func getMinorVersionList() []string {
|
|
||||||
minorVersionList := []string{}
|
|
||||||
|
|
||||||
if err := fs.WalkDir(migrationFS, "migration", func(path string, file fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if file.IsDir() && minorDirRegexp.MatchString(path) {
|
|
||||||
minorVersionList = append(minorVersionList, file.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(version.SortVersion(minorVersionList))
|
|
||||||
|
|
||||||
return minorVersionList
|
|
||||||
}
|
|
||||||
@ -1,170 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
|
||||||
"github.com/usememos/memos/server/version"
|
|
||||||
"github.com/usememos/memos/store"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed migration
|
|
||||||
var migrationFS embed.FS
|
|
||||||
|
|
||||||
const (
|
|
||||||
latestSchemaFileName = "LATEST__SCHEMA.sql"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d *DB) Migrate(ctx context.Context) error {
|
|
||||||
if d.profile.IsDev() {
|
|
||||||
return d.nonProdMigrate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.prodMigrate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) nonProdMigrate(ctx context.Context) error {
|
|
||||||
rows, err := d.db.QueryContext(ctx, "SELECT tablename FROM pg_catalog.pg_tables WHERE schemaname != 'pg_catalog' AND schemaname != 'information_schema';")
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to query database tables: %s", err)
|
|
||||||
}
|
|
||||||
if rows.Err() != nil {
|
|
||||||
return errors.Errorf("failed to query database tables: %s", err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
var tables []string
|
|
||||||
for rows.Next() {
|
|
||||||
var table string
|
|
||||||
err := rows.Scan(&table)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to scan table name: %s", err)
|
|
||||||
}
|
|
||||||
tables = append(tables, table)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(tables) != 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := migrationFS.ReadFile("migration/dev/" + latestSchemaFileName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt := string(buf)
|
|
||||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
|
||||||
return errors.Errorf("failed to exec SQL %s: %s", stmt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) prodMigrate(ctx context.Context) error {
|
|
||||||
currentVersion := version.GetCurrentVersion(d.profile.Mode)
|
|
||||||
migrationHistoryList, err := d.FindMigrationHistoryList(ctx, &store.FindMigrationHistory{})
|
|
||||||
// If there is no migration history, we should apply the latest schema.
|
|
||||||
if err != nil || len(migrationHistoryList) == 0 {
|
|
||||||
buf, err := migrationFS.ReadFile("migration/prod/" + latestSchemaFileName)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Errorf("failed to read latest schema file: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt := string(buf)
|
|
||||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
|
||||||
return errors.Errorf("failed to exec SQL %s: %s", stmt, err)
|
|
||||||
}
|
|
||||||
if _, err := d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{
|
|
||||||
Version: currentVersion,
|
|
||||||
}); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to upsert migration history")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
migrationHistoryVersionList := []string{}
|
|
||||||
for _, migrationHistory := range migrationHistoryList {
|
|
||||||
migrationHistoryVersionList = append(migrationHistoryVersionList, migrationHistory.Version)
|
|
||||||
}
|
|
||||||
sort.Sort(version.SortVersion(migrationHistoryVersionList))
|
|
||||||
latestMigrationHistoryVersion := migrationHistoryVersionList[len(migrationHistoryVersionList)-1]
|
|
||||||
if !version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), latestMigrationHistoryVersion) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("start migrate")
|
|
||||||
for _, minorVersion := range getMinorVersionList() {
|
|
||||||
normalizedVersion := minorVersion + ".0"
|
|
||||||
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
|
||||||
fmt.Println("applying migration for", normalizedVersion)
|
|
||||||
if err := d.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
|
||||||
return errors.Wrap(err, "failed to apply minor version migration")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println("end migrate")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
|
||||||
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("migration/prod/%s/*.sql", minorVersion))
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to read ddl files")
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(filenames)
|
|
||||||
// Loop over all migration files and execute them in order.
|
|
||||||
for _, filename := range filenames {
|
|
||||||
buf, err := migrationFS.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to read minor version migration file, filename=%s", filename)
|
|
||||||
}
|
|
||||||
for _, stmt := range strings.Split(string(buf), ";") {
|
|
||||||
if strings.TrimSpace(stmt) == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, err := d.db.ExecContext(ctx, stmt); err != nil {
|
|
||||||
return errors.Wrapf(err, "migrate error: %s", stmt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upsert the newest version to migration_history.
|
|
||||||
version := minorVersion + ".0"
|
|
||||||
if _, err = d.UpsertMigrationHistory(ctx, &store.UpsertMigrationHistory{Version: version}); err != nil {
|
|
||||||
return errors.Wrapf(err, "failed to upsert migration history with version: %s", version)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// minorDirRegexp is a regular expression for minor version directory.
|
|
||||||
var minorDirRegexp = regexp.MustCompile(`^migration/prod/[0-9]+\.[0-9]+$`)
|
|
||||||
|
|
||||||
func getMinorVersionList() []string {
|
|
||||||
minorVersionList := []string{}
|
|
||||||
|
|
||||||
if err := fs.WalkDir(migrationFS, "migration", func(path string, file fs.DirEntry, err error) error {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if file.IsDir() && minorDirRegexp.MatchString(path) {
|
|
||||||
minorVersionList = append(minorVersionList, file.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(version.SortVersion(minorVersionList))
|
|
||||||
|
|
||||||
return minorVersionList
|
|
||||||
}
|
|
||||||
@ -0,0 +1,146 @@
|
|||||||
|
-- migration_history
|
||||||
|
CREATE TABLE migration_history (
|
||||||
|
version TEXT NOT NULL PRIMARY KEY,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- system_setting
|
||||||
|
CREATE TABLE system_setting (
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
UNIQUE(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- user
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||||
|
email TEXT NOT NULL DEFAULT '',
|
||||||
|
nickname TEXT NOT NULL DEFAULT '',
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
avatar_url TEXT NOT NULL DEFAULT '',
|
||||||
|
description TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_username ON user (username);
|
||||||
|
|
||||||
|
-- user_setting
|
||||||
|
CREATE TABLE user_setting (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
UNIQUE(user_id, key)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- memo
|
||||||
|
CREATE TABLE memo (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
uid TEXT NOT NULL UNIQUE,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||||
|
tags TEXT NOT NULL DEFAULT '[]',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_memo_creator_id ON memo (creator_id);
|
||||||
|
CREATE INDEX idx_memo_content ON memo (content);
|
||||||
|
CREATE INDEX idx_memo_visibility ON memo (visibility);
|
||||||
|
CREATE INDEX idx_memo_tags ON memo (tags);
|
||||||
|
|
||||||
|
-- memo_organizer
|
||||||
|
CREATE TABLE memo_organizer (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||||
|
UNIQUE(memo_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- memo_relation
|
||||||
|
CREATE TABLE memo_relation (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
related_memo_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
UNIQUE(memo_id, related_memo_id, type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- resource
|
||||||
|
CREATE TABLE resource (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
uid TEXT NOT NULL UNIQUE,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
filename TEXT NOT NULL DEFAULT '',
|
||||||
|
blob BLOB DEFAULT NULL,
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
memo_id INTEGER,
|
||||||
|
storage_type TEXT NOT NULL DEFAULT '',
|
||||||
|
reference TEXT NOT NULL DEFAULT '',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_resource_creator_id ON resource (creator_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_resource_memo_id ON resource (memo_id);
|
||||||
|
|
||||||
|
-- activity
|
||||||
|
CREATE TABLE activity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- idp
|
||||||
|
CREATE TABLE idp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- inbox
|
||||||
|
CREATE TABLE inbox (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
sender_id INTEGER NOT NULL,
|
||||||
|
receiver_id INTEGER NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- webhook
|
||||||
|
CREATE TABLE webhook (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_webhook_creator_id ON webhook (creator_id);
|
||||||
|
|
||||||
|
-- reaction
|
||||||
|
CREATE TABLE reaction (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
content_id TEXT NOT NULL,
|
||||||
|
reaction_type TEXT NOT NULL,
|
||||||
|
UNIQUE(creator_id, content_id, reaction_type)
|
||||||
|
);
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
-- activity
|
||||||
|
CREATE TABLE activity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
user
|
||||||
|
ADD
|
||||||
|
COLUMN avatar_url TEXT NOT NULL DEFAULT '';
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
-- idp
|
||||||
|
CREATE TABLE idp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
-- storage
|
||||||
|
CREATE TABLE storage (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
config TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
UPDATE
|
||||||
|
user_setting
|
||||||
|
SET
|
||||||
|
key = 'memo-visibility'
|
||||||
|
WHERE
|
||||||
|
key = 'memoVisibility';
|
||||||
@ -0,0 +1,69 @@
|
|||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'server-id'
|
||||||
|
WHERE
|
||||||
|
name = 'serverId';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'secret-session'
|
||||||
|
WHERE
|
||||||
|
name = 'secretSessionName';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'allow-signup'
|
||||||
|
WHERE
|
||||||
|
name = 'allowSignUp';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'disable-public-memos'
|
||||||
|
WHERE
|
||||||
|
name = 'disablePublicMemos';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'additional-style'
|
||||||
|
WHERE
|
||||||
|
name = 'additionalStyle';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'additional-script'
|
||||||
|
WHERE
|
||||||
|
name = 'additionalScript';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'customized-profile'
|
||||||
|
WHERE
|
||||||
|
name = 'customizedProfile';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'storage-service-id'
|
||||||
|
WHERE
|
||||||
|
name = 'storageServiceId';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'local-storage-path'
|
||||||
|
WHERE
|
||||||
|
name = 'localStoragePath';
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
system_setting
|
||||||
|
SET
|
||||||
|
name = 'openai-config'
|
||||||
|
WHERE
|
||||||
|
name = 'openAIConfig';
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
resource
|
||||||
|
ADD
|
||||||
|
COLUMN internal_path TEXT NOT NULL DEFAULT '';
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
resource
|
||||||
|
ADD
|
||||||
|
COLUMN public_id TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX resource_id_public_id_unique_index ON resource (id, public_id);
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
resource
|
||||||
|
SET
|
||||||
|
public_id = printf (
|
||||||
|
'%s-%s-%s-%s-%s',
|
||||||
|
lower(hex(randomblob(4))),
|
||||||
|
lower(hex(randomblob(2))),
|
||||||
|
lower(hex(randomblob(2))),
|
||||||
|
lower(hex(randomblob(2))),
|
||||||
|
lower(hex(randomblob(6)))
|
||||||
|
);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
-- memo_relation
|
||||||
|
CREATE TABLE memo_relation (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
related_memo_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
UNIQUE(memo_id, related_memo_id, type)
|
||||||
|
);
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
DROP TABLE IF EXISTS memo_organizer_temp;
|
||||||
|
|
||||||
|
CREATE TABLE memo_organizer_temp (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||||
|
UNIQUE(memo_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo_organizer_temp (memo_id, user_id, pinned)
|
||||||
|
SELECT
|
||||||
|
memo_id,
|
||||||
|
user_id,
|
||||||
|
pinned
|
||||||
|
FROM
|
||||||
|
memo_organizer;
|
||||||
|
|
||||||
|
DROP TABLE memo_organizer;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo_organizer_temp RENAME TO memo_organizer;
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
DROP TABLE IF EXISTS resource_temp;
|
||||||
|
|
||||||
|
CREATE TABLE resource_temp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
filename TEXT NOT NULL DEFAULT '',
|
||||||
|
blob BLOB DEFAULT NULL,
|
||||||
|
external_link TEXT NOT NULL DEFAULT '',
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
internal_path TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
resource_temp (id, creator_id, created_ts, updated_ts, filename, blob, external_link, type, size, internal_path)
|
||||||
|
SELECT
|
||||||
|
id, creator_id, created_ts, updated_ts, filename, blob, external_link, type, size, internal_path
|
||||||
|
FROM
|
||||||
|
resource;
|
||||||
|
|
||||||
|
DROP TABLE resource;
|
||||||
|
|
||||||
|
ALTER TABLE resource_temp RENAME TO resource;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
CREATE INDEX IF NOT EXISTS idx_user_username ON user (username);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_memo_creator_id ON memo (creator_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_memo_content ON memo (content);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_memo_visibility ON memo (visibility);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_resource_creator_id ON resource (creator_id);
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
DROP TABLE IF EXISTS user_temp;
|
||||||
|
|
||||||
|
CREATE TABLE user_temp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||||
|
email TEXT NOT NULL DEFAULT '',
|
||||||
|
nickname TEXT NOT NULL DEFAULT '',
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
avatar_url TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user_temp (id, created_ts, updated_ts, row_status, username, role, email, nickname, password_hash, avatar_url)
|
||||||
|
SELECT
|
||||||
|
id, created_ts, updated_ts, row_status, username, role, email, nickname, password_hash, avatar_url
|
||||||
|
FROM
|
||||||
|
user;
|
||||||
|
|
||||||
|
DROP TABLE user;
|
||||||
|
|
||||||
|
ALTER TABLE user_temp RENAME TO user;
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
ALTER TABLE resource ADD COLUMN memo_id INTEGER;
|
||||||
|
|
||||||
|
UPDATE resource
|
||||||
|
SET memo_id = (
|
||||||
|
SELECT memo_id
|
||||||
|
FROM memo_resource
|
||||||
|
WHERE resource.id = memo_resource.resource_id
|
||||||
|
LIMIT 1
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_resource_memo_id ON resource (memo_id);
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS memo_resource;
|
||||||
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE IF EXISTS shortcut;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
-- inbox
|
||||||
|
CREATE TABLE inbox (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
sender_id INTEGER NOT NULL,
|
||||||
|
receiver_id INTEGER NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
DELETE FROM activity;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
-- webhook
|
||||||
|
CREATE TABLE webhook (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_webhook_creator_id ON webhook (creator_id);
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
UPDATE user_setting SET key = 'USER_SETTING_LOCALE', value = REPLACE(value, '"', '') WHERE key = 'locale';
|
||||||
|
UPDATE user_setting SET key = 'USER_SETTING_APPEARANCE', value = REPLACE(value, '"', '') WHERE key = 'appearance';
|
||||||
|
UPDATE user_setting SET key = 'USER_SETTING_MEMO_VISIBILITY', value = REPLACE(value, '"', '') WHERE key = 'memo-visibility';
|
||||||
|
UPDATE user_setting SET key = 'USER_SETTING_TELEGRAM_USER_ID', value = REPLACE(value, '"', '') WHERE key = 'telegram-user-id';
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
ALTER TABLE memo ADD COLUMN resource_name TEXT NOT NULL DEFAULT "";
|
||||||
|
|
||||||
|
UPDATE memo SET resource_name = lower(hex(randomblob(8)));
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_memo_resource_name ON memo (resource_name);
|
||||||
|
|
||||||
|
ALTER TABLE resource ADD COLUMN resource_name TEXT NOT NULL DEFAULT "";
|
||||||
|
|
||||||
|
UPDATE resource SET resource_name = lower(hex(randomblob(8)));
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_resource_resource_name ON resource (resource_name);
|
||||||
@ -0,0 +1,60 @@
|
|||||||
|
-- change user role field from "OWNER"/"USER" to "HOST"/"USER".
|
||||||
|
PRAGMA foreign_keys = off;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
user RENAME TO _user_old;
|
||||||
|
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
open_id TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user (
|
||||||
|
id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
row_status,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
password_hash,
|
||||||
|
open_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
row_status,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
password_hash,
|
||||||
|
open_id
|
||||||
|
FROM
|
||||||
|
_user_old;
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
user
|
||||||
|
SET
|
||||||
|
role = 'HOST'
|
||||||
|
WHERE
|
||||||
|
id IN (
|
||||||
|
SELECT
|
||||||
|
id
|
||||||
|
FROM
|
||||||
|
_user_old
|
||||||
|
WHERE
|
||||||
|
role = 'OWNER'
|
||||||
|
);
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = on;
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
memo
|
||||||
|
ADD
|
||||||
|
COLUMN visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PRIVATE')) DEFAULT 'PRIVATE';
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
-- reaction
|
||||||
|
CREATE TABLE reaction (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
content_id TEXT NOT NULL,
|
||||||
|
reaction_type TEXT NOT NULL,
|
||||||
|
UNIQUE(creator_id, content_id, reaction_type)
|
||||||
|
);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE user ADD COLUMN description TEXT NOT NULL DEFAULT "";
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE memo RENAME COLUMN resource_name TO uid;
|
||||||
|
|
||||||
|
ALTER TABLE resource RENAME COLUMN resource_name TO uid;
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
ALTER TABLE resource ADD COLUMN storage_type TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE resource ADD COLUMN reference TEXT NOT NULL DEFAULT '';
|
||||||
|
|
||||||
|
ALTER TABLE resource ADD COLUMN payload TEXT NOT NULL DEFAULT '{}';
|
||||||
|
|
||||||
|
UPDATE resource
|
||||||
|
SET storage_type = 'LOCAL', reference = internal_path
|
||||||
|
WHERE internal_path IS NOT NULL AND internal_path != '';
|
||||||
|
|
||||||
|
UPDATE resource
|
||||||
|
SET storage_type = 'EXTERNAL', reference = external_link
|
||||||
|
WHERE external_link IS NOT NULL AND external_link != '';
|
||||||
|
|
||||||
|
ALTER TABLE resource DROP COLUMN internal_path;
|
||||||
|
|
||||||
|
ALTER TABLE resource DROP COLUMN external_link;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE memo ADD COLUMN tags TEXT NOT NULL DEFAULT '[]';
|
||||||
|
|
||||||
|
CREATE INDEX idx_memo_tags ON memo (tags);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE memo ADD COLUMN payload TEXT NOT NULL DEFAULT '{}';
|
||||||
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE tag;
|
||||||
@ -0,0 +1,43 @@
|
|||||||
|
-- change memo visibility field from "PRIVATE"/"PUBLIC" to "PRIVATE"/"PROTECTED"/"PUBLIC".
|
||||||
|
PRAGMA foreign_keys = off;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo RENAME TO _memo_old;
|
||||||
|
|
||||||
|
CREATE TABLE memo (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||||
|
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo (
|
||||||
|
id,
|
||||||
|
creator_id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
row_status,
|
||||||
|
content,
|
||||||
|
visibility
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
creator_id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
row_status,
|
||||||
|
content,
|
||||||
|
visibility
|
||||||
|
FROM
|
||||||
|
_memo_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_old;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = on;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
-- user_setting
|
||||||
|
CREATE TABLE user_setting (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX user_setting_key_user_id_index ON user_setting(key, user_id);
|
||||||
@ -0,0 +1,217 @@
|
|||||||
|
PRAGMA foreign_keys = off;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
user RENAME TO _user_old;
|
||||||
|
|
||||||
|
-- user
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
open_id TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_user_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_user_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_user_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `user` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`user`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo RENAME TO _memo_old;
|
||||||
|
|
||||||
|
-- memo
|
||||||
|
CREATE TABLE memo (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||||
|
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_memo_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_old;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_memo_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_memo_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `memo` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`memo`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo_organizer RENAME TO _memo_organizer_old;
|
||||||
|
|
||||||
|
-- memo_organizer
|
||||||
|
CREATE TABLE memo_organizer (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||||
|
FOREIGN KEY(memo_id) REFERENCES memo(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(memo_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo_organizer
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_memo_organizer_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _shortcut_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
shortcut RENAME TO _shortcut_old;
|
||||||
|
|
||||||
|
-- shortcut
|
||||||
|
CREATE TABLE shortcut (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
title TEXT NOT NULL DEFAULT '',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}',
|
||||||
|
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
shortcut
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_shortcut_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _shortcut_old;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_shortcut_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_shortcut_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `shortcut` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`shortcut`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _resource_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
resource RENAME TO _resource_old;
|
||||||
|
|
||||||
|
-- resource
|
||||||
|
CREATE TABLE resource (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
filename TEXT NOT NULL DEFAULT '',
|
||||||
|
blob BLOB DEFAULT NULL,
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
resource
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_resource_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _resource_old;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_resource_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_resource_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `resource` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`resource`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_setting_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
user_setting RENAME TO _user_setting_old;
|
||||||
|
|
||||||
|
-- user_setting
|
||||||
|
CREATE TABLE user_setting (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(user_id, key)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user_setting
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_user_setting_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_setting_old;
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = on;
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
-- memo_resource
|
||||||
|
CREATE TABLE memo_resource (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
resource_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
FOREIGN KEY(memo_id) REFERENCES memo(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(resource_id) REFERENCES resource(id) ON DELETE CASCADE,
|
||||||
|
UNIQUE(memo_id, resource_id)
|
||||||
|
);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
-- system_setting
|
||||||
|
CREATE TABLE system_setting (
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
UNIQUE(name)
|
||||||
|
);
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE
|
||||||
|
resource
|
||||||
|
ADD
|
||||||
|
COLUMN external_link TEXT NOT NULL DEFAULT '';
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
DROP TRIGGER IF EXISTS `trigger_update_user_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_user_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `user` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`user`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_memo_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_memo_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `memo` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`memo`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_shortcut_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_shortcut_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `shortcut` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`shortcut`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_resource_modification_time`;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS `trigger_update_resource_modification_time`
|
||||||
|
AFTER
|
||||||
|
UPDATE
|
||||||
|
ON `resource` FOR EACH ROW BEGIN
|
||||||
|
UPDATE
|
||||||
|
`resource`
|
||||||
|
SET
|
||||||
|
updated_ts = (strftime('%s', 'now'))
|
||||||
|
WHERE
|
||||||
|
rowid = old.rowid;
|
||||||
|
|
||||||
|
END;
|
||||||
@ -0,0 +1,191 @@
|
|||||||
|
PRAGMA foreign_keys = off;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
user RENAME TO _user_old;
|
||||||
|
|
||||||
|
-- user
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
email TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'USER')) DEFAULT 'USER',
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
open_id TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_user_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo RENAME TO _memo_old;
|
||||||
|
|
||||||
|
-- memo
|
||||||
|
CREATE TABLE memo (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_memo_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo_organizer RENAME TO _memo_organizer_old;
|
||||||
|
|
||||||
|
-- memo_organizer
|
||||||
|
CREATE TABLE memo_organizer (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||||
|
UNIQUE(memo_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo_organizer
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_memo_organizer_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_organizer_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _shortcut_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
shortcut RENAME TO _shortcut_old;
|
||||||
|
|
||||||
|
-- shortcut
|
||||||
|
CREATE TABLE shortcut (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
title TEXT NOT NULL DEFAULT '',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
shortcut
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_shortcut_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _shortcut_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _resource_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
resource RENAME TO _resource_old;
|
||||||
|
|
||||||
|
-- resource
|
||||||
|
CREATE TABLE resource (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
filename TEXT NOT NULL DEFAULT '',
|
||||||
|
blob BLOB DEFAULT NULL,
|
||||||
|
external_link TEXT NOT NULL DEFAULT '',
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
size INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
resource (
|
||||||
|
id,
|
||||||
|
creator_id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
filename,
|
||||||
|
blob,
|
||||||
|
external_link,
|
||||||
|
type,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
creator_id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
filename,
|
||||||
|
blob,
|
||||||
|
external_link,
|
||||||
|
type,
|
||||||
|
size
|
||||||
|
FROM
|
||||||
|
_resource_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _resource_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_setting_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
user_setting RENAME TO _user_setting_old;
|
||||||
|
|
||||||
|
-- user_setting
|
||||||
|
CREATE TABLE user_setting (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
UNIQUE(user_id, key)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user_setting
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_user_setting_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_setting_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_resource_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
memo_resource RENAME TO _memo_resource_old;
|
||||||
|
|
||||||
|
-- memo_resource
|
||||||
|
CREATE TABLE memo_resource (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
resource_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
UNIQUE(memo_id, resource_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo_resource
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
_memo_resource_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _memo_resource_old;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
DROP TRIGGER IF EXISTS `trigger_update_user_modification_time`;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_memo_modification_time`;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_shortcut_modification_time`;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS `trigger_update_resource_modification_time`;
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
-- migration_history
|
||||||
|
CREATE TABLE IF NOT EXISTS migration_history (
|
||||||
|
version TEXT NOT NULL PRIMARY KEY,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
-- add column username TEXT NOT NULL UNIQUE
|
||||||
|
-- rename column name to nickname
|
||||||
|
-- add role `ADMIN`
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
|
|
||||||
|
ALTER TABLE
|
||||||
|
user RENAME TO _user_old;
|
||||||
|
|
||||||
|
-- user
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||||
|
email TEXT NOT NULL DEFAULT '',
|
||||||
|
nickname TEXT NOT NULL DEFAULT '',
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
open_id TEXT NOT NULL UNIQUE
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
user (
|
||||||
|
id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
row_status,
|
||||||
|
username,
|
||||||
|
role,
|
||||||
|
email,
|
||||||
|
nickname,
|
||||||
|
password_hash,
|
||||||
|
open_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
created_ts,
|
||||||
|
updated_ts,
|
||||||
|
row_status,
|
||||||
|
email,
|
||||||
|
role,
|
||||||
|
email,
|
||||||
|
name,
|
||||||
|
password_hash,
|
||||||
|
open_id
|
||||||
|
FROM
|
||||||
|
_user_old;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS _user_old;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
-- tag
|
||||||
|
CREATE TABLE tag (
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
UNIQUE(name, creator_id)
|
||||||
|
);
|
||||||
@ -0,0 +1,146 @@
|
|||||||
|
-- migration_history
|
||||||
|
CREATE TABLE migration_history (
|
||||||
|
version TEXT NOT NULL PRIMARY KEY,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- system_setting
|
||||||
|
CREATE TABLE system_setting (
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
UNIQUE(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- user
|
||||||
|
CREATE TABLE user (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
username TEXT NOT NULL UNIQUE,
|
||||||
|
role TEXT NOT NULL CHECK (role IN ('HOST', 'ADMIN', 'USER')) DEFAULT 'USER',
|
||||||
|
email TEXT NOT NULL DEFAULT '',
|
||||||
|
nickname TEXT NOT NULL DEFAULT '',
|
||||||
|
password_hash TEXT NOT NULL,
|
||||||
|
avatar_url TEXT NOT NULL DEFAULT '',
|
||||||
|
description TEXT NOT NULL DEFAULT ''
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_user_username ON user (username);
|
||||||
|
|
||||||
|
-- user_setting
|
||||||
|
CREATE TABLE user_setting (
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
key TEXT NOT NULL,
|
||||||
|
value TEXT NOT NULL,
|
||||||
|
UNIQUE(user_id, key)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- memo
|
||||||
|
CREATE TABLE memo (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
uid TEXT NOT NULL UNIQUE,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
content TEXT NOT NULL DEFAULT '',
|
||||||
|
visibility TEXT NOT NULL CHECK (visibility IN ('PUBLIC', 'PROTECTED', 'PRIVATE')) DEFAULT 'PRIVATE',
|
||||||
|
tags TEXT NOT NULL DEFAULT '[]',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_memo_creator_id ON memo (creator_id);
|
||||||
|
CREATE INDEX idx_memo_content ON memo (content);
|
||||||
|
CREATE INDEX idx_memo_visibility ON memo (visibility);
|
||||||
|
CREATE INDEX idx_memo_tags ON memo (tags);
|
||||||
|
|
||||||
|
-- memo_organizer
|
||||||
|
CREATE TABLE memo_organizer (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||||
|
UNIQUE(memo_id, user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- memo_relation
|
||||||
|
CREATE TABLE memo_relation (
|
||||||
|
memo_id INTEGER NOT NULL,
|
||||||
|
related_memo_id INTEGER NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
UNIQUE(memo_id, related_memo_id, type)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- resource
|
||||||
|
CREATE TABLE resource (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
uid TEXT NOT NULL UNIQUE,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
filename TEXT NOT NULL DEFAULT '',
|
||||||
|
blob BLOB DEFAULT NULL,
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
size INTEGER NOT NULL DEFAULT 0,
|
||||||
|
memo_id INTEGER,
|
||||||
|
storage_type TEXT NOT NULL DEFAULT '',
|
||||||
|
reference TEXT NOT NULL DEFAULT '',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_resource_creator_id ON resource (creator_id);
|
||||||
|
|
||||||
|
CREATE INDEX idx_resource_memo_id ON resource (memo_id);
|
||||||
|
|
||||||
|
-- activity
|
||||||
|
CREATE TABLE activity (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
type TEXT NOT NULL DEFAULT '',
|
||||||
|
level TEXT NOT NULL CHECK (level IN ('INFO', 'WARN', 'ERROR')) DEFAULT 'INFO',
|
||||||
|
payload TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- idp
|
||||||
|
CREATE TABLE idp (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
identifier_filter TEXT NOT NULL DEFAULT '',
|
||||||
|
config TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- inbox
|
||||||
|
CREATE TABLE inbox (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
sender_id INTEGER NOT NULL,
|
||||||
|
receiver_id INTEGER NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
message TEXT NOT NULL DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- webhook
|
||||||
|
CREATE TABLE webhook (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
row_status TEXT NOT NULL CHECK (row_status IN ('NORMAL', 'ARCHIVED')) DEFAULT 'NORMAL',
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
url TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_webhook_creator_id ON webhook (creator_id);
|
||||||
|
|
||||||
|
-- reaction
|
||||||
|
CREATE TABLE reaction (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
creator_id INTEGER NOT NULL,
|
||||||
|
content_id TEXT NOT NULL,
|
||||||
|
reaction_type TEXT NOT NULL,
|
||||||
|
UNIQUE(creator_id, content_id, reaction_type)
|
||||||
|
);
|
||||||
@ -1 +1,198 @@
|
|||||||
package store
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"log/slog"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/usememos/memos/server/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed migration
|
||||||
|
var migrationFS embed.FS
|
||||||
|
|
||||||
|
//go:embed seed
|
||||||
|
var seedFS embed.FS
|
||||||
|
|
||||||
|
// Migrate applies the latest schema to the database.
|
||||||
|
func (s *Store) Migrate(ctx context.Context) error {
|
||||||
|
if err := s.preMigrate(ctx); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to pre-migrate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Profile.Mode == "prod" {
|
||||||
|
migrationHistoryList, err := s.driver.FindMigrationHistoryList(ctx, &FindMigrationHistory{})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to find migration history")
|
||||||
|
}
|
||||||
|
if len(migrationHistoryList) == 0 {
|
||||||
|
return errors.Errorf("no migration history found")
|
||||||
|
}
|
||||||
|
|
||||||
|
migrationHistoryVersions := []string{}
|
||||||
|
for _, migrationHistory := range migrationHistoryList {
|
||||||
|
migrationHistoryVersions = append(migrationHistoryVersions, migrationHistory.Version)
|
||||||
|
}
|
||||||
|
sort.Sort(version.SortVersion(migrationHistoryVersions))
|
||||||
|
latestMigrationHistoryVersion := migrationHistoryVersions[len(migrationHistoryVersions)-1]
|
||||||
|
currentVersion := version.GetCurrentVersion(s.Profile.Mode)
|
||||||
|
|
||||||
|
if version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), latestMigrationHistoryVersion) {
|
||||||
|
minorVersionList := s.getMinorVersionList()
|
||||||
|
fmt.Println("start migration")
|
||||||
|
for _, minorVersion := range minorVersionList {
|
||||||
|
normalizedVersion := minorVersion + ".0"
|
||||||
|
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
||||||
|
fmt.Println("applying migration for", normalizedVersion)
|
||||||
|
if err := s.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to apply minor version migration")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println("end migrate")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// In demo mode, we should seed the database.
|
||||||
|
if s.Profile.Mode == "demo" {
|
||||||
|
if err := s.seed(ctx); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to seed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) preMigrate(ctx context.Context) error {
|
||||||
|
migrationHistoryList, err := s.driver.FindMigrationHistoryList(ctx, &FindMigrationHistory{})
|
||||||
|
// If there is no migration history, we should apply the latest schema.
|
||||||
|
if err != nil || len(migrationHistoryList) == 0 {
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to find migration history", "error", err)
|
||||||
|
}
|
||||||
|
fileName := s.getMigrationBasePath() + latestSchemaFileName
|
||||||
|
bytes, err := migrationFS.ReadFile(fileName)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("failed to read latest schema file: %s", err)
|
||||||
|
}
|
||||||
|
if err := s.execute(ctx, string(bytes)); err != nil {
|
||||||
|
return errors.Errorf("failed to exec SQL file %s, err %s", fileName, err)
|
||||||
|
}
|
||||||
|
if _, err := s.driver.UpsertMigrationHistory(ctx, &UpsertMigrationHistory{
|
||||||
|
Version: version.GetCurrentVersion(s.Profile.Mode),
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to upsert migration history")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) getMigrationBasePath() string {
|
||||||
|
mode := "dev"
|
||||||
|
if s.Profile.Mode == "prod" {
|
||||||
|
mode = "prod"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("migration/%s/%s/", s.driver.Type(), mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) getSeedBasePath() string {
|
||||||
|
return fmt.Sprintf("seed/%s/", s.driver.Type())
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
latestSchemaFileName = "LATEST__SCHEMA.sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Store) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
||||||
|
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("%s%s/*.sql", s.getMigrationBasePath(), minorVersion))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to read migration files")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(filenames)
|
||||||
|
migrationStmt := ""
|
||||||
|
// Loop over all migration files and execute them in order.
|
||||||
|
for _, filename := range filenames {
|
||||||
|
buf, err := migrationFS.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read minor version migration file, filename=%s", filename)
|
||||||
|
}
|
||||||
|
stmt := string(buf)
|
||||||
|
migrationStmt += stmt
|
||||||
|
if err := s.execute(ctx, stmt); err != nil {
|
||||||
|
return errors.Wrapf(err, "migrate error: %s", stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upsert the newest version to migration_history.
|
||||||
|
version := minorVersion + ".0"
|
||||||
|
if _, err = s.driver.UpsertMigrationHistory(ctx, &UpsertMigrationHistory{
|
||||||
|
Version: version,
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to upsert migration history with version: %s", version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) seed(ctx context.Context) error {
|
||||||
|
// Only seed for SQLite.
|
||||||
|
if s.driver.Type() != "sqlite" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filenames, err := fs.Glob(seedFS, fmt.Sprintf("%s*.sql", s.getSeedBasePath()))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to read seed files")
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(filenames)
|
||||||
|
// Loop over all seed files and execute them in order.
|
||||||
|
for _, filename := range filenames {
|
||||||
|
bytes, err := seedFS.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to read seed file, filename=%s", filename)
|
||||||
|
}
|
||||||
|
if err := s.execute(ctx, string(bytes)); err != nil {
|
||||||
|
return errors.Wrapf(err, "seed error: %s", filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute runs a single SQL statement within a transaction.
|
||||||
|
func (s *Store) execute(ctx context.Context, stmt string) error {
|
||||||
|
tx, err := s.driver.GetDB().Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tx.Rollback()
|
||||||
|
if _, err := tx.ExecContext(ctx, stmt); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to execute statement")
|
||||||
|
}
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) getMinorVersionList() []string {
|
||||||
|
var minorDirRegexp = regexp.MustCompile(fmt.Sprintf(`^%s[0-9]+\.[0-9]+$`, s.getMigrationBasePath()))
|
||||||
|
minorVersionList := []string{}
|
||||||
|
if err := fs.WalkDir(migrationFS, "migration", func(path string, file fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if file.IsDir() && minorDirRegexp.MatchString(path) {
|
||||||
|
minorVersionList = append(minorVersionList, file.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(version.SortVersion(minorVersionList))
|
||||||
|
return minorVersionList
|
||||||
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
DELETE FROM system_setting;
|
||||||
|
DELETE FROM user;
|
||||||
|
DELETE FROM user_setting;
|
||||||
|
DELETE FROM memo;
|
||||||
|
DELETE FROM memo_organizer;
|
||||||
|
DELETE FROM memo_relation;
|
||||||
|
DELETE FROM resource;
|
||||||
|
DELETE FROM activity;
|
||||||
|
DELETE FROM idp;
|
||||||
|
DELETE FROM inbox;
|
||||||
|
DELETE FROM webhook;
|
||||||
|
DELETE FROM reaction;
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue