From 5912029c1b3cbbaccd235efbeb2387c2842317ee Mon Sep 17 00:00:00 2001 From: Sung Date: Sun, 5 Oct 2025 11:04:34 -0700 Subject: [PATCH] Simplify --- .gitignore | 1 + SELF_HOSTING.md | 52 +++++------- host/docker/compose.yml | 20 +---- host/docker/entrypoint.sh | 23 +----- host/smoketest/testsuite.sh | 2 +- pkg/e2e/server_test.go | 114 ++++++++++++++++++++++++++ pkg/server/.env.dev | 2 +- pkg/server/.env.test | 2 +- pkg/server/app/testutils.go | 2 +- pkg/server/app/users.go | 20 ----- pkg/server/config/config.go | 45 +++++----- pkg/server/config/config_test.go | 16 +--- pkg/server/database/consts.go | 2 - pkg/server/database/database.go | 6 +- pkg/server/database/models.go | 15 ---- pkg/server/job/job.go | 127 ----------------------------- pkg/server/job/job_test.go | 105 ------------------------ pkg/server/main.go | 31 ++----- pkg/server/middleware/auth_test.go | 4 +- pkg/server/middleware/limit.go | 2 +- pkg/server/testutils/main.go | 8 +- pkg/server/token/token_test.go | 2 +- 22 files changed, 184 insertions(+), 417 deletions(-) create mode 100644 pkg/e2e/server_test.go delete mode 100644 pkg/server/job/job.go delete mode 100644 pkg/server/job/job_test.go diff --git a/.gitignore b/.gitignore index f1abba6b..57d82ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ node_modules /test tmp *.db +server diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md index 1c080bed..5d3db690 100644 --- a/SELF_HOSTING.md +++ b/SELF_HOSTING.md @@ -4,45 +4,30 @@ This guide documents the steps for installing the Dnote server on your own machi ## Overview -Dnote server comes as a single binary file that you can simply download and run. It uses Postgres as the database. +Dnote server comes as a single binary file that you can simply download and run. It uses SQLite as the database. ## Installation -1. Install Postgres 11+. -2. Create a `dnote` database by running `createdb dnote` -3. Download the official Dnote server release from the [release page](https://github.com/dnote/dnote/releases). -4. Extract the archive and move the `dnote-server` executable to `/usr/local/bin`. +1. Download the official Dnote server release from the [release page](https://github.com/dnote/dnote/releases). +2. Extract the archive and move the `dnote-server` executable to `/usr/local/bin`. ```bash tar -xzf dnote-server-$version-$os.tar.gz mv ./dnote-server /usr/local/bin ``` -4. Run Dnote +3. Run Dnote ```bash -GO_ENV=PRODUCTION \ -DBHost=localhost \ -DBPort=5432 \ -DBName=dnote \ -DBUser=$user \ -DBPassword=$password \ +APP_ENV=PRODUCTION \ WebURL=$webURL \ -SmtpHost=$SmtpHost \ -SmtpPort=$SmtpPort \ -SmtpUsername=$SmtpUsername \ -SmtpPassword=$SmtpPassword \ DisableRegistration=false \ dnote-server start ``` -Replace `$user`, `$password` with the credentials of the Postgres user that owns the `dnote` database. - Replace `$webURL` with the full URL to your server, without a trailing slash (e.g. `https://your.server`). -Replace `$SmtpHost`, `SmtpPort`, `$SmtpUsername`, `$SmtpPassword` with actual values, if you would like to receive spaced repetition through email. - -Replace `DisableRegistration` to `true` if you would like to disable user registrations. +Set `DisableRegistration=true` if you would like to disable user registrations. By default, dnote server will run on the port 3000. @@ -127,25 +112,32 @@ Restart=always RestartSec=3 WorkingDirectory=/home/$user ExecStart=/usr/local/bin/dnote-server start -Environment=GO_ENV=PRODUCTION +Environment=APP_ENV=PRODUCTION Environment=WebURL=$WebURL -Environment=SmtpHost= -Environment=SmtpPort= -Environment=SmtpUsername= -Environment=SmtpPassword= [Install] WantedBy=multi-user.target ``` -Replace `$user`, `$WebURL`, `$DBUser`, and `$DBPassword` with the actual values. +Replace `$user` and `$WebURL` with the actual values. -Optionally, if you would like to send spaced repetitions throught email, populate `SmtpHost`, `SmtpPort`, `SmtpUsername`, and `SmtpPassword`. +By default, the database will be stored at `$XDG_DATA_HOME/dnote/server.db` (typically `~/.local/share/dnote/server.db`). To use a custom location, add `Environment=DBPath=/path/to/database.db` to the service file. 2. Reload the change by running `sudo systemctl daemon-reload`. 3. Enable the Daemon by running `sudo systemctl enable dnote`.` 4. Start the Daemon by running `sudo systemctl start dnote` +### Optional: Email Support + +To enable sending emails, add the following environment variables to your configuration. But they are not required. + +- `SmtpHost` - SMTP server hostname +- `SmtpPort` - SMTP server port +- `SmtpUsername` - SMTP username +- `SmtpPassword` - SMTP password + +For systemd, add these as additional `Environment=` lines in `/etc/systemd/system/dnote.service`. + ### Configure clients Let's configure Dnote clients to connect to the self-hosted web API endpoint. @@ -169,7 +161,3 @@ e.g. editor: nvim apiEndpoint: my-dnote-server.com/api ``` - -#### Browser extension - -Navigate into the 'Settings' tab and set the values for 'API URL', and 'Web URL'. diff --git a/host/docker/compose.yml b/host/docker/compose.yml index 8860084a..d2c2e5ed 100644 --- a/host/docker/compose.yml +++ b/host/docker/compose.yml @@ -1,28 +1,14 @@ version: "3" services: - postgres: - image: postgres:14-alpine - environment: - POSTGRES_USER: dnote - POSTGRES_PASSWORD: dnote - POSTGRES_DB: dnote - volumes: - - ./dnote_data:/var/lib/postgresql/data - restart: always - dnote: image: dnote/dnote:latest environment: - GO_ENV: PRODUCTION + APP_ENV: PRODUCTION WebURL: localhost:3000 - SmtpHost: - SmtpPort: - SmtpUsername: - SmtpPassword: DisableRegistration: "false" ports: - 3000:3000 - depends_on: - - postgres + volumes: + - ./dnote_data:/data restart: always diff --git a/host/docker/entrypoint.sh b/host/docker/entrypoint.sh index 8fb62de9..0fee185c 100755 --- a/host/docker/entrypoint.sh +++ b/host/docker/entrypoint.sh @@ -1,25 +1,6 @@ #!/bin/sh -wait_for_db() { - HOST=${DBHost:-postgres} - PORT=${DBPort:-5432} - echo "Waiting for the database connection..." - - attempts=0 - max_attempts=10 - while [ $attempts -lt $max_attempts ]; do - nc -z "${HOST}" "${PORT}" 2>/dev/null && break - echo "Waiting for db at ${HOST}:${PORT}..." - sleep 5 - attempts=$((attempts+1)) - done - - if [ $attempts -eq $max_attempts ]; then - echo "Timed out while waiting for db at ${HOST}:${PORT}" - exit 1 - fi -} - -wait_for_db +# Set default DBPath to /data if not specified +export DBPath=${DBPath:-/data/dnote.db} exec "$@" diff --git a/host/smoketest/testsuite.sh b/host/smoketest/testsuite.sh index 4fa05af3..a32ad09f 100755 --- a/host/smoketest/testsuite.sh +++ b/host/smoketest/testsuite.sh @@ -14,7 +14,7 @@ cd /vagrant tar -xvf dnote_server_integration_test_linux_amd64.tar.gz -GO_ENV=PRODUCTION \ +APP_ENV=PRODUCTION \ DBHost=localhost \ DBPort=5432 \ DBName=dnote \ diff --git a/pkg/e2e/server_test.go b/pkg/e2e/server_test.go new file mode 100644 index 00000000..3891cdb9 --- /dev/null +++ b/pkg/e2e/server_test.go @@ -0,0 +1,114 @@ +/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors + * + * This file is part of Dnote. + * + * Dnote is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Dnote is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Dnote. If not, see . + */ + +package main + +import ( + "fmt" + "net/http" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/dnote/dnote/pkg/assert" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func TestServerStart(t *testing.T) { + tmpDB := t.TempDir() + "/test.db" + port := "3456" // Use non-standard port to avoid conflicts + + // Start server in background + cmd := exec.Command("go", "run", "-tags", "fts5", "../server", "-port", port, "start") + cmd.Env = append(os.Environ(), + "DBPath="+tmpDB, + "WebURL=http://localhost:"+port, + "APP_ENV=PRODUCTION", + ) + // Capture output for debugging + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Start(); err != nil { + t.Fatalf("failed to start server: %v", err) + } + defer func() { + if cmd.Process != nil { + cmd.Process.Kill() + } + }() + + // Wait for server to start and migrations to run + time.Sleep(3 * time.Second) + + // Verify server responds to health check + resp, err := http.Get(fmt.Sprintf("http://localhost:%s/health", port)) + if err != nil { + t.Fatalf("failed to reach server health endpoint: %v", err) + } + defer resp.Body.Close() + + assert.Equal(t, resp.StatusCode, 200, "health endpoint should return 200") + + // Kill server before checking database to avoid locks + if cmd.Process != nil { + cmd.Process.Kill() + cmd.Wait() // Clean up zombie process + } + + // Verify database file was created + if _, err := os.Stat(tmpDB); os.IsNotExist(err) { + t.Fatalf("database file was not created at %s", tmpDB) + } + + // Verify migrations ran by checking database + db, err := gorm.Open(sqlite.Open(tmpDB), &gorm.Config{}) + if err != nil { + t.Fatalf("failed to open test database: %v", err) + } + + // Verify migrations ran + var count int64 + if err := db.Raw("SELECT COUNT(*) FROM schema_migrations").Scan(&count).Error; err != nil { + t.Fatalf("schema_migrations table not found: %v", err) + } + if count == 0 { + t.Fatal("no migrations were run") + } + + // Verify FTS table exists and is functional + if err := db.Exec("SELECT * FROM notes_fts LIMIT 1").Error; err != nil { + t.Fatalf("notes_fts table not found or not functional: %v", err) + } +} + +func TestServerVersion(t *testing.T) { + cmd := exec.Command("go", "run", "-tags", "fts5", "../server", "version") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("version command failed: %v", err) + } + + outputStr := string(output) + if !strings.Contains(outputStr, "dnote-server-") { + t.Errorf("expected version output to contain 'dnote-server-', got: %s", outputStr) + } +} diff --git a/pkg/server/.env.dev b/pkg/server/.env.dev index b7268daa..334b1196 100644 --- a/pkg/server/.env.dev +++ b/pkg/server/.env.dev @@ -1,4 +1,4 @@ -GO_ENV=DEVELOPMENT +APP_ENV=DEVELOPMENT SmtpUsername=mock-SmtpUsername SmtpPassword=mock-SmtpPassword diff --git a/pkg/server/.env.test b/pkg/server/.env.test index 8ab39d3d..d633f83c 100644 --- a/pkg/server/.env.test +++ b/pkg/server/.env.test @@ -1,4 +1,4 @@ -GO_ENV=TEST +APP_ENV=TEST SmtpUsername=mock-SmtpUsername SmtpPassword=mock-SmtpPassword diff --git a/pkg/server/app/testutils.go b/pkg/server/app/testutils.go index fc0be26b..d31f8918 100644 --- a/pkg/server/app/testutils.go +++ b/pkg/server/app/testutils.go @@ -35,7 +35,7 @@ func NewTest(appParams *App) App { WebURL: "http://127.0.0.0.1", Port: "3000", DisableRegistration: false, - DB: config.LoadDBConfig(), + DBPath: config.LoadDBPath(), AssetBaseURL: "", HTTP500Page: assets.MustGetHTTP500ErrorPage(), } diff --git a/pkg/server/app/users.go b/pkg/server/app/users.go index 707fcd42..5c9d7f1f 100644 --- a/pkg/server/app/users.go +++ b/pkg/server/app/users.go @@ -24,7 +24,6 @@ import ( "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/log" - "github.com/dnote/dnote/pkg/server/token" pkgErrors "github.com/pkg/errors" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" @@ -40,17 +39,6 @@ func (a *App) TouchLastLoginAt(user database.User, tx *gorm.DB) error { return nil } -func createEmailPreference(user database.User, tx *gorm.DB) error { - p := database.EmailPreference{ - UserID: user.ID, - } - if err := tx.Save(&p).Error; err != nil { - return pkgErrors.Wrap(err, "inserting email preference") - } - - return nil -} - // CreateUser creates a user func (a *App) CreateUser(email, password string, passwordConfirmation string) (database.User, error) { if email == "" { @@ -104,14 +92,6 @@ func (a *App) CreateUser(email, password string, passwordConfirmation string) (d return database.User{}, pkgErrors.Wrap(err, "saving account") } - if _, err := token.Create(tx, user.ID, database.TokenTypeEmailPreference); err != nil { - tx.Rollback() - return database.User{}, pkgErrors.Wrap(err, "creating email verificaiton token") - } - if err := createEmailPreference(user, tx); err != nil { - tx.Rollback() - return database.User{}, pkgErrors.Wrap(err, "creating email preference") - } if err := a.TouchLastLoginAt(user, tx); err != nil { tx.Rollback() return database.User{}, pkgErrors.Wrap(err, "updating last login") diff --git a/pkg/server/config/config.go b/pkg/server/config/config.go index 56947483..a9a4b7de 100644 --- a/pkg/server/config/config.go +++ b/pkg/server/config/config.go @@ -19,18 +19,24 @@ package config import ( + "fmt" "net/url" "os" "path/filepath" "github.com/dnote/dnote/pkg/dirs" "github.com/dnote/dnote/pkg/server/assets" + "github.com/dnote/dnote/pkg/server/log" "github.com/pkg/errors" ) const ( // AppEnvProduction represents an app environment for production. AppEnvProduction string = "PRODUCTION" + // DefaultDBDir is the default directory name for Dnote data + DefaultDBDir = "dnote" + // DefaultDBFilename is the default database filename + DefaultDBFilename = "server.db" ) var ( @@ -42,11 +48,6 @@ var ( ErrPortInvalid = errors.New("Invalid Port") ) -// DBConfig holds the database connection configuration. -type DBConfig struct { - Path string -} - func readBoolEnv(name string) bool { if os.Getenv(name) == "true" { return true @@ -55,15 +56,13 @@ func readBoolEnv(name string) bool { return false } -func LoadDBConfig() DBConfig { +func LoadDBPath() string { path := os.Getenv("DBPath") if path == "" { - path = filepath.Join(dirs.DataHome, "dnote", "server.db") + path = filepath.Join(dirs.DataHome, DefaultDBDir, DefaultDBFilename) } - return DBConfig{ - Path: path, - } + return path } // Config is an application configuration @@ -72,14 +71,25 @@ type Config struct { WebURL string DisableRegistration bool Port string - DB DBConfig + DBPath string AssetBaseURL string HTTP500Page []byte } +func getDeprecatedEnvVar(deprecated, current string) string { + val := os.Getenv(deprecated) + if val != "" { + log.WithFields(log.Fields{ + "deprecated": deprecated, + "current": current, + }).Warn(fmt.Sprintf("%s is deprecated. Please use %s instead.", deprecated, current)) + return val + } + return "" +} + func getAppEnv() string { - // DEPRECATED - goEnv := os.Getenv("GO_ENV") + goEnv := getDeprecatedEnvVar("GO_ENV", "APP_ENV") if goEnv != "" { return goEnv } @@ -87,9 +97,6 @@ func getAppEnv() string { return os.Getenv("APP_ENV") } -func checkDeprecatedEnvVars() { -} - // Load constructs and returns a new config based on the environment variables. func Load() Config { port := os.Getenv("PORT") @@ -97,14 +104,12 @@ func Load() Config { port = "3000" } - checkDeprecatedEnvVars() - c := Config{ AppEnv: getAppEnv(), WebURL: os.Getenv("WebURL"), Port: port, DisableRegistration: readBoolEnv("DisableRegistration"), - DB: LoadDBConfig(), + DBPath: LoadDBPath(), AssetBaseURL: "", HTTP500Page: assets.MustGetHTTP500ErrorPage(), } @@ -134,7 +139,7 @@ func validate(c Config) error { return ErrPortInvalid } - if c.DB.Path == "" { + if c.DBPath == "" { return ErrDBMissingPath } diff --git a/pkg/server/config/config_test.go b/pkg/server/config/config_test.go index 96ab3a55..802a3add 100644 --- a/pkg/server/config/config_test.go +++ b/pkg/server/config/config_test.go @@ -33,9 +33,7 @@ func TestValidate(t *testing.T) { }{ { config: Config{ - DB: DBConfig{ - Path: "test.db", - }, + DBPath: "test.db", WebURL: "http://mock.url", Port: "3000", }, @@ -43,9 +41,7 @@ func TestValidate(t *testing.T) { }, { config: Config{ - DB: DBConfig{ - Path: "", - }, + DBPath: "", WebURL: "http://mock.url", Port: "3000", }, @@ -53,17 +49,13 @@ func TestValidate(t *testing.T) { }, { config: Config{ - DB: DBConfig{ - Path: "test.db", - }, + DBPath: "test.db", }, expectedErr: ErrWebURLInvalid, }, { config: Config{ - DB: DBConfig{ - Path: "test.db", - }, + DBPath: "test.db", WebURL: "http://mock.url", }, expectedErr: ErrPortInvalid, diff --git a/pkg/server/database/consts.go b/pkg/server/database/consts.go index 38c4d536..b4a1db03 100644 --- a/pkg/server/database/consts.go +++ b/pkg/server/database/consts.go @@ -23,8 +23,6 @@ const ( TokenTypeResetPassword = "reset_password" // TokenTypeEmailVerification is a type of a token for verifying email TokenTypeEmailVerification = "email_verification" - // TokenTypeEmailPreference is a type of a token for updating email preference - TokenTypeEmailPreference = "email_preference" ) const ( diff --git a/pkg/server/database/database.go b/pkg/server/database/database.go index 0795fc3d..eaab7c50 100644 --- a/pkg/server/database/database.go +++ b/pkg/server/database/database.go @@ -39,9 +39,7 @@ func InitSchema(db *gorm.DB) { &Account{}, &Book{}, &Note{}, - &Notification{}, &Token{}, - &EmailPreference{}, &Session{}, ); err != nil { panic(err) @@ -56,9 +54,7 @@ func Open(dbPath string) *gorm.DB { panic(errors.Wrapf(err, "creating database directory at %s", dir)) } - // Enable FTS5 extension for full-text search - dsn := dbPath + "?_fts5=1" - db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) + db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) if err != nil { panic(errors.Wrap(err, "opening database conection")) } diff --git a/pkg/server/database/models.go b/pkg/server/database/models.go index 595e1260..ac3e4e56 100644 --- a/pkg/server/database/models.go +++ b/pkg/server/database/models.go @@ -88,21 +88,6 @@ type Token struct { UsedAt *time.Time } -// Notification is the learning notification sent to the user -type Notification struct { - Model - Type string - UserID int `gorm:"index"` -} - -// EmailPreference is a preference per user for receiving email communication -type EmailPreference struct { - Model - UserID int `gorm:"index" json:"-"` - InactiveReminder bool `json:"inactive_reminder" gorm:"default:false"` - ProductUpdate bool `json:"product_update" gorm:"default:true"` -} - // Session represents a user session type Session struct { Model diff --git a/pkg/server/job/job.go b/pkg/server/job/job.go deleted file mode 100644 index 12efd823..00000000 --- a/pkg/server/job/job.go +++ /dev/null @@ -1,127 +0,0 @@ -/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package job - -import ( - slog "log" - - "github.com/dnote/dnote/pkg/clock" - "github.com/dnote/dnote/pkg/server/config" - "github.com/dnote/dnote/pkg/server/mailer" - "gorm.io/gorm" - "github.com/pkg/errors" - "github.com/robfig/cron" -) - -var ( - // ErrEmptyDB is an error for missing database connection in the app configuration - ErrEmptyDB = errors.New("No database connection was provided") - // ErrEmptyClock is an error for missing clock in the app configuration - ErrEmptyClock = errors.New("No clock was provided") - // ErrEmptyWebURL is an error for missing WebURL content in the app configuration - ErrEmptyWebURL = errors.New("No WebURL was provided") - // ErrEmptyEmailTemplates is an error for missing EmailTemplates content in the app configuration - ErrEmptyEmailTemplates = errors.New("No EmailTemplate store was provided") - // ErrEmptyEmailBackend is an error for missing EmailBackend content in the app configuration - ErrEmptyEmailBackend = errors.New("No EmailBackend was provided") -) - -// Runner is a configuration for job -type Runner struct { - DB *gorm.DB - Clock clock.Clock - EmailTmpl mailer.Templates - EmailBackend mailer.Backend - Config config.Config -} - -// NewRunner returns a new runner -func NewRunner(db *gorm.DB, c clock.Clock, t mailer.Templates, b mailer.Backend, config config.Config) (Runner, error) { - ret := Runner{ - DB: db, - EmailTmpl: t, - EmailBackend: b, - Clock: c, - Config: config, - } - - if err := ret.validate(); err != nil { - return Runner{}, errors.Wrap(err, "validating runner configuration") - } - - return ret, nil -} - -func (r *Runner) validate() error { - if r.DB == nil { - return ErrEmptyDB - } - if r.Clock == nil { - return ErrEmptyClock - } - if r.EmailTmpl == nil { - return ErrEmptyEmailTemplates - } - if r.EmailBackend == nil { - return ErrEmptyEmailBackend - } - if r.Config.WebURL == "" { - return ErrEmptyWebURL - } - - return nil -} - -func scheduleJob(c *cron.Cron, spec string, cmd func()) { - s, err := cron.ParseStandard(spec) - if err != nil { - panic(errors.Wrap(err, "parsing schedule")) - } - - c.Schedule(s, cron.FuncJob(cmd)) -} - -func (r *Runner) schedule(ch chan error) { - // Schedule jobs - cr := cron.New() - cr.Start() - - ch <- nil - - // Block forever - select {} -} - -// Do starts the background tasks in a separate goroutine that runs forever -func (r *Runner) Do() error { - // validate - if err := r.validate(); err != nil { - return errors.Wrap(err, "validating job configurations") - } - - ch := make(chan error) - go r.schedule(ch) - if err := <-ch; err != nil { - return errors.Wrap(err, "scheduling jobs") - } - - slog.Println("Started background tasks") - - return nil -} diff --git a/pkg/server/job/job_test.go b/pkg/server/job/job_test.go deleted file mode 100644 index 58b362ca..00000000 --- a/pkg/server/job/job_test.go +++ /dev/null @@ -1,105 +0,0 @@ -/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024, 2025 Dnote contributors - * - * This file is part of Dnote. - * - * Dnote is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Dnote is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Dnote. If not, see . - */ - -package job - -import ( - "fmt" - "testing" - - "github.com/dnote/dnote/pkg/assert" - "github.com/dnote/dnote/pkg/clock" - "github.com/dnote/dnote/pkg/server/config" - "github.com/dnote/dnote/pkg/server/mailer" - "github.com/dnote/dnote/pkg/server/testutils" - "gorm.io/gorm" - "github.com/pkg/errors" -) - -func TestNewRunner(t *testing.T) { - testCases := []struct { - db *gorm.DB - clock clock.Clock - emailTmpl mailer.Templates - emailBackend mailer.Backend - webURL string - expectedErr error - }{ - { - db: &gorm.DB{}, - clock: clock.NewMock(), - emailTmpl: mailer.Templates{}, - emailBackend: &testutils.MockEmailbackendImplementation{}, - webURL: "http://mock.url", - expectedErr: nil, - }, - { - db: nil, - clock: clock.NewMock(), - emailTmpl: mailer.Templates{}, - emailBackend: &testutils.MockEmailbackendImplementation{}, - webURL: "http://mock.url", - expectedErr: ErrEmptyDB, - }, - { - db: &gorm.DB{}, - clock: nil, - emailTmpl: mailer.Templates{}, - emailBackend: &testutils.MockEmailbackendImplementation{}, - webURL: "http://mock.url", - expectedErr: ErrEmptyClock, - }, - { - db: &gorm.DB{}, - clock: clock.NewMock(), - emailTmpl: nil, - emailBackend: &testutils.MockEmailbackendImplementation{}, - webURL: "http://mock.url", - expectedErr: ErrEmptyEmailTemplates, - }, - { - db: &gorm.DB{}, - clock: clock.NewMock(), - emailTmpl: mailer.Templates{}, - emailBackend: nil, - webURL: "http://mock.url", - expectedErr: ErrEmptyEmailBackend, - }, - { - db: &gorm.DB{}, - clock: clock.NewMock(), - emailTmpl: mailer.Templates{}, - emailBackend: &testutils.MockEmailbackendImplementation{}, - webURL: "", - expectedErr: ErrEmptyWebURL, - }, - } - - for idx, tc := range testCases { - t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) { - - c := config.Config{ - WebURL: tc.webURL, - } - - _, err := NewRunner(tc.db, tc.clock, tc.emailTmpl, tc.emailBackend, c) - - assert.Equal(t, errors.Cause(err), tc.expectedErr, "error mismatch") - }) - } -} diff --git a/pkg/server/main.go b/pkg/server/main.go index 4585a03e..02936cc8 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -23,7 +23,6 @@ import ( "fmt" "log" "net/http" - "os" "github.com/dnote/dnote/pkg/clock" "github.com/dnote/dnote/pkg/server/app" @@ -31,7 +30,6 @@ import ( "github.com/dnote/dnote/pkg/server/config" "github.com/dnote/dnote/pkg/server/controllers" "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/job" "github.com/dnote/dnote/pkg/server/mailer" "github.com/pkg/errors" "gorm.io/gorm" @@ -40,7 +38,7 @@ import ( var port = flag.String("port", "3000", "port to connect to") func initDB(c config.Config) *gorm.DB { - db := database.Open(c.DB.Path) + db := database.Open(c.DBPath) database.InitSchema(db) database.Migrate(db) @@ -50,11 +48,11 @@ func initDB(c config.Config) *gorm.DB { func initApp(cfg config.Config) app.App { db := initDB(cfg) - isProduction := os.Getenv("GO_ENV") == "PRODUCTION" - emailBackend, err := mailer.NewDefaultBackend(isProduction) + emailBackend, err := mailer.NewDefaultBackend(cfg.IsProd()) if err != nil { - log.Printf("Email backend not fully configured: %v. Emails will only be logged.", err) emailBackend = &mailer.DefaultBackend{Enabled: false} + } else { + log.Printf("Email backend configured") } return app.App{ @@ -67,18 +65,6 @@ func initApp(cfg config.Config) app.App { } } -func runJob(a app.App) error { - runner, err := job.NewRunner(a.DB, a.Clock, a.EmailTemplates, a.EmailBackend, a.Config) - if err != nil { - return errors.Wrap(err, "getting a job runner") - } - if err := runner.Do(); err != nil { - return errors.Wrap(err, "running job") - } - - return nil -} - func startCmd() { cfg := config.Load() cfg.SetAssetBaseURL("/static") @@ -91,13 +77,6 @@ func startCmd() { } }() - if err := database.Migrate(app.DB); err != nil { - panic(errors.Wrap(err, "running migrations")) - } - if err := runJob(app); err != nil { - panic(errors.Wrap(err, "running job")) - } - ctl := controllers.New(&app) rc := controllers.RouteConfig{ WebRoutes: controllers.NewWebRoutes(&app, ctl), @@ -119,7 +98,7 @@ func versionCmd() { } func rootCmd() { - fmt.Printf(`Dnote server - a simple personal knowledge base + fmt.Printf(`Dnote server - a simple command line notebook Usage: dnote-server [command] diff --git a/pkg/server/middleware/auth_test.go b/pkg/server/middleware/auth_test.go index d8dc8802..8451ae5d 100644 --- a/pkg/server/middleware/auth_test.go +++ b/pkg/server/middleware/auth_test.go @@ -178,7 +178,7 @@ func TestTokenAuth(t *testing.T) { user := testutils.SetupUserData(db) tok := database.Token{ UserID: user.ID, - Type: database.TokenTypeEmailPreference, + Type: database.TokenTypeEmailVerification, Value: "xpwFnc0MdllFUePDq9DLeQ==", } testutils.MustExec(t, db.Save(&tok), "preparing token") @@ -193,7 +193,7 @@ func TestTokenAuth(t *testing.T) { w.WriteHeader(http.StatusOK) } - server := httptest.NewServer(TokenAuth(db, handler, database.TokenTypeEmailPreference, nil)) + server := httptest.NewServer(TokenAuth(db, handler, database.TokenTypeEmailVerification, nil)) defer server.Close() t.Run("with token", func(t *testing.T) { diff --git a/pkg/server/middleware/limit.go b/pkg/server/middleware/limit.go index b186c6a9..3b3c3987 100644 --- a/pkg/server/middleware/limit.go +++ b/pkg/server/middleware/limit.go @@ -128,7 +128,7 @@ func Limit(next http.Handler) http.HandlerFunc { func ApplyLimit(h http.HandlerFunc, rateLimit bool) http.Handler { ret := h - if rateLimit && os.Getenv("GO_ENV") != "TEST" { + if rateLimit && os.Getenv("APP_ENV") != "TEST" { ret = Limit(ret) } diff --git a/pkg/server/testutils/main.go b/pkg/server/testutils/main.go index b8519581..e07f41e8 100644 --- a/pkg/server/testutils/main.go +++ b/pkg/server/testutils/main.go @@ -45,7 +45,7 @@ import ( // the environment variable configuration and initalizes a new schema func InitTestDB() *gorm.DB { c := config.Load() - return database.Open(c.DB.Path) + return database.Open(c.DBPath) } // InitMemoryDB creates an in-memory SQLite database with the schema initialized @@ -76,15 +76,9 @@ func ClearData(db *gorm.DB) { if err := db.Where("1 = 1").Delete(&database.Book{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear books")) } - if err := db.Where("1 = 1").Delete(&database.Notification{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear notifications")) - } if err := db.Where("1 = 1").Delete(&database.Token{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear tokens")) } - if err := db.Where("1 = 1").Delete(&database.EmailPreference{}).Error; err != nil { - panic(errors.Wrap(err, "Failed to clear email preferences")) - } if err := db.Where("1 = 1").Delete(&database.Session{}).Error; err != nil { panic(errors.Wrap(err, "Failed to clear sessions")) } diff --git a/pkg/server/token/token_test.go b/pkg/server/token/token_test.go index cdc114c0..922cc93d 100644 --- a/pkg/server/token/token_test.go +++ b/pkg/server/token/token_test.go @@ -33,7 +33,7 @@ func TestCreate(t *testing.T) { kind string }{ { - kind: database.TokenTypeEmailPreference, + kind: database.TokenTypeEmailVerification, }, }