From d67eaaaee2a086efc3db9c7e6e0b8b313f9f81c1 Mon Sep 17 00:00:00 2001 From: Steven Date: Sat, 6 Jan 2024 16:55:13 +0800 Subject: [PATCH] chore: update database migrator --- docs/documenting-the-api.md | 113 ------------------ docs/windows-service.md | 98 --------------- go.mod | 1 + go.sum | 2 + store/db/mysql/activity.go | 2 +- store/db/mysql/memo.go | 18 ++- .../db/mysql/migration/dev/LATEST__SCHEMA.sql | 16 --- .../mysql/migration/prod/LATEST__SCHEMA.sql | 16 --- store/db/mysql/migration_history.go | 1 - store/db/mysql/migrator.go | 54 ++------- store/db/mysql/seed/10000__reset.sql | 4 - store/db/mysql/webhook.go | 4 +- store/db/postgres/memo.go | 1 - .../postgres/migration/dev/LATEST__SCHEMA.sql | 16 --- .../migration/prod/LATEST__SCHEMA.sql | 16 --- store/db/postgres/migrator.go | 43 +------ store/db/postgres/seed/10000__reset.sql | 4 - store/migrator.go | 2 +- test/store/store.go | 38 +++++- test/store/store_test.go | 1 - test/test.go | 6 + 21 files changed, 71 insertions(+), 385 deletions(-) delete mode 100644 docs/documenting-the-api.md delete mode 100644 docs/windows-service.md delete mode 100644 store/db/mysql/seed/10000__reset.sql delete mode 100644 store/db/postgres/seed/10000__reset.sql delete mode 100644 test/store/store_test.go diff --git a/docs/documenting-the-api.md b/docs/documenting-the-api.md deleted file mode 100644 index 7eac6bbe..00000000 --- a/docs/documenting-the-api.md +++ /dev/null @@ -1,113 +0,0 @@ -# Documenting the API - -## Principles - -- The documentation is generated by [swaggo/swag](https://github.com/swaggo/swag) from comments in the API code. - -- Documentation is written using [Declarative Comments Format](https://github.com/swaggo/swag#declarative-comments-format). - -- The documentation is generated in the `./api/v1` folder as `docs.go`. - -- [echo-swagger](https://github.com/swaggo/echo-swagger) is used to integrate with Echo framework and serve the documentation with [Swagger-UI](https://swagger.io/tools/swagger-ui/) at `http://memos.host:5230/api/index.html` - -## Updating the documentation - -1. Update or add API-related comments in the code. Make sure to follow the [Declarative Comments Format](https://github.com/swaggo/swag#declarative-comments-format): - - ```go - // signIn godoc - // - // @Summary Sign-in to memos. - // @Tags auth - // @Accept json - // @Produce json - // @Param body body SignIn true "Sign-in object" - // @Success 200 {object} store.User "User information" - // @Failure 400 {object} nil "Malformatted signin request" - // @Failure 401 {object} nil "Password login is deactivated | Incorrect login credentials, please try again" - // @Failure 403 {object} nil "User has been archived with username {username}" - // @Failure 500 {object} nil "Failed to find system setting | Failed to unmarshal system setting | Incorrect login credentials, please try again | Failed to generate tokens | Failed to create activity" - // @Router /api/v1/auth/signin [POST] - func (s *APIV1Service) signIn(c echo.Context) error { - ... - ``` - - > Sample from [api/v1/auth.go](https://github.com/usememos/memos/tree/main/api/v1/auth.go) - > You can check existing comments at [api/v1](https://github.com/usememos/memos/tree/main/api/v1) - -2. Run one of the following provided scripts: - - - Linux: `./scripts/gen-api-v1-docs.sh` (remember to `chmod +x` the script first) - - Windows: `./scripts/gen-api-v1-docs.ps1` - - > The scripts will install swag if needed (via go install), then run `swag fmt` and `swag init` commands. - -3. That's it! The documentation is updated. You can check it at `http://memos.host:5230/api/index.html` - -### Extra tips - -- If you reference a custom Go struct from outside the API file, use a relative definition, like `store.IdentityProvider`. This works because `./` is passed to swag at `--dir` argument. If swag can't resolve the reference, it will fail. - -- If the API grows or you need to reference some type from another location, remember to update ./scripts/gen-api-v1-docs.cfg file with the new paths. - -- It's possible to list multiple errors for the same code using enum-like structs, that will show a proper, spec-conformant model with all entries at Swagger-UI. The drawback is that this approach requires a major refactoring and will add a lot of boilerplate code, as there are inconsistencies between API methods error responses. - - ```go - type signInInternalServerError string - - const signInErrorFailedToFindSystemSetting signInInternalServerError = "Failed to find system setting" - const signInErrorFailedToUnmarshalSystemSetting signInInternalServerError = "Failed to unmarshal system setting" - const signInErrorIncorrectLoginCredentials signInInternalServerError = "Incorrect login credentials, please try again" - const signInErrorFailedToGenerateTokens signInInternalServerError = "Failed to generate tokens" - const signInErrorFailedToCreateActivity signInInternalServerError = "Failed to create activity" - - type signInUnauthorized string - - const signInErrorPasswordLoginDeactivated signInUnauthorized = "Password login is deactivated" - const signInErrorIncorrectCredentials signInUnauthorized = "Incorrect login credentials, please try again" - - // signIn godoc - // - // @Summary Sign-in to memos. - // @Tags auth - // @Accept json - // @Produce json - // @Param body body SignIn true "Sign-in object" - // @Success 200 {object} store.User "User information" - // @Failure 400 {object} nil "Malformatted signin request" - // @Failure 401 {object} signInUnauthorized - // @Failure 403 {object} nil "User has been archived with username {username}" - // @Failure 500 {object} signInInternalServerError - // @Router /api/v1/auth/signin [POST] - func (s *APIV1Service) signIn(c echo.Context) error { - ... - ``` - -### Step-by-step (no scripts) - -#### Required tools - -```bash -# Swag v1.8.12 or newer -# Also updates swag if needed -$ go install github.com/swaggo/swag/cmd/swag@latest -``` - -If `$HOME/go/bin` is not in your `PATH`, you can call `swag` directly at `$HOME/go/bin/swag`. - -#### Generate the documentation - -1. Run `swag fmt` to format the comments - - ```bash - swag fmt --dir ./api/v1 && go fmt - ``` - -2. Run `swag init` to generate the documentation - - ```bash - cd - swag init --output ./api/v1 --generalInfo ./api/v1/v1.go --dir ./,./api/v1 - ``` - -> If the API gets a new version, you'll need to add the file system path to swag's `--dir` parameter. diff --git a/docs/windows-service.md b/docs/windows-service.md deleted file mode 100644 index f0e98c87..00000000 --- a/docs/windows-service.md +++ /dev/null @@ -1,98 +0,0 @@ -# Installing memos as a service on Windows - -While memos first-class support is for Docker, you may also install memos as a Windows service. It will run under SYSTEM account and start automatically at system boot. - -❗ All service management methods requires admin privileges. Use [gsudo](https://gerardog.github.io/gsudo/docs/install), or open a new PowerShell terminal as admin: - -```powershell -Start-Process powershell -Verb RunAs -``` - -## Choose one of the following methods - -### 1. Using [NSSM](https://nssm.cc/download) - -NSSM is a lightweight service wrapper. - -You may put `nssm.exe` in the same directory as `memos.exe`, or add its directory to your system PATH. Prefer the latest 64-bit version of `nssm.exe`. - -```powershell -# Install memos as a service -nssm install memos "C:\path\to\memos.exe" --mode prod --port 5230 - -# Delay auto start -nssm set memos DisplayName "memos service" - -# Configure extra service parameters -nssm set memos Description "A lightweight, self-hosted memo hub. https://usememos.com/" - -# Delay auto start -nssm set memos Start SERVICE_DELAYED_AUTO_START - -# Edit service using NSSM GUI -nssm edit memos - -# Start the service -nssm start memos - -# Remove the service, if ever needed -nssm remove memos confirm -``` - -### 2. Using [WinSW](https://github.com/winsw/winsw) - -Find the latest release tag and download the asset `WinSW-net46x.exe`. Then, put it in the same directory as `memos.exe` and rename it to `memos-service.exe`. - -Now, in the same directory, create the service configuration file `memos-service.xml`: - -```xml - - memos - memos service - A lightweight, self-hosted memo hub. https://usememos.com/ - - %BASE%\memos.exe - --mode prod --port 5230 - true - - -``` - -Then, install the service: - -```powershell -# Install the service -.\memos-service.exe install - -# Start the service -.\memos-service.exe start - -# Remove the service, if ever needed -.\memos-service.exe uninstall -``` - -### Manage the service - -You may use the `net` command to manage the service: - -```powershell -net start memos -net stop memos -``` - -Also, by using one of the provided methods, the service will appear in the Windows Services Manager `services.msc`. - -## Notes - -- On Windows, memos store its data in the following directory: - - ```powershell - $env:ProgramData\memos - # Typically, this will resolve to C:\ProgramData\memos - ``` - - You may specify a custom directory by appending `--data ` to the service command line. - -- If the service fails to start, you should inspect the Windows Event Viewer `eventvwr.msc`. - -- Memos will be accessible at [http://localhost:5230](http://localhost:5230) by default. diff --git a/go.mod b/go.mod index 6f4b40b7..f389a6e5 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/gorilla/feeds v1.1.2 github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 github.com/improbable-eng/grpc-web v0.15.0 + github.com/joho/godotenv v1.5.1 github.com/labstack/echo/v4 v4.11.4 github.com/lib/pq v1.10.9 github.com/microcosm-cc/bluemonday v1.0.26 diff --git a/go.sum b/go.sum index 22095da4..9a62a269 100644 --- a/go.sum +++ b/go.sum @@ -249,6 +249,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= diff --git a/store/db/mysql/activity.go b/store/db/mysql/activity.go index 75e5aa3f..fd36be4e 100644 --- a/store/db/mysql/activity.go +++ b/store/db/mysql/activity.go @@ -24,7 +24,7 @@ func (d *DB) CreateActivity(ctx context.Context, create *store.Activity) (*store placeholder := []string{"?", "?", "?", "?"} args := []any{create.CreatorID, create.Type.String(), create.Level.String(), payloadString} - stmt := "INSERT INTO activity (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")" + stmt := "INSERT INTO `activity` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")" result, err := d.db.ExecContext(ctx, stmt, args...) if err != nil { return nil, errors.Wrap(err, "failed to execute statement") diff --git a/store/db/mysql/memo.go b/store/db/mysql/memo.go index ca4a7c48..c9d66a37 100644 --- a/store/db/mysql/memo.go +++ b/store/db/mysql/memo.go @@ -16,7 +16,7 @@ func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, e placeholder := []string{"?", "?", "?"} args := []any{create.CreatorID, create.Content, create.Visibility} - stmt := "INSERT INTO memo (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")" + stmt := "INSERT INTO `memo` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ")" result, err := d.db.ExecContext(ctx, stmt, args...) if err != nil { return nil, err @@ -89,12 +89,15 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo "UNIX_TIMESTAMP(`memo`.`created_ts`) AS `created_ts`", "UNIX_TIMESTAMP(`memo`.`updated_ts`) AS `updated_ts`", "`memo`.`row_status` AS `row_status`", - "`memo`.`content` AS `content`", "`memo`.`visibility` AS `visibility`", "`memo_organizer`.`pinned` AS `pinned`", "`memo_relation`.`related_memo_id` AS `parent_id`", } - query := "SELECT " + strings.Join(fields, ",\n") + " FROM `memo` LEFT JOIN `memo_organizer` ON `memo`.`id` = `memo_organizer`.`memo_id` AND `memo`.`creator_id` = `memo_organizer`.`user_id` LEFT JOIN `memo_relation` ON `memo`.`id` = `memo_relation`.`memo_id` AND `memo_relation`.`type` = \"COMMENT\" WHERE " + strings.Join(where, " AND ") + " HAVING " + strings.Join(having, " AND ") + " ORDER BY " + strings.Join(orders, ", ") + if !find.ExcludeContent { + fields = append(fields, "`memo`.`content` AS `content`") + } + + query := "SELECT " + strings.Join(fields, ", ") + " FROM `memo` LEFT JOIN `memo_organizer` ON `memo`.`id` = `memo_organizer`.`memo_id` AND `memo`.`creator_id` = `memo_organizer`.`user_id` LEFT JOIN `memo_relation` ON `memo`.`id` = `memo_relation`.`memo_id` AND `memo_relation`.`type` = \"COMMENT\" WHERE " + strings.Join(where, " AND ") + " HAVING " + strings.Join(having, " AND ") + " ORDER BY " + strings.Join(orders, ", ") if find.Limit != nil { query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit) if find.Offset != nil { @@ -112,17 +115,20 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo for rows.Next() { var memo store.Memo pinned := sql.NullBool{} - if err := rows.Scan( + dests := []any{ &memo.ID, &memo.CreatorID, &memo.CreatedTs, &memo.UpdatedTs, &memo.RowStatus, - &memo.Content, &memo.Visibility, &pinned, &memo.ParentID, - ); err != nil { + } + if !find.ExcludeContent { + dests = append(dests, &memo.Content) + } + if err := rows.Scan(dests...); err != nil { return nil, err } if pinned.Valid { diff --git a/store/db/mysql/migration/dev/LATEST__SCHEMA.sql b/store/db/mysql/migration/dev/LATEST__SCHEMA.sql index 9a29e8a5..2ff4b65e 100644 --- a/store/db/mysql/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/mysql/migration/dev/LATEST__SCHEMA.sql @@ -1,19 +1,3 @@ --- drop all tables first -DROP TABLE IF EXISTS `migration_history`; -DROP TABLE IF EXISTS `system_setting`; -DROP TABLE IF EXISTS `user`; -DROP TABLE IF EXISTS `user_setting`; -DROP TABLE IF EXISTS `memo`; -DROP TABLE IF EXISTS `memo_organizer`; -DROP TABLE IF EXISTS `memo_relation`; -DROP TABLE IF EXISTS `resource`; -DROP TABLE IF EXISTS `tag`; -DROP TABLE IF EXISTS `activity`; -DROP TABLE IF EXISTS `storage`; -DROP TABLE IF EXISTS `idp`; -DROP TABLE IF EXISTS `inbox`; -DROP TABLE IF EXISTS `webhook`; - -- migration_history CREATE TABLE `migration_history` ( `version` VARCHAR(256) NOT NULL PRIMARY KEY, diff --git a/store/db/mysql/migration/prod/LATEST__SCHEMA.sql b/store/db/mysql/migration/prod/LATEST__SCHEMA.sql index 9a29e8a5..2ff4b65e 100644 --- a/store/db/mysql/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/mysql/migration/prod/LATEST__SCHEMA.sql @@ -1,19 +1,3 @@ --- drop all tables first -DROP TABLE IF EXISTS `migration_history`; -DROP TABLE IF EXISTS `system_setting`; -DROP TABLE IF EXISTS `user`; -DROP TABLE IF EXISTS `user_setting`; -DROP TABLE IF EXISTS `memo`; -DROP TABLE IF EXISTS `memo_organizer`; -DROP TABLE IF EXISTS `memo_relation`; -DROP TABLE IF EXISTS `resource`; -DROP TABLE IF EXISTS `tag`; -DROP TABLE IF EXISTS `activity`; -DROP TABLE IF EXISTS `storage`; -DROP TABLE IF EXISTS `idp`; -DROP TABLE IF EXISTS `inbox`; -DROP TABLE IF EXISTS `webhook`; - -- migration_history CREATE TABLE `migration_history` ( `version` VARCHAR(256) NOT NULL PRIMARY KEY, diff --git a/store/db/mysql/migration_history.go b/store/db/mysql/migration_history.go index 0f7bd1a7..bc6c89fc 100644 --- a/store/db/mysql/migration_history.go +++ b/store/db/mysql/migration_history.go @@ -49,6 +49,5 @@ func (d *DB) UpsertMigrationHistory(ctx context.Context, upsert *store.UpsertMig ); err != nil { return nil, err } - return &migrationHistory, nil } diff --git a/store/db/mysql/migrator.go b/store/db/mysql/migrator.go index 94bd540c..0a9d88ad 100644 --- a/store/db/mysql/migrator.go +++ b/store/db/mysql/migrator.go @@ -15,13 +15,13 @@ import ( "github.com/usememos/memos/store" ) +//go:embed migration +var migrationFS embed.FS + const ( latestSchemaFileName = "LATEST__SCHEMA.sql" ) -//go:embed migration -var migrationFS embed.FS - func (d *DB) Migrate(ctx context.Context) error { if d.profile.IsDev() { return d.nonProdMigrate(ctx) @@ -54,8 +54,6 @@ func (d *DB) nonProdMigrate(ctx context.Context) error { return nil } - println("no tables in the database. start migration") - buf, err := migrationFS.ReadFile("migration/dev/" + latestSchemaFileName) if err != nil { return errors.Errorf("failed to read latest schema file: %s", err) @@ -65,13 +63,6 @@ func (d *DB) nonProdMigrate(ctx context.Context) error { if _, err := d.db.ExecContext(ctx, stmt); err != nil { return errors.Errorf("failed to exec SQL %s: %s", stmt, err) } - - // In demo mode, we should seed the database. - if d.profile.Mode == "demo" { - if err := d.seed(ctx); err != nil { - return errors.Wrap(err, "failed to seed") - } - } return nil } @@ -84,10 +75,8 @@ func (d *DB) prodMigrate(ctx context.Context) error { 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.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, @@ -107,11 +96,11 @@ func (d *DB) prodMigrate(ctx context.Context) error { return nil } - println("start migrate") + println("start to migrate database schema") for _, minorVersion := range getMinorVersionList() { normalizedVersion := minorVersion + ".0" if version.IsVersionGreaterThan(normalizedVersion, latestMigrationHistoryVersion) && version.IsVersionGreaterOrEqualThan(currentVersion, normalizedVersion) { - println("applying migration for", normalizedVersion) + println("applying migration of", normalizedVersion) if err := d.applyMigrationForMinorVersion(ctx, minorVersion); err != nil { return errors.Wrap(err, "failed to apply minor version migration") } @@ -153,35 +142,6 @@ func (d *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion str return nil } -//go:embed seed -var seedFS embed.FS - -func (d *DB) seed(ctx context.Context) error { - filenames, err := fs.Glob(seedFS, "seed/*.sql") - 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 { - buf, err := seedFS.ReadFile(filename) - if err != nil { - return errors.Wrapf(err, "failed to read seed 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, "seed error: %s", stmt) - } - } - } - return nil -} - // minorDirRegexp is a regular expression for minor version directory. var minorDirRegexp = regexp.MustCompile(`^migration/prod/[0-9]+\.[0-9]+$`) diff --git a/store/db/mysql/seed/10000__reset.sql b/store/db/mysql/seed/10000__reset.sql deleted file mode 100644 index de4e97c9..00000000 --- a/store/db/mysql/seed/10000__reset.sql +++ /dev/null @@ -1,4 +0,0 @@ -TRUNCATE TABLE memo_organizer; -TRUNCATE TABLE resource; -TRUNCATE TABLE memo; -TRUNCATE TABLE user; diff --git a/store/db/mysql/webhook.go b/store/db/mysql/webhook.go index 20f58970..37082df2 100644 --- a/store/db/mysql/webhook.go +++ b/store/db/mysql/webhook.go @@ -25,7 +25,7 @@ func (d *DB) CreateWebhook(ctx context.Context, create *storepb.Webhook) (*store } create.Id = int32(id) - return create, nil + return d.GetWebhook(ctx, &store.FindWebhook{ID: &create.Id}) } func (d *DB) ListWebhooks(ctx context.Context, find *store.FindWebhook) ([]*storepb.Webhook, error) { @@ -37,7 +37,7 @@ func (d *DB) ListWebhooks(ctx context.Context, find *store.FindWebhook) ([]*stor where, args = append(where, "`creator_id` = ?"), append(args, *find.CreatorID) } - rows, err := d.db.QueryContext(ctx, "SELECT `id`, `created_ts`, `updated_ts`, `row_status`, `creator_id`, `name`, `url` FROM `webhook` WHERE "+strings.Join(where, " AND ")+" ORDER BY `id` DESC", + rows, err := d.db.QueryContext(ctx, "SELECT `id`, UNIX_TIMESTAMP(`created_ts`), UNIX_TIMESTAMP(`updated_ts`), `row_status`, `creator_id`, `name`, `url` FROM `webhook` WHERE "+strings.Join(where, " AND ")+" ORDER BY `id` DESC", args..., ) if err != nil { diff --git a/store/db/postgres/memo.go b/store/db/postgres/memo.go index 6359e078..52f4c7e1 100644 --- a/store/db/postgres/memo.go +++ b/store/db/postgres/memo.go @@ -181,7 +181,6 @@ func (d *DB) UpdateMemo(ctx context.Context, update *store.UpdateMemo) error { func (d *DB) DeleteMemo(ctx context.Context, delete *store.DeleteMemo) error { where, args := []string{"id = " + placeholder(1)}, []any{delete.ID} stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ") - println("stmt", stmt, delete.ID) result, err := d.db.ExecContext(ctx, stmt, args...) if err != nil { return errors.Wrap(err, "failed to delete memo") diff --git a/store/db/postgres/migration/dev/LATEST__SCHEMA.sql b/store/db/postgres/migration/dev/LATEST__SCHEMA.sql index 8d62b0e2..8c414fd1 100644 --- a/store/db/postgres/migration/dev/LATEST__SCHEMA.sql +++ b/store/db/postgres/migration/dev/LATEST__SCHEMA.sql @@ -1,19 +1,3 @@ --- drop all tables first (PostgreSQL style) -DROP TABLE IF EXISTS migration_history CASCADE; -DROP TABLE IF EXISTS system_setting CASCADE; -DROP TABLE IF EXISTS "user" CASCADE; -DROP TABLE IF EXISTS user_setting CASCADE; -DROP TABLE IF EXISTS memo CASCADE; -DROP TABLE IF EXISTS memo_organizer CASCADE; -DROP TABLE IF EXISTS memo_relation CASCADE; -DROP TABLE IF EXISTS resource CASCADE; -DROP TABLE IF EXISTS tag CASCADE; -DROP TABLE IF EXISTS activity CASCADE; -DROP TABLE IF EXISTS storage CASCADE; -DROP TABLE IF EXISTS idp CASCADE; -DROP TABLE IF EXISTS inbox CASCADE; -DROP TABLE IF EXISTS webhook CASCADE; - -- migration_history CREATE TABLE migration_history ( version TEXT NOT NULL PRIMARY KEY, diff --git a/store/db/postgres/migration/prod/LATEST__SCHEMA.sql b/store/db/postgres/migration/prod/LATEST__SCHEMA.sql index 8d62b0e2..8c414fd1 100644 --- a/store/db/postgres/migration/prod/LATEST__SCHEMA.sql +++ b/store/db/postgres/migration/prod/LATEST__SCHEMA.sql @@ -1,19 +1,3 @@ --- drop all tables first (PostgreSQL style) -DROP TABLE IF EXISTS migration_history CASCADE; -DROP TABLE IF EXISTS system_setting CASCADE; -DROP TABLE IF EXISTS "user" CASCADE; -DROP TABLE IF EXISTS user_setting CASCADE; -DROP TABLE IF EXISTS memo CASCADE; -DROP TABLE IF EXISTS memo_organizer CASCADE; -DROP TABLE IF EXISTS memo_relation CASCADE; -DROP TABLE IF EXISTS resource CASCADE; -DROP TABLE IF EXISTS tag CASCADE; -DROP TABLE IF EXISTS activity CASCADE; -DROP TABLE IF EXISTS storage CASCADE; -DROP TABLE IF EXISTS idp CASCADE; -DROP TABLE IF EXISTS inbox CASCADE; -DROP TABLE IF EXISTS webhook CASCADE; - -- migration_history CREATE TABLE migration_history ( version TEXT NOT NULL PRIMARY KEY, diff --git a/store/db/postgres/migrator.go b/store/db/postgres/migrator.go index d94a56f2..4c74d4b6 100644 --- a/store/db/postgres/migrator.go +++ b/store/db/postgres/migrator.go @@ -15,13 +15,13 @@ import ( "github.com/usememos/memos/store" ) +//go:embed migration +var migrationFS embed.FS + const ( latestSchemaFileName = "LATEST__SCHEMA.sql" ) -//go:embed migration -var migrationFS embed.FS - func (d *DB) Migrate(ctx context.Context) error { if d.profile.IsDev() { return d.nonProdMigrate(ctx) @@ -54,8 +54,6 @@ func (d *DB) nonProdMigrate(ctx context.Context) error { return nil } - println("no tables in the database. start migration") - buf, err := migrationFS.ReadFile("migration/dev/" + latestSchemaFileName) if err != nil { return errors.Errorf("failed to read latest schema file: %s", err) @@ -66,12 +64,6 @@ func (d *DB) nonProdMigrate(ctx context.Context) error { return errors.Errorf("failed to exec SQL %s: %s", stmt, err) } - // In demo mode, we should seed the database. - if d.profile.Mode == "demo" { - if err := d.seed(ctx); err != nil { - return errors.Wrap(err, "failed to seed") - } - } return nil } @@ -153,35 +145,6 @@ func (d *DB) applyMigrationForMinorVersion(ctx context.Context, minorVersion str return nil } -//go:embed seed -var seedFS embed.FS - -func (d *DB) seed(ctx context.Context) error { - filenames, err := fs.Glob(seedFS, "seed/*.sql") - 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 { - buf, err := seedFS.ReadFile(filename) - if err != nil { - return errors.Wrapf(err, "failed to read seed 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, "seed error: %s", stmt) - } - } - } - return nil -} - // minorDirRegexp is a regular expression for minor version directory. var minorDirRegexp = regexp.MustCompile(`^migration/prod/[0-9]+\.[0-9]+$`) diff --git a/store/db/postgres/seed/10000__reset.sql b/store/db/postgres/seed/10000__reset.sql deleted file mode 100644 index aa60072e..00000000 --- a/store/db/postgres/seed/10000__reset.sql +++ /dev/null @@ -1,4 +0,0 @@ -TRUNCATE TABLE memo_organizer; -TRUNCATE TABLE resource; -TRUNCATE TABLE memo; -TRUNCATE TABLE "user"; diff --git a/store/migrator.go b/store/migrator.go index 9b9995af..003734e6 100644 --- a/store/migrator.go +++ b/store/migrator.go @@ -49,7 +49,7 @@ func (s *Store) MigrateResourceInternalPath(ctx context.Context) error { migratedCount++ } - if migratedCount > 0 { + if migratedCount > 0 && s.Profile.Mode == "prod" { log.Info(fmt.Sprintf("migrated %d local resource paths in %s", migratedCount, time.Since(migrateStartTime))) } return nil diff --git a/test/store/store.go b/test/store/store.go index e05af5f0..d3133cdd 100644 --- a/test/store/store.go +++ b/test/store/store.go @@ -30,8 +30,42 @@ func NewTestingStore(ctx context.Context, t *testing.T) *store.Store { } func resetTestingDB(ctx context.Context, profile *profile.Profile, dbDriver store.Driver) { - if profile.Driver == "postgres" { - _, err := dbDriver.GetDB().ExecContext(ctx, `DROP SCHEMA public CASCADE; CREATE SCHEMA public;`) + if profile.Driver == "mysql" { + _, err := dbDriver.GetDB().ExecContext(ctx, ` + DROP TABLE IF EXISTS migration_history; + DROP TABLE IF EXISTS system_setting; + DROP TABLE IF EXISTS user; + DROP TABLE IF EXISTS user_setting; + DROP TABLE IF EXISTS memo; + DROP TABLE IF EXISTS memo_organizer; + DROP TABLE IF EXISTS memo_relation; + DROP TABLE IF EXISTS resource; + DROP TABLE IF EXISTS tag; + DROP TABLE IF EXISTS activity; + DROP TABLE IF EXISTS storage; + DROP TABLE IF EXISTS idp; + DROP TABLE IF EXISTS inbox; + DROP TABLE IF EXISTS webhook;`) + if err != nil { + fmt.Printf("failed to reset testing db, error: %+v\n", err) + panic(err) + } + } else if profile.Driver == "postgres" { + _, err := dbDriver.GetDB().ExecContext(ctx, ` + DROP TABLE IF EXISTS migration_history CASCADE; + DROP TABLE IF EXISTS system_setting CASCADE; + DROP TABLE IF EXISTS "user" CASCADE; + DROP TABLE IF EXISTS user_setting CASCADE; + DROP TABLE IF EXISTS memo CASCADE; + DROP TABLE IF EXISTS memo_organizer CASCADE; + DROP TABLE IF EXISTS memo_relation CASCADE; + DROP TABLE IF EXISTS resource CASCADE; + DROP TABLE IF EXISTS tag CASCADE; + DROP TABLE IF EXISTS activity CASCADE; + DROP TABLE IF EXISTS storage CASCADE; + DROP TABLE IF EXISTS idp CASCADE; + DROP TABLE IF EXISTS inbox CASCADE; + DROP TABLE IF EXISTS webhook CASCADE;`) if err != nil { fmt.Printf("failed to reset testing db, error: %+v\n", err) panic(err) diff --git a/test/store/store_test.go b/test/store/store_test.go deleted file mode 100644 index 47e99ce6..00000000 --- a/test/store/store_test.go +++ /dev/null @@ -1 +0,0 @@ -package teststore diff --git a/test/test.go b/test/test.go index 9738c138..655ba0e8 100644 --- a/test/test.go +++ b/test/test.go @@ -6,6 +6,8 @@ import ( "os" "testing" + "github.com/joho/godotenv" + "github.com/usememos/memos/server/profile" "github.com/usememos/memos/server/version" ) @@ -24,6 +26,10 @@ func getUnusedPort() int { } func GetTestingProfile(t *testing.T) *profile.Profile { + if err := godotenv.Load(".env"); err != nil { + t.Fatal("failed to load .env file", err) + } + // Get a temporary directory for the test data. dir := t.TempDir() mode := "dev"