chore: update database migrator

This commit is contained in:
Steven 2024-01-06 16:55:13 +08:00
parent fd8333eeda
commit d67eaaaee2
21 changed files with 71 additions and 385 deletions

View file

@ -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 <project-root>
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.

View file

@ -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
<service>
<id>memos</id>
<name>memos service</name>
<description>A lightweight, self-hosted memo hub. https://usememos.com/</description>
<onfailure action="restart" delay="10 sec"/>
<executable>%BASE%\memos.exe</executable>
<arguments>--mode prod --port 5230</arguments>
<delayedAutoStart>true</delayedAutoStart>
<log mode="none" />
</service>
```
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 <path>` 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.

1
go.mod
View file

@ -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

2
go.sum
View file

@ -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=

View file

@ -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")

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -49,6 +49,5 @@ func (d *DB) UpsertMigrationHistory(ctx context.Context, upsert *store.UpsertMig
); err != nil {
return nil, err
}
return &migrationHistory, nil
}

View file

@ -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]+$`)

View file

@ -1,4 +0,0 @@
TRUNCATE TABLE memo_organizer;
TRUNCATE TABLE resource;
TRUNCATE TABLE memo;
TRUNCATE TABLE user;

View file

@ -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 {

View file

@ -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")

View file

@ -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,

View file

@ -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,

View file

@ -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]+$`)

View file

@ -1,4 +0,0 @@
TRUNCATE TABLE memo_organizer;
TRUNCATE TABLE resource;
TRUNCATE TABLE memo;
TRUNCATE TABLE "user";

View file

@ -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

View file

@ -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)

View file

@ -1 +0,0 @@
package teststore

View file

@ -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"