diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30e3a24c..68ab3708 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,13 @@ The following log documentes the history of the server project.
### [Unreleased]
-- N/A
+#### Upgrade Guide
+
+* Please define a new environment variable `WebURL` whose value is the URL to your Dnote server, without the trailing slash. (e.g. `https://my-server.com`) (Please see #290)
+
+#### Fixed
+
+- Allow to customize the app URL in the emails (#290)
### 0.2.0 - 2019-10-28
@@ -24,6 +30,9 @@ The following log documentes the history of the server project.
- Treat a linebreak as a new line in the preview (#261)
- Allow to have multiple editor states for adding and editing notes (#260)
+
+#### Fixed
+
- Fix jumping focus on editor (#265)
### 0.1.1 - 2019-09-30
diff --git a/SELF_HOSTING.md b/SELF_HOSTING.md
index c688b42f..892d2860 100644
--- a/SELF_HOSTING.md
+++ b/SELF_HOSTING.md
@@ -23,10 +23,13 @@ DBPort=5432 \
DBName=dnote \
DBUser=$user \
DBPassword=$password \
+WebURL=$webURL
dnote-server start
```
-Replace $user and $password with the credentials of the Postgres user that owns the `dnote` database.
+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`).
By default, dnote server will run on the port 3000.
@@ -91,6 +94,7 @@ Environment=GO_ENV=PRODUCTION
Environment=DBHost=localhost
Environment=DBPort=5432
Environment=DBName=dnote
+Environment=WebURL=$WebURL
Environment=DBUser=$DBUser
Environment=DBPassword=$DBPassword
Environment=SmtpHost=
@@ -101,9 +105,9 @@ Environment=SmtpPassword=
WantedBy=multi-user.target
```
-Replace `$user`, `$DBUser`, and `$DBPassword` with the actual values.
+Replace `$user`, `$WebURL`, `$DBUser`, and `$DBPassword` with the actual values.
-Optionally, if you would like to send email digests, populate `SmtpHost`, `SmtpUsername`, and `SmtpPassword`.
+Optionally, if you would like to send spaced repetitions throught email, populate `SmtpHost`, `SmtpUsername`, and `SmtpPassword`.
2. Reload the change by running `sudo systemctl daemon-reload`.
3. Enable the Daemon by running `sudo systemctl enable dnote`.`
@@ -144,3 +148,7 @@ 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/pkg/server/.env.dev b/pkg/server/.env.dev
index 2be2070a..9e9644f0 100644
--- a/pkg/server/.env.dev
+++ b/pkg/server/.env.dev
@@ -9,3 +9,5 @@ DBPassword=
SmtpUsername=mock-SmtpUsername
SmtpPassword=mock-SmtpPassword
SmtpHost=mock-SmtpHost
+
+WebURL=http://localhost:3000
diff --git a/pkg/server/.env.test b/pkg/server/.env.test
index 17a64fc7..06585243 100644
--- a/pkg/server/.env.test
+++ b/pkg/server/.env.test
@@ -9,3 +9,5 @@ DBPassword=
SmtpUsername=mock-SmtpUsername
SmtpPassword=mock-SmtpPassword
SmtpHost=mock-SmtpHost
+
+WebURL=http://localhost:3000
diff --git a/pkg/server/api/handlers/auth.go b/pkg/server/api/handlers/auth.go
index eea6f93b..f2397fb2 100644
--- a/pkg/server/api/handlers/auth.go
+++ b/pkg/server/api/handlers/auth.go
@@ -133,12 +133,10 @@ func (a *App) createResetToken(w http.ResponseWriter, r *http.Request) {
}
subject := "Reset your password"
- data := struct {
- Subject string
- Token string
- }{
- subject,
- resetToken,
+ data := mailer.EmailResetPasswordTmplData{
+ Subject: subject,
+ Token: resetToken,
+ WebURL: a.WebURL,
}
email := mailer.NewEmail("noreply@getdnote.com", []string{params.Email}, subject)
if err := email.ParseTemplate(mailer.EmailTypeResetPassword, data); err != nil {
diff --git a/pkg/server/api/handlers/auth_test.go b/pkg/server/api/handlers/auth_test.go
index a5aeb394..cb47f727 100644
--- a/pkg/server/api/handlers/auth_test.go
+++ b/pkg/server/api/handlers/auth_test.go
@@ -20,7 +20,6 @@ package handlers
import (
"net/http"
- "net/http/httptest"
"testing"
"time"
@@ -36,9 +35,9 @@ func TestGetMe(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -64,9 +63,9 @@ func TestCreateResetToken(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -97,9 +96,9 @@ func TestCreateResetToken(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -126,9 +125,9 @@ func TestResetPassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -172,9 +171,9 @@ func TestResetPassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -210,9 +209,9 @@ func TestResetPassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -247,9 +246,9 @@ func TestResetPassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -295,9 +294,9 @@ func TestResetPassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
diff --git a/pkg/server/api/handlers/classic_test.go b/pkg/server/api/handlers/classic_test.go
index 01dec8f4..05b88469 100644
--- a/pkg/server/api/handlers/classic_test.go
+++ b/pkg/server/api/handlers/classic_test.go
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/http/httptest"
"testing"
"github.com/dnote/dnote/pkg/assert"
@@ -78,9 +77,9 @@ func TestClassicPresignin(t *testing.T) {
t.Run(fmt.Sprintf("presignin %s", tc.email), func(t *testing.T) {
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
endpoint := fmt.Sprintf("/classic/presignin?email=%s", tc.email)
@@ -106,9 +105,9 @@ func TestClassicPresignin_MissingParams(t *testing.T) {
defer testutils.ClearData()
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
req := testutils.MakeReq(server, "GET", "/classic/presignin", "")
@@ -129,9 +128,9 @@ func TestClassicSignin(t *testing.T) {
testutils.MustExec(t, db.Save(&alice), "saving alice")
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
dat := fmt.Sprintf(`{"email": "%s", "auth_key": "%s"}`, "alice@example.com", "/XCYisXJ6/o+vf6NUEtmrdYzJYPz+T9oAUCtMpOjhzc=")
@@ -227,9 +226,9 @@ func TestClassicSignin_Failure(t *testing.T) {
t.Run(fmt.Sprintf("signin %s %s", tc.email, tc.authKey), func(t *testing.T) {
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
dat := fmt.Sprintf(`{"email": "%s", "auth_key": "%s"}`, tc.email, tc.authKey)
diff --git a/pkg/server/api/handlers/health_test.go b/pkg/server/api/handlers/health_test.go
index 61af9e63..c778755c 100644
--- a/pkg/server/api/handlers/health_test.go
+++ b/pkg/server/api/handlers/health_test.go
@@ -20,7 +20,6 @@ package handlers
import (
"net/http"
- "net/http/httptest"
"testing"
"github.com/dnote/dnote/pkg/assert"
@@ -32,9 +31,9 @@ func TestCheckHealth(t *testing.T) {
defer testutils.ClearData()
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
// Execute
diff --git a/pkg/server/api/handlers/notes_test.go b/pkg/server/api/handlers/notes_test.go
index d5a27cdc..afed2591 100644
--- a/pkg/server/api/handlers/notes_test.go
+++ b/pkg/server/api/handlers/notes_test.go
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/http/httptest"
"reflect"
"testing"
"time"
@@ -44,9 +43,9 @@ func TestGetNotes(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -187,9 +186,9 @@ func TestGetNote(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
diff --git a/pkg/server/api/handlers/repetition_rules_test.go b/pkg/server/api/handlers/repetition_rules_test.go
index 3c3f00ab..665955e2 100644
--- a/pkg/server/api/handlers/repetition_rules_test.go
+++ b/pkg/server/api/handlers/repetition_rules_test.go
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/http/httptest"
"testing"
"time"
@@ -43,9 +42,9 @@ func TestGetRepetitionRule(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -117,9 +116,9 @@ func TestGetRepetitionRules(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -225,9 +224,10 @@ func TestCreateRepetitionRules(t *testing.T) {
c := clock.NewMock()
t0 := time.Date(2009, time.November, 1, 2, 3, 4, 5, time.UTC)
c.SetNow(t0)
- server := httptest.NewServer(NewRouter(&App{
+
+ server := mustNewServer(t, &App{
Clock: c,
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -282,9 +282,10 @@ func TestCreateRepetitionRules(t *testing.T) {
c := clock.NewMock()
t0 := time.Date(2009, time.November, 1, 2, 3, 4, 5, time.UTC)
c.SetNow(t0)
- server := httptest.NewServer(NewRouter(&App{
+
+ server := mustNewServer(t, &App{
Clock: c,
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -345,9 +346,9 @@ func TestUpdateRepetitionRules(t *testing.T) {
c := clock.NewMock()
t0 := time.Date(2009, time.November, 1, 2, 3, 4, 5, time.UTC)
c.SetNow(t0)
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: c,
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -419,9 +420,9 @@ func TestDeleteRepetitionRules(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -544,9 +545,9 @@ func TestCreateUpdateRepetitionRules_BadRequest(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -588,9 +589,9 @@ func TestCreateUpdateRepetitionRules_BadRequest(t *testing.T) {
}
testutils.MustExec(t, db.Save(&b1), "preparing book1")
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
// Execute
@@ -627,9 +628,9 @@ func TestCreateRepetitionRules_BadRequest(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
diff --git a/pkg/server/api/handlers/routes.go b/pkg/server/api/handlers/routes.go
index a49249b1..d92a9a64 100644
--- a/pkg/server/api/handlers/routes.go
+++ b/pkg/server/api/handlers/routes.go
@@ -351,20 +351,37 @@ func applyMiddleware(h http.HandlerFunc, rateLimit bool) http.Handler {
type App struct {
Clock clock.Clock
StripeAPIBackend stripe.Backend
+ WebURL string
+}
+
+func (a *App) validate() error {
+ if a.WebURL == "" {
+ return errors.New("WebURL is empty")
+ }
+
+ return nil
}
// init sets up the application based on the configuration
-func (a *App) init() {
+func (a *App) init() error {
+ if err := a.validate(); err != nil {
+ return errors.Wrap(err, "validating the app parameters")
+ }
+
stripe.Key = os.Getenv("StripeSecretKey")
if a.StripeAPIBackend != nil {
stripe.SetBackend(stripe.APIBackend, a.StripeAPIBackend)
}
+
+ return nil
}
// NewRouter creates and returns a new router
-func NewRouter(app *App) *mux.Router {
- app.init()
+func NewRouter(app *App) (*mux.Router, error) {
+ if err := app.init(); err != nil {
+ return nil, errors.Wrap(err, "initializing app")
+ }
proOnly := authMiddlewareParams{ProOnly: true}
@@ -436,5 +453,5 @@ func NewRouter(app *App) *mux.Router {
Handler(applyMiddleware(handler, route.RateLimit))
}
- return router
+ return router, nil
}
diff --git a/pkg/server/api/handlers/routes_test.go b/pkg/server/api/handlers/routes_test.go
index 8d5bb90c..b4ca5f4d 100644
--- a/pkg/server/api/handlers/routes_test.go
+++ b/pkg/server/api/handlers/routes_test.go
@@ -681,9 +681,9 @@ func TestNotSupportedVersions(t *testing.T) {
}
// setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
for _, tc := range testCases {
diff --git a/pkg/server/api/handlers/testutils.go b/pkg/server/api/handlers/testutils.go
new file mode 100644
index 00000000..dde58712
--- /dev/null
+++ b/pkg/server/api/handlers/testutils.go
@@ -0,0 +1,42 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * 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 handlers
+
+import (
+ "net/http/httptest"
+ "os"
+ "testing"
+
+ "github.com/pkg/errors"
+)
+
+// mustNewServer is a test utility function to initialize a new server
+// with the given app paratmers
+func mustNewServer(t *testing.T, app *App) *httptest.Server {
+ app.WebURL = os.Getenv("WebURL")
+
+ r, err := NewRouter(app)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "initializing server"))
+ }
+
+ server := httptest.NewServer(r)
+
+ return server
+}
diff --git a/pkg/server/api/handlers/user.go b/pkg/server/api/handlers/user.go
index 3077205a..9160e1ca 100644
--- a/pkg/server/api/handlers/user.go
+++ b/pkg/server/api/handlers/user.go
@@ -188,12 +188,10 @@ func (a *App) createVerificationToken(w http.ResponseWriter, r *http.Request) {
}
subject := "Verify your email"
- data := struct {
- Subject string
- Token string
- }{
- subject,
- tokenValue,
+ data := mailer.EmailVerificationTmplData{
+ Subject: subject,
+ Token: tokenValue,
+ WebURL: a.WebURL,
}
email := mailer.NewEmail("noreply@getdnote.com", []string{account.Email.String}, subject)
if err := email.ParseTemplate(mailer.EmailTypeEmailVerification, data); err != nil {
diff --git a/pkg/server/api/handlers/user_test.go b/pkg/server/api/handlers/user_test.go
index 69b5c150..809fea3e 100644
--- a/pkg/server/api/handlers/user_test.go
+++ b/pkg/server/api/handlers/user_test.go
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/http/httptest"
"testing"
"time"
@@ -47,9 +46,9 @@ func TestUpdatePassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -75,9 +74,9 @@ func TestUpdatePassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -101,9 +100,9 @@ func TestUpdatePassword(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -135,9 +134,9 @@ func TestCreateVerificationToken(t *testing.T) {
templatePath := fmt.Sprintf("%s/mailer/templates/src", testutils.ServerPath)
mailer.InitTemplates(&templatePath)
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -168,9 +167,9 @@ func TestCreateVerificationToken(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -201,9 +200,9 @@ func TestVerifyEmail(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -242,9 +241,9 @@ func TestVerifyEmail(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -286,9 +285,9 @@ func TestVerifyEmail(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -328,9 +327,9 @@ func TestVerifyEmail(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -373,9 +372,9 @@ func TestUpdateEmail(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -407,9 +406,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -433,9 +432,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -473,9 +472,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -507,9 +506,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -543,9 +542,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -579,9 +578,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -605,9 +604,9 @@ func TestUpdateEmailPreference(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -641,9 +640,9 @@ func TestGetEmailPreference(t *testing.T) {
defer testutils.ClearData()
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
diff --git a/pkg/server/api/handlers/v3_auth_test.go b/pkg/server/api/handlers/v3_auth_test.go
index c2d8ff75..8faa482d 100644
--- a/pkg/server/api/handlers/v3_auth_test.go
+++ b/pkg/server/api/handlers/v3_auth_test.go
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/http/httptest"
"testing"
"time"
@@ -91,9 +90,9 @@ func TestRegister(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
dat := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, tc.email, tc.password)
@@ -134,9 +133,9 @@ func TestRegisterMissingParams(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
dat := fmt.Sprintf(`{"password": %s}`, "SLMZFM5RmSjA5vfXnG5lPOnrpZSbtmV76cnAcrlr2yU")
@@ -161,9 +160,9 @@ func TestRegisterMissingParams(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
dat := fmt.Sprintf(`{"email": "%s"}`, "alice@example.com")
@@ -189,9 +188,9 @@ func TestRegisterDuplicateEmail(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -226,9 +225,9 @@ func TestSignIn(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -256,9 +255,9 @@ func TestSignIn(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -287,9 +286,9 @@ func TestSignIn(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
u := testutils.SetupUserData()
@@ -318,9 +317,9 @@ func TestSignIn(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
dat := `{"email": "nonexistent@example.com", "password": "pass1234"}`
@@ -361,9 +360,9 @@ func TestSignout(t *testing.T) {
testutils.MustExec(t, db.Save(&session2), "preparing session2")
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
// Execute
@@ -412,9 +411,9 @@ func TestSignout(t *testing.T) {
testutils.MustExec(t, db.Save(&session2), "preparing session2")
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
// Execute
diff --git a/pkg/server/api/handlers/v3_books_test.go b/pkg/server/api/handlers/v3_books_test.go
index cd7d4bc9..01184947 100644
--- a/pkg/server/api/handlers/v3_books_test.go
+++ b/pkg/server/api/handlers/v3_books_test.go
@@ -22,7 +22,6 @@ import (
"encoding/json"
"fmt"
"net/http"
- "net/http/httptest"
"reflect"
"testing"
@@ -43,9 +42,9 @@ func TestGetBooks(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -122,9 +121,9 @@ func TestGetBooksByName(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -207,9 +206,9 @@ func TestDeleteBook(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -357,9 +356,9 @@ func TestCreateBook(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -418,9 +417,9 @@ func TestCreateBookDuplicate(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -497,9 +496,9 @@ func TestUpdateBook(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
diff --git a/pkg/server/api/handlers/v3_notes_test.go b/pkg/server/api/handlers/v3_notes_test.go
index 912b6c14..19e311c4 100644
--- a/pkg/server/api/handlers/v3_notes_test.go
+++ b/pkg/server/api/handlers/v3_notes_test.go
@@ -21,7 +21,6 @@ package handlers
import (
"fmt"
"net/http"
- "net/http/httptest"
"testing"
"github.com/dnote/dnote/pkg/assert"
@@ -39,9 +38,9 @@ func TestCreateNote(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -172,9 +171,9 @@ func TestUpdateNote(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
@@ -265,9 +264,9 @@ func TestDeleteNote(t *testing.T) {
db := database.DBConn
// Setup
- server := httptest.NewServer(NewRouter(&App{
+ server := mustNewServer(t, &App{
Clock: clock.NewMock(),
- }))
+ })
defer server.Close()
user := testutils.SetupUserData()
diff --git a/pkg/server/job/job.go b/pkg/server/job/job.go
index 56085e8d..5be2867e 100644
--- a/pkg/server/job/job.go
+++ b/pkg/server/job/job.go
@@ -20,6 +20,7 @@ package job
import (
"log"
+ "os"
"github.com/dnote/dnote/pkg/clock"
"github.com/dnote/dnote/pkg/server/job/repetition"
@@ -36,10 +37,15 @@ func scheduleJob(c *cron.Cron, spec string, cmd func()) {
c.Schedule(s, cron.FuncJob(cmd))
}
-// Run starts the background tasks and blocks forever.
-func Run() {
- log.Println("Started background tasks")
+func checkEnvironment() error {
+ if os.Getenv("WebURL") == "" {
+ return errors.New("WebURL is empty")
+ }
+ return nil
+}
+
+func schedule(ch chan error) {
cl := clock.New()
// Schedule jobs
@@ -47,6 +53,25 @@ func Run() {
scheduleJob(c, "* * * * *", func() { repetition.Do(cl) })
c.Start()
+ ch <- nil
+
// Block forever
select {}
}
+
+// Run starts the background tasks in a separate goroutine that runs forever
+func Run() error {
+ if err := checkEnvironment(); err != nil {
+ return errors.Wrap(err, "checking environment variables")
+ }
+
+ ch := make(chan error)
+ go schedule(ch)
+ if err := <-ch; err != nil {
+ return errors.Wrap(err, "scheduling jobs")
+ }
+
+ log.Println("Started background tasks")
+
+ return nil
+}
diff --git a/pkg/server/job/repetition/repetition.go b/pkg/server/job/repetition/repetition.go
index b0df34e1..8401eb6a 100644
--- a/pkg/server/job/repetition/repetition.go
+++ b/pkg/server/job/repetition/repetition.go
@@ -20,6 +20,7 @@ package repetition
import (
"fmt"
+ "os"
"time"
"github.com/dnote/dnote/pkg/clock"
@@ -74,6 +75,7 @@ func BuildEmail(now time.Time, user database.User, emailAddr string, digest data
EmailSessionToken: tok.Value,
RuleUUID: rule.UUID,
RuleTitle: rule.Title,
+ WebURL: os.Getenv("WebURL"),
}
email := mailer.NewEmail("noreply@getdnote.com", []string{emailAddr}, subject)
diff --git a/pkg/server/mailer/mailer.go b/pkg/server/mailer/mailer.go
index 0c9187f2..9d4942a8 100644
--- a/pkg/server/mailer/mailer.go
+++ b/pkg/server/mailer/mailer.go
@@ -161,12 +161,12 @@ func (e *Email) ParseTemplate(templateName string, data interface{}) error {
buf := new(bytes.Buffer)
if err := t.Execute(buf, data); err != nil {
- return err
+ return errors.Wrap(err, "executing the template")
}
html, err := inliner.Inline(buf.String())
if err != nil {
- return err
+ return errors.Wrap(err, "inlining the css rules")
}
e.Body = html
diff --git a/pkg/server/mailer/mailer_test.go b/pkg/server/mailer/mailer_test.go
new file mode 100644
index 00000000..8d596de0
--- /dev/null
+++ b/pkg/server/mailer/mailer_test.go
@@ -0,0 +1,113 @@
+/* Copyright (C) 2019 Monomax Software Pty Ltd
+ *
+ * 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 mailer
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+
+ "github.com/dnote/dnote/pkg/server/testutils"
+ "github.com/pkg/errors"
+)
+
+func init() {
+ testutils.InitTestDB()
+
+ templatePath := fmt.Sprintf("%s/mailer/templates/src", testutils.ServerPath)
+ InitTemplates(&templatePath)
+}
+
+func TestEmailVerificationEmail(t *testing.T) {
+ testCases := []struct {
+ token string
+ webURL string
+ }{
+ {
+ token: "someRandomToken1",
+ webURL: "http://localhost:3000",
+ },
+ {
+ token: "someRandomToken2",
+ webURL: "http://localhost:3001",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("with WebURL %s", tc.webURL), func(t *testing.T) {
+ m := NewEmail("alice@example.com", []string{"bob@example.com"}, "Test email")
+
+ dat := EmailVerificationTmplData{
+ Subject: "Test email verification email",
+ Token: tc.token,
+ WebURL: tc.webURL,
+ }
+ err := m.ParseTemplate(EmailTypeEmailVerification, dat)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "executing"))
+ }
+
+ if ok := strings.Contains(m.Body, tc.webURL); !ok {
+ t.Errorf("email body did not contain %s", tc.webURL)
+ }
+ if ok := strings.Contains(m.Body, tc.token); !ok {
+ t.Errorf("email body did not contain %s", tc.token)
+ }
+ })
+ }
+}
+
+func TestResetPasswordEmail(t *testing.T) {
+ testCases := []struct {
+ token string
+ webURL string
+ }{
+ {
+ token: "someRandomToken1",
+ webURL: "http://localhost:3000",
+ },
+ {
+ token: "someRandomToken2",
+ webURL: "http://localhost:3001",
+ },
+ }
+
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("with WebURL %s", tc.webURL), func(t *testing.T) {
+ m := NewEmail("alice@example.com", []string{"bob@example.com"}, "Test email")
+
+ dat := EmailVerificationTmplData{
+ Subject: "Test reset passowrd email",
+ Token: tc.token,
+ WebURL: tc.webURL,
+ }
+ err := m.ParseTemplate(EmailTypeResetPassword, dat)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "executing"))
+ }
+
+ if ok := strings.Contains(m.Body, tc.webURL); !ok {
+ t.Errorf("email body did not contain %s", tc.webURL)
+ }
+ if ok := strings.Contains(m.Body, tc.token); !ok {
+ t.Errorf("email body did not contain %s", tc.token)
+ }
+ })
+ }
+}
diff --git a/pkg/server/mailer/templates/src/digest.html b/pkg/server/mailer/templates/src/digest.html
index bb7db9d7..f141f5de 100644
--- a/pkg/server/mailer/templates/src/digest.html
+++ b/pkg/server/mailer/templates/src/digest.html
@@ -392,7 +392,7 @@
|
- Open
+ Open
|
@@ -413,9 +413,7 @@
-
-
@@ -436,7 +434,7 @@
|
- Change digest settings
+ Change digest settings
|
diff --git a/pkg/server/mailer/templates/src/email_verification.html b/pkg/server/mailer/templates/src/email_verification.html
index c25bd174..ec05d79f 100644
--- a/pkg/server/mailer/templates/src/email_verification.html
+++ b/pkg/server/mailer/templates/src/email_verification.html
@@ -318,7 +318,7 @@
|
- Verify email
+ Verify email
|
@@ -332,7 +332,7 @@
|
- Alternatively you can manually go to the following URL: https://app.getdnote.com/verify-email/{{ .Token }}
+ Alternatively you can manually go to the following URL: {{ .WebURL }}/verify-email/{{ .Token }}
|
diff --git a/pkg/server/mailer/templates/src/reset_password.html b/pkg/server/mailer/templates/src/reset_password.html
index 7b56893c..682dc27b 100644
--- a/pkg/server/mailer/templates/src/reset_password.html
+++ b/pkg/server/mailer/templates/src/reset_password.html
@@ -319,7 +319,7 @@
|
- Reset Password
+ Reset Password
|
diff --git a/pkg/server/mailer/types.go b/pkg/server/mailer/types.go
index 0555aa08..b4604d73 100644
--- a/pkg/server/mailer/types.go
+++ b/pkg/server/mailer/types.go
@@ -34,17 +34,6 @@ type DigestNoteInfo struct {
Stage int
}
-// DigestTmplData is a template data for digest emails
-type DigestTmplData struct {
- Subject string
- NoteInfo []DigestNoteInfo
- ActiveBookCount int
- ActiveNoteCount int
- EmailSessionToken string
- RuleUUID string
- RuleTitle string
-}
-
// NewNoteInfo returns a new NoteInfo
func NewNoteInfo(note database.Note, stage int) DigestNoteInfo {
tm := time.Unix(0, int64(note.AddedOn))
@@ -57,3 +46,29 @@ func NewNoteInfo(note database.Note, stage int) DigestNoteInfo {
Stage: stage,
}
}
+
+// DigestTmplData is a template data for digest emails
+type DigestTmplData struct {
+ Subject string
+ NoteInfo []DigestNoteInfo
+ ActiveBookCount int
+ ActiveNoteCount int
+ EmailSessionToken string
+ RuleUUID string
+ RuleTitle string
+ WebURL string
+}
+
+// EmailVerificationTmplData is a template data for email verification emails
+type EmailVerificationTmplData struct {
+ Subject string
+ Token string
+ WebURL string
+}
+
+// EmailResetPasswordTmplData is a template data for reset password emails
+type EmailResetPasswordTmplData struct {
+ Subject string
+ Token string
+ WebURL string
+}
diff --git a/pkg/server/main.go b/pkg/server/main.go
index b870b0ba..8ac5bb65 100644
--- a/pkg/server/main.go
+++ b/pkg/server/main.go
@@ -38,7 +38,6 @@ import (
var versionTag = "master"
var port = flag.String("port", "3000", "port to connect to")
-
var rootBox *packr.Box
func init() {
@@ -89,13 +88,17 @@ func getSWHandler() http.HandlerFunc {
}
}
-func initServer() *mux.Router {
+func initServer() (*mux.Router, error) {
srv := mux.NewRouter()
- apiRouter := handlers.NewRouter(&handlers.App{
+ apiRouter, err := handlers.NewRouter(&handlers.App{
Clock: clock.New(),
StripeAPIBackend: nil,
+ WebURL: os.Getenv("WebURL"),
})
+ if err != nil {
+ return nil, errors.Wrap(err, "initializing router")
+ }
srv.PathPrefix("/api").Handler(http.StripPrefix("/api", apiRouter))
srv.PathPrefix("/static").Handler(getStaticHandler())
@@ -105,49 +108,45 @@ func initServer() *mux.Router {
// For all other requests, serve the index.html file
srv.PathPrefix("/").Handler(getRootHandler())
- return srv
+ return srv, nil
}
func startCmd() {
- c := database.Config{
+ mailer.InitTemplates(nil)
+
+ database.Open(database.Config{
Host: os.Getenv("DBHost"),
Port: os.Getenv("DBPort"),
Name: os.Getenv("DBName"),
User: os.Getenv("DBUser"),
Password: os.Getenv("DBPassword"),
- }
- database.Open(c)
+ })
database.InitSchema()
defer database.Close()
- mailer.InitTemplates(nil)
-
- // Perform database migration
if err := database.Migrate(); err != nil {
panic(errors.Wrap(err, "running migrations"))
}
+ if err := job.Run(); err != nil {
+ panic(errors.Wrap(err, "running job"))
+ }
- // Run job in the background
- go job.Run()
-
- srv := initServer()
+ srv, err := initServer()
+ if err != nil {
+ panic(errors.Wrap(err, "initializing server"))
+ }
log.Printf("Dnote version %s is running on port %s", versionTag, *port)
addr := fmt.Sprintf(":%s", *port)
- log.Println(http.ListenAndServe(addr, srv))
+ http.ListenAndServe(addr, srv)
}
func versionCmd() {
fmt.Printf("dnote-server-%s\n", versionTag)
}
-func main() {
- flag.Parse()
- cmd := flag.Arg(0)
-
- switch cmd {
- case "":
- fmt.Printf(`Dnote Server - A simple notebook for developers
+func rootCmd() {
+ fmt.Printf(`Dnote Server - A simple notebook for developers
Usage:
dnote-server [command]
@@ -156,6 +155,15 @@ Available commands:
start: Start the server
version: Print the version
`)
+}
+
+func main() {
+ flag.Parse()
+ cmd := flag.Arg(0)
+
+ switch cmd {
+ case "":
+ rootCmd()
case "start":
startCmd()
case "version":