mirror of
https://github.com/aykhans/slash-e.git
synced 2025-06-15 04:17:50 +00:00
feat: refactor code structure
This commit is contained in:
161
store/db/db.go
161
store/db/db.go
@ -24,8 +24,8 @@ var seedFS embed.FS
|
||||
|
||||
type DB struct {
|
||||
// sqlite db connection instance
|
||||
Db *sql.DB
|
||||
profile *profile.Profile
|
||||
DBInstance *sql.DB
|
||||
profile *profile.Profile
|
||||
}
|
||||
|
||||
// NewDB returns a new instance of DB associated with the given datasource name.
|
||||
@ -42,83 +42,87 @@ func (db *DB) Open(ctx context.Context) (err error) {
|
||||
return fmt.Errorf("dsn required")
|
||||
}
|
||||
|
||||
// Connect to the database.
|
||||
sqlDB, err := sql.Open("sqlite3", db.profile.DSN+"?_foreign_keys=1")
|
||||
// Connect to the database without foreign_key.
|
||||
sqliteDB, err := sql.Open("sqlite3", db.profile.DSN+"?cache=shared&_foreign_keys=0&_journal_mode=WAL")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open db with dsn: %s, err: %w", db.profile.DSN, err)
|
||||
}
|
||||
db.DBInstance = sqliteDB
|
||||
|
||||
db.Db = sqlDB
|
||||
// If mode is dev, we should migrate and seed the database.
|
||||
if db.profile.Mode == "dev" {
|
||||
if err := db.applyLatestSchema(ctx); err != nil {
|
||||
return fmt.Errorf("failed to apply latest schema: %w", err)
|
||||
}
|
||||
if err := db.seed(ctx); err != nil {
|
||||
return fmt.Errorf("failed to seed: %w", err)
|
||||
}
|
||||
} else {
|
||||
if db.profile.Mode == "prod" {
|
||||
// If db file not exists, we should migrate the database.
|
||||
if _, err := os.Stat(db.profile.DSN); errors.Is(err, os.ErrNotExist) {
|
||||
err := db.applyLatestSchema(ctx)
|
||||
if err != nil {
|
||||
if err := db.applyLatestSchema(ctx); err != nil {
|
||||
return fmt.Errorf("failed to apply latest schema: %w", err)
|
||||
}
|
||||
} else {
|
||||
err := db.createMigrationHistoryTable(ctx)
|
||||
}
|
||||
|
||||
currentVersion := version.GetCurrentVersion(db.profile.Mode)
|
||||
migrationHistoryList, err := db.FindMigrationHistoryList(ctx, &MigrationHistoryFind{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find migration history, err: %w", err)
|
||||
}
|
||||
if len(migrationHistoryList) == 0 {
|
||||
_, err := db.UpsertMigrationHistory(ctx, &MigrationHistoryUpsert{
|
||||
Version: currentVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create migration_history table: %w", err)
|
||||
return fmt.Errorf("failed to upsert migration history, err: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
currentVersion := version.GetCurrentVersion(db.profile.Mode)
|
||||
migrationHistory, err := db.FindMigrationHistory(ctx, &MigrationHistoryFind{})
|
||||
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) {
|
||||
minorVersionList := getMinorVersionList()
|
||||
|
||||
// backup the raw database file before migration
|
||||
rawBytes, err := os.ReadFile(db.profile.DSN)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to read raw database file, err: %w", err)
|
||||
}
|
||||
if migrationHistory == nil {
|
||||
migrationHistory, err = db.UpsertMigrationHistory(ctx, &MigrationHistoryUpsert{
|
||||
Version: currentVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
backupDBFilePath := fmt.Sprintf("%s/corgi_%s_%d_backup.db", db.profile.Data, db.profile.Version, time.Now().Unix())
|
||||
if err := os.WriteFile(backupDBFilePath, rawBytes, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write raw database file, err: %w", err)
|
||||
}
|
||||
println("succeed to copy a backup database file")
|
||||
|
||||
if version.IsVersionGreaterThan(version.GetSchemaVersion(currentVersion), migrationHistory.Version) {
|
||||
minorVersionList := getMinorVersionList()
|
||||
|
||||
// backup the raw database file before migration
|
||||
rawBytes, err := os.ReadFile(db.profile.DSN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read raw database file, err: %w", err)
|
||||
}
|
||||
backupDBFilePath := fmt.Sprintf("%s/corgi_%s_%d_backup.db", db.profile.Data, db.profile.Version, time.Now().Unix())
|
||||
if err := os.WriteFile(backupDBFilePath, rawBytes, 0644); err != nil {
|
||||
return fmt.Errorf("failed to write raw database file, err: %w", err)
|
||||
}
|
||||
|
||||
println("succeed to copy a backup database file")
|
||||
println("start migrate")
|
||||
for _, minorVersion := range minorVersionList {
|
||||
normalizedVersion := minorVersion + ".0"
|
||||
if version.IsVersionGreaterThan(normalizedVersion, migrationHistory.Version) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
||||
println("applying migration for", normalizedVersion)
|
||||
if err := db.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
||||
return fmt.Errorf("failed to apply minor version migration: %w", err)
|
||||
}
|
||||
println("start migrate")
|
||||
for _, minorVersion := range minorVersionList {
|
||||
normalizedVersion := minorVersion + ".0"
|
||||
if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) {
|
||||
println("applying migration for", normalizedVersion)
|
||||
if err := db.applyMigrationForMinorVersion(ctx, minorVersion); err != nil {
|
||||
return fmt.Errorf("failed to apply minor version migration: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
println("end migrate")
|
||||
|
||||
println("end migrate")
|
||||
// remove the created backup db file after migrate succeed
|
||||
if err := os.Remove(backupDBFilePath); err != nil {
|
||||
println(fmt.Sprintf("Failed to remove temp database file, err %v", err))
|
||||
}
|
||||
// remove the created backup db file after migrate succeed
|
||||
if err := os.Remove(backupDBFilePath); err != nil {
|
||||
println(fmt.Sprintf("Failed to remove temp database file, err %v", err))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In non-prod mode, we should always migrate the database.
|
||||
if _, err := os.Stat(db.profile.DSN); errors.Is(err, os.ErrNotExist) {
|
||||
if err := db.applyLatestSchema(ctx); err != nil {
|
||||
return fmt.Errorf("failed to apply latest schema: %w", err)
|
||||
}
|
||||
if err := db.seed(ctx); err != nil {
|
||||
return fmt.Errorf("failed to seed: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
@ -126,7 +130,11 @@ const (
|
||||
)
|
||||
|
||||
func (db *DB) applyLatestSchema(ctx context.Context) error {
|
||||
latestSchemaPath := fmt.Sprintf("%s/%s/%s", "migration", db.profile.Mode, latestSchemaFileName)
|
||||
schemaMode := "dev"
|
||||
if db.profile.Mode == "prod" {
|
||||
schemaMode = "prod"
|
||||
}
|
||||
latestSchemaPath := fmt.Sprintf("%s/%s/%s", "migration", schemaMode, latestSchemaFileName)
|
||||
buf, err := migrationFS.ReadFile(latestSchemaPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read latest schema %q, error %w", latestSchemaPath, err)
|
||||
@ -141,7 +149,7 @@ func (db *DB) applyLatestSchema(ctx context.Context) error {
|
||||
func (db *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion string) error {
|
||||
filenames, err := fs.Glob(migrationFS, fmt.Sprintf("%s/%s/*.sql", "migration/prod", minorVersion))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to read ddl files, err: %w", err)
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
@ -160,17 +168,18 @@ func (db *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion st
|
||||
}
|
||||
}
|
||||
|
||||
tx, err := db.Db.Begin()
|
||||
tx, err := db.DBInstance.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// upsert the newest version to migration_history
|
||||
version := minorVersion + ".0"
|
||||
if _, err = upsertMigrationHistory(ctx, tx, &MigrationHistoryUpsert{
|
||||
Version: minorVersion + ".0",
|
||||
Version: version,
|
||||
}); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to upsert migration history with version: %s, err: %w", version, err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
@ -179,7 +188,7 @@ func (db *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion st
|
||||
func (db *DB) seed(ctx context.Context) error {
|
||||
filenames, err := fs.Glob(seedFS, fmt.Sprintf("%s/*.sql", "seed"))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to read seed files, err: %w", err)
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
@ -200,14 +209,14 @@ func (db *DB) seed(ctx context.Context) error {
|
||||
|
||||
// execute runs a single SQL statement within a transaction.
|
||||
func (db *DB) execute(ctx context.Context, stmt string) error {
|
||||
tx, err := db.Db.Begin()
|
||||
tx, err := db.DBInstance.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if _, err := tx.ExecContext(ctx, stmt); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to execute statement, err: %w", err)
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
@ -232,27 +241,7 @@ func getMinorVersionList() []string {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Strings(minorVersionList)
|
||||
sort.Sort(version.SortVersion(minorVersionList))
|
||||
|
||||
return minorVersionList
|
||||
}
|
||||
|
||||
// createMigrationHistoryTable creates the migration_history table if it doesn't exist.
|
||||
func (db *DB) createMigrationHistoryTable(ctx context.Context) error {
|
||||
tx, err := db.Db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if err := createTable(ctx, tx, `
|
||||
CREATE TABLE IF NOT EXISTS migration_history (
|
||||
version TEXT NOT NULL PRIMARY KEY,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
`); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
@ -1,52 +1,9 @@
|
||||
-- drop all tables
|
||||
DROP TABLE IF EXISTS `activity`;
|
||||
DROP TABLE IF EXISTS `shortcut_organizer`;
|
||||
DROP TABLE IF EXISTS `shortcut`;
|
||||
DROP TABLE IF EXISTS `workspace_user`;
|
||||
DROP TABLE IF EXISTS `user_setting`;
|
||||
DROP TABLE IF EXISTS `user`;
|
||||
DROP TABLE IF EXISTS `workspace_setting`;
|
||||
DROP TABLE IF EXISTS `workspace`;
|
||||
|
||||
-- workspace
|
||||
CREATE TABLE workspace (
|
||||
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',
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
-- migration_history
|
||||
CREATE TABLE migration_history (
|
||||
version TEXT NOT NULL PRIMARY KEY,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('workspace', 10);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_workspace_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `workspace` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`workspace`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
END;
|
||||
|
||||
-- workspace_setting
|
||||
CREATE TABLE workspace_setting (
|
||||
workspace_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX workspace_setting_key_workspace_id_index ON workspace_setting(key, workspace_id);
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@ -54,7 +11,7 @@ CREATE TABLE user (
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
open_id TEXT NOT NULL UNIQUE
|
||||
);
|
||||
@ -64,47 +21,46 @@ INSERT INTO
|
||||
VALUES
|
||||
('user', 100);
|
||||
|
||||
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;
|
||||
|
||||
CREATE INDEX user_id_index ON user(id);
|
||||
|
||||
CREATE UNIQUE INDEX user_email_index ON user(email);
|
||||
|
||||
CREATE UNIQUE INDEX user_open_id_index ON user(open_id);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX user_setting_key_user_id_index ON user_setting(key, user_id);
|
||||
-- workspace
|
||||
CREATE TABLE workspace (
|
||||
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',
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('workspace', 10);
|
||||
|
||||
-- workspace_setting
|
||||
CREATE TABLE workspace_setting (
|
||||
workspace_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
UNIQUE(workspace_id, key)
|
||||
);
|
||||
|
||||
-- workspace_user
|
||||
CREATE TABLE workspace_user (
|
||||
workspace_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER',
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
UNIQUE(workspace_id, user_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX workspace_user_workspace_id_user_id_index ON workspace_user(workspace_id, user_id);
|
||||
|
||||
-- shortcut
|
||||
CREATE TABLE shortcut (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@ -116,52 +72,18 @@ CREATE TABLE shortcut (
|
||||
name TEXT NOT NULL,
|
||||
link TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE'
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX shortcut_workspace_id_name_index ON shortcut(workspace_id, name);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('shortcut', 1000);
|
||||
|
||||
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;
|
||||
|
||||
-- shortcut_organizer
|
||||
CREATE TABLE shortcut_organizer (
|
||||
shortcut_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
FOREIGN KEY(shortcut_id) REFERENCES shortcut(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
UNIQUE(shortcut_id, user_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,
|
||||
comment TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('activity', 10000);
|
||||
);
|
@ -1,52 +1,9 @@
|
||||
-- drop all tables
|
||||
DROP TABLE IF EXISTS `activity`;
|
||||
DROP TABLE IF EXISTS `shortcut_organizer`;
|
||||
DROP TABLE IF EXISTS `shortcut`;
|
||||
DROP TABLE IF EXISTS `workspace_user`;
|
||||
DROP TABLE IF EXISTS `user_setting`;
|
||||
DROP TABLE IF EXISTS `user`;
|
||||
DROP TABLE IF EXISTS `workspace_setting`;
|
||||
DROP TABLE IF EXISTS `workspace`;
|
||||
|
||||
-- workspace
|
||||
CREATE TABLE workspace (
|
||||
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',
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
-- migration_history
|
||||
CREATE TABLE migration_history (
|
||||
version TEXT NOT NULL PRIMARY KEY,
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('workspace', 10);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS `trigger_update_workspace_modification_time`
|
||||
AFTER
|
||||
UPDATE
|
||||
ON `workspace` FOR EACH ROW BEGIN
|
||||
UPDATE
|
||||
`workspace`
|
||||
SET
|
||||
updated_ts = (strftime('%s', 'now'))
|
||||
WHERE
|
||||
rowid = old.rowid;
|
||||
END;
|
||||
|
||||
-- workspace_setting
|
||||
CREATE TABLE workspace_setting (
|
||||
workspace_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX workspace_setting_key_workspace_id_index ON workspace_setting(key, workspace_id);
|
||||
|
||||
-- user
|
||||
CREATE TABLE user (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@ -54,7 +11,7 @@ CREATE TABLE user (
|
||||
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,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
open_id TEXT NOT NULL UNIQUE
|
||||
);
|
||||
@ -64,47 +21,46 @@ INSERT INTO
|
||||
VALUES
|
||||
('user', 100);
|
||||
|
||||
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;
|
||||
|
||||
CREATE INDEX user_id_index ON user(id);
|
||||
|
||||
CREATE UNIQUE INDEX user_email_index ON user(email);
|
||||
|
||||
CREATE UNIQUE INDEX user_open_id_index ON user(open_id);
|
||||
|
||||
-- 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)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX user_setting_key_user_id_index ON user_setting(key, user_id);
|
||||
-- workspace
|
||||
CREATE TABLE workspace (
|
||||
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',
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('workspace', 10);
|
||||
|
||||
-- workspace_setting
|
||||
CREATE TABLE workspace_setting (
|
||||
workspace_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
UNIQUE(workspace_id, key)
|
||||
);
|
||||
|
||||
-- workspace_user
|
||||
CREATE TABLE workspace_user (
|
||||
workspace_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
role TEXT NOT NULL CHECK (role IN ('ADMIN', 'USER')) DEFAULT 'USER',
|
||||
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
UNIQUE(workspace_id, user_id)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX workspace_user_workspace_id_user_id_index ON workspace_user(workspace_id, user_id);
|
||||
|
||||
-- shortcut
|
||||
CREATE TABLE shortcut (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@ -116,52 +72,18 @@ CREATE TABLE shortcut (
|
||||
name TEXT NOT NULL,
|
||||
link TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE')) DEFAULT 'PRIVATE',
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(workspace_id) REFERENCES workspace(id) ON DELETE CASCADE
|
||||
visibility TEXT NOT NULL CHECK (visibility IN ('PRIVATE', 'WORKSPACE', 'PUBLIC')) DEFAULT 'PRIVATE'
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX shortcut_workspace_id_name_index ON shortcut(workspace_id, name);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('shortcut', 1000);
|
||||
|
||||
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;
|
||||
|
||||
-- shortcut_organizer
|
||||
CREATE TABLE shortcut_organizer (
|
||||
shortcut_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
pinned INTEGER NOT NULL CHECK (pinned IN (0, 1)) DEFAULT 0,
|
||||
FOREIGN KEY(shortcut_id) REFERENCES shortcut(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(user_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
UNIQUE(shortcut_id, user_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,
|
||||
comment TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
FOREIGN KEY(creator_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
sqlite_sequence (name, seq)
|
||||
VALUES
|
||||
('activity', 10000);
|
||||
);
|
@ -19,8 +19,8 @@ type MigrationHistoryFind struct {
|
||||
Version *string
|
||||
}
|
||||
|
||||
func (db *DB) FindMigrationHistory(ctx context.Context, find *MigrationHistoryFind) (*MigrationHistory, error) {
|
||||
tx, err := db.Db.Begin()
|
||||
func (db *DB) FindMigrationHistoryList(ctx context.Context, find *MigrationHistoryFind) ([]*MigrationHistory, error) {
|
||||
tx, err := db.DBInstance.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -31,16 +31,11 @@ func (db *DB) FindMigrationHistory(ctx context.Context, find *MigrationHistoryFi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(list) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
migrationHistory := list[0]
|
||||
return migrationHistory, nil
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (db *DB) UpsertMigrationHistory(ctx context.Context, upsert *MigrationHistoryUpsert) (*MigrationHistory, error) {
|
||||
tx, err := db.Db.Begin()
|
||||
tx, err := db.DBInstance.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -111,24 +106,13 @@ func upsertMigrationHistory(ctx context.Context, tx *sql.Tx, upsert *MigrationHi
|
||||
version=EXCLUDED.version
|
||||
RETURNING version, created_ts
|
||||
`
|
||||
row, err := tx.QueryContext(ctx, query, upsert.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer row.Close()
|
||||
|
||||
row.Next()
|
||||
var migrationHistory MigrationHistory
|
||||
if err := row.Scan(
|
||||
if err := tx.QueryRowContext(ctx, query, upsert.Version).Scan(
|
||||
&migrationHistory.Version,
|
||||
&migrationHistory.CreatedTs,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := row.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &migrationHistory, nil
|
||||
}
|
||||
|
@ -1,8 +1,20 @@
|
||||
DELETE FROM activity;
|
||||
DELETE FROM shortcut_organizer;
|
||||
DELETE FROM shortcut;
|
||||
DELETE FROM workspace_user;
|
||||
DELETE FROM user_setting;
|
||||
DELETE FROM user;
|
||||
DELETE FROM workspace_setting;
|
||||
DELETE FROM workspace;
|
||||
DELETE FROM
|
||||
shortcut_organizer;
|
||||
|
||||
DELETE FROM
|
||||
shortcut;
|
||||
|
||||
DELETE FROM
|
||||
workspace_user;
|
||||
|
||||
DELETE FROM
|
||||
user_setting;
|
||||
|
||||
DELETE FROM
|
||||
user;
|
||||
|
||||
DELETE FROM
|
||||
workspace_setting;
|
||||
|
||||
DELETE FROM
|
||||
workspace;
|
@ -1,14 +1,14 @@
|
||||
INSERT INTO
|
||||
INSERT INTO
|
||||
user (
|
||||
`id`,
|
||||
`id`,
|
||||
`email`,
|
||||
`name`,
|
||||
`display_name`,
|
||||
`password_hash`,
|
||||
`open_id`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
101,
|
||||
101,
|
||||
'frank@iamcorgi.com',
|
||||
'Frank',
|
||||
-- raw password: secret
|
||||
@ -16,20 +16,20 @@ VALUES
|
||||
'frank_open_id'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
INSERT INTO
|
||||
user (
|
||||
`id`,
|
||||
`id`,
|
||||
`email`,
|
||||
`name`,
|
||||
`display_name`,
|
||||
`password_hash`,
|
||||
`open_id`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
102,
|
||||
102,
|
||||
'bob@iamcorgi.com',
|
||||
'Bob',
|
||||
-- raw password: secret
|
||||
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK',
|
||||
'bob_open_id'
|
||||
);
|
||||
);
|
@ -1,65 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
Name string
|
||||
SQL string
|
||||
}
|
||||
|
||||
//lint:ignore U1000 Ignore unused function temporarily for debugging
|
||||
//nolint:all
|
||||
func findTable(ctx context.Context, tx *sql.Tx, tableName string) (*Table, error) {
|
||||
where, args := []string{"1 = 1"}, []interface{}{}
|
||||
|
||||
where, args = append(where, "type = ?"), append(args, "table")
|
||||
where, args = append(where, "name = ?"), append(args, tableName)
|
||||
|
||||
query := `
|
||||
SELECT
|
||||
tbl_name,
|
||||
sql
|
||||
FROM sqlite_schema
|
||||
WHERE ` + strings.Join(where, " AND ")
|
||||
rows, err := tx.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
tableList := make([]*Table, 0)
|
||||
for rows.Next() {
|
||||
var table Table
|
||||
if err := rows.Scan(
|
||||
&table.Name,
|
||||
&table.SQL,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tableList = append(tableList, &table)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(tableList) == 0 {
|
||||
return nil, nil
|
||||
} else {
|
||||
return tableList[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
func createTable(ctx context.Context, tx *sql.Tx, stmt string) error {
|
||||
_, err := tx.ExecContext(ctx, stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user