This commit is contained in:
Sung 2025-10-05 11:04:34 -07:00
commit 5912029c1b
22 changed files with 184 additions and 417 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ node_modules
/test
tmp
*.db
server

View file

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

View file

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

View file

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

View file

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

114
pkg/e2e/server_test.go Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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)
}
}

View file

@ -1,4 +1,4 @@
GO_ENV=DEVELOPMENT
APP_ENV=DEVELOPMENT
SmtpUsername=mock-SmtpUsername
SmtpPassword=mock-SmtpPassword

View file

@ -1,4 +1,4 @@
GO_ENV=TEST
APP_ENV=TEST
SmtpUsername=mock-SmtpUsername
SmtpPassword=mock-SmtpPassword

View file

@ -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(),
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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
}

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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")
})
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -33,7 +33,7 @@ func TestCreate(t *testing.T) {
kind string
}{
{
kind: database.TokenTypeEmailPreference,
kind: database.TokenTypeEmailVerification,
},
}