diff --git a/pkg/server/api/auth.go b/pkg/server/api/auth.go
deleted file mode 100644
index 56425c87..00000000
--- a/pkg/server/api/auth.go
+++ /dev/null
@@ -1,187 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "encoding/json"
- "net/http"
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/handlers"
- "github.com/dnote/dnote/pkg/server/helpers"
- "github.com/dnote/dnote/pkg/server/log"
- "github.com/dnote/dnote/pkg/server/mailer"
- "github.com/dnote/dnote/pkg/server/session"
- "github.com/dnote/dnote/pkg/server/token"
- "github.com/pkg/errors"
- "golang.org/x/crypto/bcrypt"
-)
-
-// GetMeResponse is the response for getMe endpoint
-type GetMeResponse struct {
- User session.Session `json:"user"`
-}
-
-func (a *API) getMe(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var account database.Account
- if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
- handlers.DoError(w, "finding account", err, http.StatusInternalServerError)
- return
- }
-
- tx := a.App.DB.Begin()
- if err := a.App.TouchLastLoginAt(user, tx); err != nil {
- tx.Rollback()
- // In case of an error, gracefully continue to avoid disturbing the service
- log.ErrorWrap(err, "error touching last_login_at")
- }
- tx.Commit()
-
- response := GetMeResponse{
- User: session.New(user, account),
- }
- handlers.RespondJSON(w, http.StatusOK, response)
-}
-
-type createResetTokenPayload struct {
- Email string `json:"email"`
-}
-
-func (a *API) createResetToken(w http.ResponseWriter, r *http.Request) {
- var params createResetTokenPayload
- if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
- http.Error(w, "invalid payload", http.StatusBadRequest)
- return
- }
-
- var account database.Account
- conn := a.App.DB.Where("email = ?", params.Email).First(&account)
- if conn.RecordNotFound() {
- return
- }
- if err := conn.Error; err != nil {
- handlers.DoError(w, errors.Wrap(err, "finding account").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- resetToken, err := token.Create(a.App.DB, account.UserID, database.TokenTypeResetPassword)
- if err != nil {
- handlers.DoError(w, errors.Wrap(err, "generating token").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- if err := a.App.SendPasswordResetEmail(account.Email.String, resetToken.Value); err != nil {
- if errors.Cause(err) == mailer.ErrSMTPNotConfigured {
- handlers.RespondInvalidSMTPConfig(w)
- } else {
- handlers.DoError(w, errors.Wrap(err, "sending password reset email").Error(), nil, http.StatusInternalServerError)
- }
-
- return
- }
-}
-
-type resetPasswordPayload struct {
- Password string `json:"password"`
- Token string `json:"token"`
-}
-
-func (a *API) resetPassword(w http.ResponseWriter, r *http.Request) {
- var params resetPasswordPayload
- if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
- http.Error(w, "invalid payload", http.StatusBadRequest)
- return
- }
-
- var token database.Token
- conn := a.App.DB.Where("value = ? AND type =? AND used_at IS NULL", params.Token, database.TokenTypeResetPassword).First(&token)
- if conn.RecordNotFound() {
- http.Error(w, "invalid token", http.StatusBadRequest)
- return
- }
- if err := conn.Error; err != nil {
- handlers.DoError(w, errors.Wrap(err, "finding token").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- if token.UsedAt != nil {
- http.Error(w, "invalid token", http.StatusBadRequest)
- return
- }
-
- // Expire after 10 minutes
- if time.Since(token.CreatedAt).Minutes() > 10 {
- http.Error(w, "This link has been expired. Please request a new password reset link.", http.StatusGone)
- return
- }
-
- tx := a.App.DB.Begin()
-
- hashedPassword, err := bcrypt.GenerateFromPassword([]byte(params.Password), bcrypt.DefaultCost)
- if err != nil {
- tx.Rollback()
- handlers.DoError(w, errors.Wrap(err, "hashing password").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- var account database.Account
- if err := a.App.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil {
- tx.Rollback()
- handlers.DoError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- if err := tx.Model(&account).Update("password", string(hashedPassword)).Error; err != nil {
- tx.Rollback()
- handlers.DoError(w, errors.Wrap(err, "updating password").Error(), nil, http.StatusInternalServerError)
- return
- }
- if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil {
- tx.Rollback()
- handlers.DoError(w, errors.Wrap(err, "updating password reset token").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- if err := a.App.DeleteUserSessions(tx, account.UserID); err != nil {
- tx.Rollback()
- handlers.DoError(w, errors.Wrap(err, "deleting user sessions").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- tx.Commit()
-
- var user database.User
- if err := a.App.DB.Where("id = ?", account.UserID).First(&user).Error; err != nil {
- handlers.DoError(w, errors.Wrap(err, "finding user").Error(), nil, http.StatusInternalServerError)
- return
- }
-
- a.respondWithSession(a.App.DB, w, user.ID, http.StatusOK)
-
- if err := a.App.SendPasswordResetAlertEmail(account.Email.String); err != nil {
- log.ErrorWrap(err, "sending password reset email")
- }
-}
diff --git a/pkg/server/api/auth_test.go b/pkg/server/api/auth_test.go
deleted file mode 100644
index 1e75dd92..00000000
--- a/pkg/server/api/auth_test.go
+++ /dev/null
@@ -1,408 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "testing"
- "time"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/session"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/pkg/errors"
- "golang.org/x/crypto/bcrypt"
-)
-
-func TestGetMe(t *testing.T) {
- testutils.InitTestDB()
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u1 := testutils.SetupUserData()
- a1 := testutils.SetupAccountData(u1, "alice@example.com", "somepassword")
-
- u2 := testutils.SetupUserData()
- testutils.MustExec(t, testutils.DB.Model(&u2).Update("cloud", false), "preparing u2 cloud")
- a2 := testutils.SetupAccountData(u2, "bob@example.com", "somepassword")
-
- testCases := []struct {
- user database.User
- account database.Account
- expectedPro bool
- }{
- {
- user: u1,
- account: a1,
- expectedPro: true,
- },
- {
- user: u2,
- account: a2,
- expectedPro: false,
- },
- }
-
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("user pro %t", tc.expectedPro), func(t *testing.T) {
- // Execute
- req := testutils.MakeReq(server.URL, "GET", "/me", "")
- res := testutils.HTTPAuthDo(t, req, tc.user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var payload GetMeResponse
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- expectedPayload := GetMeResponse{
- User: session.Session{
- UUID: tc.user.UUID,
- Pro: tc.expectedPro,
- Email: tc.account.Email.String,
- EmailVerified: tc.account.EmailVerified,
- },
- }
- assert.DeepEqual(t, payload, expectedPayload, "payload mismatch")
-
- var user database.User
- testutils.MustExec(t, testutils.DB.Where("id = ?", tc.user.ID).First(&user), "finding user")
- assert.NotEqual(t, user.LastLoginAt, nil, "LastLoginAt mismatch")
- })
- }
-}
-
-func TestCreateResetToken(t *testing.T) {
- t.Run("success", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- testutils.SetupAccountData(u, "alice@example.com", "somepassword")
-
- dat := `{"email": "alice@example.com"}`
- req := testutils.MakeReq(server.URL, "POST", "/reset-token", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach")
-
- var tokenCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting tokens")
-
- var resetToken database.Token
- testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", u.ID, database.TokenTypeResetPassword).First(&resetToken), "finding reset token")
-
- assert.Equal(t, tokenCount, 1, "reset_token count mismatch")
- assert.NotEqual(t, resetToken.Value, nil, "reset_token value mismatch")
- assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "reset_token UsedAt mismatch")
- })
-
- t.Run("nonexistent email", func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- testutils.SetupAccountData(u, "alice@example.com", "somepassword")
-
- dat := `{"email": "bob@example.com"}`
- req := testutils.MakeReq(server.URL, "POST", "/reset-token", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismtach")
-
- var tokenCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting tokens")
- assert.Equal(t, tokenCount, 0, "reset_token count mismatch")
- })
-}
-
-func TestResetPassword(t *testing.T) {
- t.Run("success", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword")
- tok := database.Token{
- UserID: u.ID,
- Value: "MivFxYiSMMA4An9dP24DNQ==",
- Type: database.TokenTypeResetPassword,
- }
- testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
- otherTok := database.Token{
- UserID: u.ID,
- Value: "somerandomvalue",
- Type: database.TokenTypeEmailVerification,
- }
- testutils.MustExec(t, testutils.DB.Save(&otherTok), "preparing another token")
-
- dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "newpassword"}`
- req := testutils.MakeReq(server.URL, "PATCH", "/reset-password", dat)
-
- s1 := database.Session{
- Key: "some-session-key-1",
- UserID: u.ID,
- ExpiresAt: time.Now().Add(time.Hour * 10 * 24),
- }
- testutils.MustExec(t, testutils.DB.Save(&s1), "preparing user session 1")
-
- s2 := &database.Session{
- Key: "some-session-key-2",
- UserID: u.ID,
- ExpiresAt: time.Now().Add(time.Hour * 10 * 24),
- }
- testutils.MustExec(t, testutils.DB.Save(&s2), "preparing user session 2")
-
- anotherUser := testutils.SetupUserData()
- testutils.MustExec(t, testutils.DB.Save(&database.Session{
- Key: "some-session-key-3",
- UserID: anotherUser.ID,
- ExpiresAt: time.Now().Add(time.Hour * 10 * 24),
- }), "preparing anotherUser session 1")
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismatch")
-
- var resetToken, verificationToken database.Token
- var account database.Account
- testutils.MustExec(t, testutils.DB.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "finding reset token")
- testutils.MustExec(t, testutils.DB.Where("value = ?", "somerandomvalue").First(&verificationToken), "finding reset token")
- testutils.MustExec(t, testutils.DB.Where("id = ?", a.ID).First(&account), "finding account")
-
- assert.NotEqual(t, resetToken.UsedAt, nil, "reset_token UsedAt mismatch")
- passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte("newpassword"))
- assert.Equal(t, passwordErr, nil, "Password mismatch")
- assert.Equal(t, verificationToken.UsedAt, (*time.Time)(nil), "verificationToken UsedAt mismatch")
-
- var s1Count, s2Count int
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Where("id = ?", s1.ID).Count(&s1Count), "counting s1")
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Where("id = ?", s2.ID).Count(&s2Count), "counting s2")
-
- assert.Equal(t, s1Count, 0, "s1 should have been deleted")
- assert.Equal(t, s2Count, 0, "s2 should have been deleted")
-
- var userSessionCount, anotherUserSessionCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Where("user_id = ?", u.ID).Count(&userSessionCount), "counting user session")
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Where("user_id = ?", anotherUser.ID).Count(&anotherUserSessionCount), "counting anotherUser session")
-
- assert.Equal(t, userSessionCount, 1, "should have created a new user session")
- assert.Equal(t, anotherUserSessionCount, 1, "anotherUser session count mismatch")
- })
-
- t.Run("nonexistent token", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
- tok := database.Token{
- UserID: u.ID,
- Value: "MivFxYiSMMA4An9dP24DNQ==",
- Type: database.TokenTypeResetPassword,
- }
- testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-
- dat := `{"token": "-ApMnyvpg59uOU5b-Kf5uQ==", "password": "oldpassword"}`
- req := testutils.MakeReq(server.URL, "PATCH", "/reset-password", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
-
- var resetToken database.Token
- var account database.Account
- testutils.MustExec(t, testutils.DB.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "finding reset token")
- testutils.MustExec(t, testutils.DB.Where("id = ?", a.ID).First(&account), "finding account")
-
- assert.Equal(t, a.Password, account.Password, "password should not have been updated")
- assert.Equal(t, a.Password, account.Password, "password should not have been updated")
- assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
- })
-
- t.Run("expired token", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
- tok := database.Token{
- UserID: u.ID,
- Value: "MivFxYiSMMA4An9dP24DNQ==",
- Type: database.TokenTypeResetPassword,
- }
- testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
- testutils.MustExec(t, testutils.DB.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
-
- dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "oldpassword"}`
- req := testutils.MakeReq(server.URL, "PATCH", "/reset-password", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusGone, "Status code mismatch")
-
- var resetToken database.Token
- var account database.Account
- testutils.MustExec(t, testutils.DB.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
- testutils.MustExec(t, testutils.DB.Where("id = ?", a.ID).First(&account), "failed to find account")
- assert.Equal(t, a.Password, account.Password, "password should not have been updated")
- assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
- })
-
- t.Run("used token", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
-
- usedAt := time.Now().Add(time.Hour * -11).UTC()
- tok := database.Token{
- UserID: u.ID,
- Value: "MivFxYiSMMA4An9dP24DNQ==",
- Type: database.TokenTypeResetPassword,
- UsedAt: &usedAt,
- }
- testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
- testutils.MustExec(t, testutils.DB.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
-
- dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "oldpassword"}`
- req := testutils.MakeReq(server.URL, "PATCH", "/reset-password", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
-
- var resetToken database.Token
- var account database.Account
- testutils.MustExec(t, testutils.DB.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
- testutils.MustExec(t, testutils.DB.Where("id = ?", a.ID).First(&account), "failed to find account")
- assert.Equal(t, a.Password, account.Password, "password should not have been updated")
-
- if resetToken.UsedAt.Year() != usedAt.Year() ||
- resetToken.UsedAt.Month() != usedAt.Month() ||
- resetToken.UsedAt.Day() != usedAt.Day() ||
- resetToken.UsedAt.Hour() != usedAt.Hour() ||
- resetToken.UsedAt.Minute() != usedAt.Minute() ||
- resetToken.UsedAt.Second() != usedAt.Second() {
- t.Errorf("used_at should be %+v but got: %+v", usedAt, resetToken.UsedAt)
- }
- })
-
- t.Run("using wrong type token: email_verification", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- a := testutils.SetupAccountData(u, "alice@example.com", "somepassword")
- tok := database.Token{
- UserID: u.ID,
- Value: "MivFxYiSMMA4An9dP24DNQ==",
- Type: database.TokenTypeEmailVerification,
- }
- testutils.MustExec(t, testutils.DB.Save(&tok), "Failed to prepare reset_token")
- testutils.MustExec(t, testutils.DB.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-11)), "Failed to prepare reset_token created_at")
-
- dat := `{"token": "MivFxYiSMMA4An9dP24DNQ==", "password": "oldpassword"}`
- req := testutils.MakeReq(server.URL, "PATCH", "/reset-password", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismatch")
-
- var resetToken database.Token
- var account database.Account
- testutils.MustExec(t, testutils.DB.Where("value = ?", "MivFxYiSMMA4An9dP24DNQ==").First(&resetToken), "failed to find reset_token")
- testutils.MustExec(t, testutils.DB.Where("id = ?", a.ID).First(&account), "failed to find account")
-
- assert.Equal(t, a.Password, account.Password, "password should not have been updated")
- assert.Equal(t, resetToken.UsedAt, (*time.Time)(nil), "used_at should be nil")
- })
-}
diff --git a/pkg/server/api/helpers.go b/pkg/server/api/helpers.go
deleted file mode 100644
index 69c65d25..00000000
--- a/pkg/server/api/helpers.go
+++ /dev/null
@@ -1,85 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "net/http"
-// "strings"
-//
-// "github.com/dnote/dnote/pkg/server/database"
-// "github.com/jinzhu/gorm"
-// "github.com/pkg/errors"
-// )
-//
-// func paginate(conn *gorm.DB, page int) *gorm.DB {
-// limit := 30
-//
-// // Paginate
-// if page > 0 {
-// offset := limit * (page - 1)
-// conn = conn.Offset(offset)
-// }
-//
-// conn = conn.Limit(limit)
-//
-// return conn
-// }
-//
-// func getBookIDs(books []database.Book) []int {
-// ret := []int{}
-//
-// for _, book := range books {
-// ret = append(ret, book.ID)
-// }
-//
-// return ret
-// }
-//
-// func validatePassword(password string) error {
-// if len(password) < 8 {
-// return errors.New("Password should be longer than 8 characters")
-// }
-//
-// return nil
-// }
-//
-// func getClientType(r *http.Request) string {
-// origin := r.Header.Get("Origin")
-//
-// if strings.HasPrefix(origin, "moz-extension://") {
-// return "firefox-extension"
-// }
-//
-// if strings.HasPrefix(origin, "chrome-extension://") {
-// return "chrome-extension"
-// }
-//
-// userAgent := r.Header.Get("User-Agent")
-// if strings.HasPrefix(userAgent, "Go-http-client") {
-// return "cli"
-// }
-//
-// return "web"
-// }
-//
-// // notSupported is the handler for the route that is no longer supported
-// func (a *API) notSupported(w http.ResponseWriter, r *http.Request) {
-// http.Error(w, "API version is not supported. Please upgrade your client.", http.StatusGone)
-// return
-// }
diff --git a/pkg/server/api/notes.go b/pkg/server/api/notes.go
deleted file mode 100644
index f3570a42..00000000
--- a/pkg/server/api/notes.go
+++ /dev/null
@@ -1,324 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "fmt"
-// "net/http"
-// "net/url"
-// "strconv"
-// "strings"
-// "time"
-//
-// "github.com/dnote/dnote/pkg/server/database"
-// "github.com/dnote/dnote/pkg/server/helpers"
-// "github.com/dnote/dnote/pkg/server/middleware"
-// "github.com/dnote/dnote/pkg/server/operations"
-// "github.com/dnote/dnote/pkg/server/presenters"
-// "github.com/gorilla/mux"
-// "github.com/jinzhu/gorm"
-// "github.com/pkg/errors"
-// )
-//
-// type ftsParams struct {
-// HighlightAll bool
-// }
-//
-// func getHeadlineOptions(params *ftsParams) string {
-// headlineOptions := []string{
-// "StartSel=",
-// "StopSel=",
-// "ShortWord=0",
-// }
-//
-// if params != nil && params.HighlightAll {
-// headlineOptions = append(headlineOptions, "HighlightAll=true")
-// } else {
-// headlineOptions = append(headlineOptions, "MaxFragments=3, MaxWords=50, MinWords=10")
-// }
-//
-// return strings.Join(headlineOptions, ",")
-// }
-//
-// func selectFTSFields(conn *gorm.DB, search string, params *ftsParams) *gorm.DB {
-// headlineOpts := getHeadlineOptions(params)
-//
-// return conn.Select(`
-// notes.id,
-// notes.uuid,
-// notes.created_at,
-// notes.updated_at,
-// notes.book_uuid,
-// notes.user_id,
-// notes.added_on,
-// notes.edited_on,
-// notes.usn,
-// notes.deleted,
-// notes.encrypted,
-// ts_headline('english_nostop', notes.body, plainto_tsquery('english_nostop', ?), ?) AS body
-// `, search, headlineOpts)
-// }
-//
-// func respondWithNote(w http.ResponseWriter, note database.Note) {
-// presentedNote := presenters.PresentNote(note)
-//
-// middleware.RespondJSON(w, http.StatusOK, presentedNote)
-// }
-//
-// func parseSearchQuery(q url.Values) string {
-// searchStr := q.Get("q")
-//
-// return escapeSearchQuery(searchStr)
-// }
-//
-// func getNoteBaseQuery(db *gorm.DB, noteUUID string, search string) *gorm.DB {
-// var conn *gorm.DB
-// if search != "" {
-// conn = selectFTSFields(db, search, &ftsParams{HighlightAll: true})
-// } else {
-// conn = db
-// }
-//
-// conn = conn.Where("notes.uuid = ? AND deleted = ?", noteUUID, false)
-//
-// return conn
-// }
-//
-// func (a *API) getNote(w http.ResponseWriter, r *http.Request) {
-// user, _, err := middleware.AuthWithSession(a.App.DB, r)
-// if err != nil {
-// middleware.DoError(w, "authenticating", err, http.StatusInternalServerError)
-// return
-// }
-//
-// vars := mux.Vars(r)
-// noteUUID := vars["noteUUID"]
-//
-// note, ok, err := operations.GetNote(a.App.DB, noteUUID, &user)
-// if !ok {
-// middleware.RespondNotFound(w)
-// return
-// }
-// if err != nil {
-// middleware.DoError(w, "finding note", err, http.StatusInternalServerError)
-// return
-// }
-//
-// respondWithNote(w, note)
-// }
-//
-// /**** getNotesHandler */
-//
-// // GetNotesResponse is a reponse by getNotesHandler
-// type GetNotesResponse struct {
-// Notes []presenters.Note `json:"notes"`
-// Total int `json:"total"`
-// }
-//
-// type dateRange struct {
-// lower int64
-// upper int64
-// }
-//
-// func (a *API) getNotes(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-// query := r.URL.Query()
-//
-// respondGetNotes(a.App.DB, user.ID, query, w)
-// }
-//
-// func respondGetNotes(db *gorm.DB, userID int, query url.Values, w http.ResponseWriter) {
-// q, err := parseGetNotesQuery(query)
-// if err != nil {
-// http.Error(w, err.Error(), http.StatusBadRequest)
-// return
-// }
-//
-// conn := getNotesBaseQuery(db, userID, q)
-//
-// var total int
-// if err := conn.Model(database.Note{}).Count(&total).Error; err != nil {
-// middleware.DoError(w, "counting total", err, http.StatusInternalServerError)
-// return
-// }
-//
-// notes := []database.Note{}
-// if total != 0 {
-// conn = orderGetNotes(conn)
-// conn = database.PreloadNote(conn)
-// conn = paginate(conn, q.Page)
-//
-// if err := conn.Find(¬es).Error; err != nil {
-// middleware.DoError(w, "finding notes", err, http.StatusInternalServerError)
-// return
-// }
-// }
-//
-// response := GetNotesResponse{
-// Notes: presenters.PresentNotes(notes),
-// Total: total,
-// }
-// middleware.RespondJSON(w, http.StatusOK, response)
-// }
-//
-// type getNotesQuery struct {
-// Year int
-// Month int
-// Page int
-// Books []string
-// Search string
-// Encrypted bool
-// }
-//
-// func parseGetNotesQuery(q url.Values) (getNotesQuery, error) {
-// yearStr := q.Get("year")
-// monthStr := q.Get("month")
-// books := q["book"]
-// pageStr := q.Get("page")
-// encryptedStr := q.Get("encrypted")
-//
-// fmt.Println("books", books)
-//
-// var page int
-// if len(pageStr) > 0 {
-// p, err := strconv.Atoi(pageStr)
-// if err != nil {
-// return getNotesQuery{}, errors.Errorf("invalid page %s", pageStr)
-// }
-// if p < 1 {
-// return getNotesQuery{}, errors.Errorf("invalid page %s", pageStr)
-// }
-//
-// page = p
-// } else {
-// page = 1
-// }
-//
-// var year int
-// if len(yearStr) > 0 {
-// y, err := strconv.Atoi(yearStr)
-// if err != nil {
-// return getNotesQuery{}, errors.Errorf("invalid year %s", yearStr)
-// }
-//
-// year = y
-// }
-//
-// var month int
-// if len(monthStr) > 0 {
-// m, err := strconv.Atoi(monthStr)
-// if err != nil {
-// return getNotesQuery{}, errors.Errorf("invalid month %s", monthStr)
-// }
-// if m < 1 || m > 12 {
-// return getNotesQuery{}, errors.Errorf("invalid month %s", monthStr)
-// }
-//
-// month = m
-// }
-//
-// var encrypted bool
-// if strings.ToLower(encryptedStr) == "true" {
-// encrypted = true
-// } else {
-// encrypted = false
-// }
-//
-// ret := getNotesQuery{
-// Year: year,
-// Month: month,
-// Page: page,
-// Search: parseSearchQuery(q),
-// Books: books,
-// Encrypted: encrypted,
-// }
-//
-// return ret, nil
-// }
-//
-// func getDateBounds(year, month int) (int64, int64) {
-// var yearUpperbound, monthUpperbound int
-//
-// if month == 12 {
-// monthUpperbound = 1
-// yearUpperbound = year + 1
-// } else {
-// monthUpperbound = month + 1
-// yearUpperbound = year
-// }
-//
-// lower := time.Date(year, time.Month(month), 1, 0, 0, 0, 0, time.UTC).UnixNano()
-// upper := time.Date(yearUpperbound, time.Month(monthUpperbound), 1, 0, 0, 0, 0, time.UTC).UnixNano()
-//
-// return lower, upper
-// }
-//
-// func getNotesBaseQuery(db *gorm.DB, userID int, q getNotesQuery) *gorm.DB {
-// conn := db.Where(
-// "notes.user_id = ? AND notes.deleted = ? AND notes.encrypted = ?",
-// userID, false, q.Encrypted,
-// )
-//
-// if q.Search != "" {
-// conn = selectFTSFields(conn, q.Search, nil)
-// conn = conn.Where("tsv @@ plainto_tsquery('english_nostop', ?)", q.Search)
-// }
-//
-// if len(q.Books) > 0 {
-// conn = conn.Joins("INNER JOIN books ON books.uuid = notes.book_uuid").
-// Where("books.label in (?)", q.Books)
-// }
-//
-// if q.Year != 0 || q.Month != 0 {
-// dateLowerbound, dateUpperbound := getDateBounds(q.Year, q.Month)
-// conn = conn.Where("notes.added_on >= ? AND notes.added_on < ?", dateLowerbound, dateUpperbound)
-// }
-//
-// return conn
-// }
-//
-// func orderGetNotes(conn *gorm.DB) *gorm.DB {
-// return conn.Order("notes.updated_at DESC, notes.id DESC")
-// }
-//
-// // escapeSearchQuery escapes the query for full text search
-// func escapeSearchQuery(searchQuery string) string {
-// return strings.Join(strings.Fields(searchQuery), "&")
-// }
-//
-// func (a *API) legacyGetNotes(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var notes []database.Note
-// if err := a.App.DB.Where("user_id = ? AND encrypted = true", user.ID).Find(¬es).Error; err != nil {
-// middleware.DoError(w, "finding notes", err, http.StatusInternalServerError)
-// return
-// }
-//
-// presented := presenters.PresentNotes(notes)
-// middleware.RespondJSON(w, http.StatusOK, presented)
-// }
diff --git a/pkg/server/api/notes_test.go b/pkg/server/api/notes_test.go
deleted file mode 100644
index 93282607..00000000
--- a/pkg/server/api/notes_test.go
+++ /dev/null
@@ -1,357 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "encoding/json"
-// "fmt"
-// "io/ioutil"
-// "net/http"
-// "testing"
-// "time"
-//
-// "github.com/dnote/dnote/pkg/assert"
-// "github.com/dnote/dnote/pkg/clock"
-// "github.com/dnote/dnote/pkg/server/app"
-// "github.com/dnote/dnote/pkg/server/database"
-// "github.com/dnote/dnote/pkg/server/presenters"
-// "github.com/dnote/dnote/pkg/server/testutils"
-// "github.com/pkg/errors"
-// )
-//
-// func getExpectedNotePayload(n database.Note, b database.Book, u database.User) presenters.Note {
-// return presenters.Note{
-// UUID: n.UUID,
-// CreatedAt: n.CreatedAt,
-// UpdatedAt: n.UpdatedAt,
-// Body: n.Body,
-// AddedOn: n.AddedOn,
-// Public: n.Public,
-// USN: n.USN,
-// Book: presenters.NoteBook{
-// UUID: b.UUID,
-// Label: b.Label,
-// },
-// User: presenters.NoteUser{
-// UUID: u.UUID,
-// },
-// }
-// }
-//
-// func TestGetNotes(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// anotherUser := testutils.SetupUserData()
-//
-// b1 := database.Book{
-// UserID: user.ID,
-// Label: "js",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
-// b2 := database.Book{
-// UserID: user.ID,
-// Label: "css",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
-// b3 := database.Book{
-// UserID: anotherUser.ID,
-// Label: "css",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
-//
-// n1 := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Body: "n1 content",
-// USN: 11,
-// Deleted: false,
-// AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
-// }
-// testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
-// n2 := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Body: "n2 content",
-// USN: 14,
-// Deleted: false,
-// AddedOn: time.Date(2018, time.August, 11, 22, 0, 0, 0, time.UTC).UnixNano(),
-// }
-// testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2")
-// n3 := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Body: "n3 content",
-// USN: 17,
-// Deleted: false,
-// AddedOn: time.Date(2017, time.January, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
-// }
-// testutils.MustExec(t, testutils.DB.Save(&n3), "preparing n3")
-// n4 := database.Note{
-// UserID: user.ID,
-// BookUUID: b2.UUID,
-// Body: "n4 content",
-// USN: 18,
-// Deleted: false,
-// AddedOn: time.Date(2018, time.September, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
-// }
-// testutils.MustExec(t, testutils.DB.Save(&n4), "preparing n4")
-// n5 := database.Note{
-// UserID: anotherUser.ID,
-// BookUUID: b3.UUID,
-// Body: "n5 content",
-// USN: 19,
-// Deleted: false,
-// AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
-// }
-// testutils.MustExec(t, testutils.DB.Save(&n5), "preparing n5")
-// n6 := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Body: "",
-// USN: 11,
-// Deleted: true,
-// AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
-// }
-// testutils.MustExec(t, testutils.DB.Save(&n6), "preparing n6")
-//
-// // Execute
-// req := testutils.MakeReq(server.URL, "GET", "/notes?year=2018&month=8", "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var payload GetNotesResponse
-// if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
-// t.Fatal(errors.Wrap(err, "decoding payload"))
-// }
-//
-// var n2Record, n1Record database.Note
-// testutils.MustExec(t, testutils.DB.Where("uuid = ?", n2.UUID).First(&n2Record), "finding n2Record")
-// testutils.MustExec(t, testutils.DB.Where("uuid = ?", n1.UUID).First(&n1Record), "finding n1Record")
-//
-// expected := GetNotesResponse{
-// Notes: []presenters.Note{
-// getExpectedNotePayload(n2Record, b1, user),
-// getExpectedNotePayload(n1Record, b1, user),
-// },
-// Total: 2,
-// }
-//
-// assert.DeepEqual(t, payload, expected, "payload mismatch")
-// }
-//
-// func TestGetNote(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// anotherUser := testutils.SetupUserData()
-//
-// b1 := database.Book{
-// UserID: user.ID,
-// Label: "js",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
-//
-// privateNote := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Body: "privateNote content",
-// Public: false,
-// }
-// testutils.MustExec(t, testutils.DB.Save(&privateNote), "preparing privateNote")
-// publicNote := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Body: "publicNote content",
-// Public: true,
-// }
-// testutils.MustExec(t, testutils.DB.Save(&publicNote), "preparing publicNote")
-// deletedNote := database.Note{
-// UserID: user.ID,
-// BookUUID: b1.UUID,
-// Deleted: true,
-// }
-// testutils.MustExec(t, testutils.DB.Save(&deletedNote), "preparing publicNote")
-//
-// t.Run("owner accessing private note", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", privateNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var payload presenters.Note
-// if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
-// t.Fatal(errors.Wrap(err, "decoding payload"))
-// }
-//
-// var n1Record database.Note
-// testutils.MustExec(t, testutils.DB.Where("uuid = ?", privateNote.UUID).First(&n1Record), "finding n1Record")
-//
-// expected := getExpectedNotePayload(n1Record, b1, user)
-// assert.DeepEqual(t, payload, expected, "payload mismatch")
-// })
-//
-// t.Run("owner accessing public note", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", publicNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var payload presenters.Note
-// if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
-// t.Fatal(errors.Wrap(err, "decoding payload"))
-// }
-//
-// var n2Record database.Note
-// testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-//
-// expected := getExpectedNotePayload(n2Record, b1, user)
-// assert.DeepEqual(t, payload, expected, "payload mismatch")
-// })
-//
-// t.Run("non-owner accessing public note", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", publicNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPAuthDo(t, req, anotherUser)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var payload presenters.Note
-// if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
-// t.Fatal(errors.Wrap(err, "decoding payload"))
-// }
-//
-// var n2Record database.Note
-// testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-//
-// expected := getExpectedNotePayload(n2Record, b1, user)
-// assert.DeepEqual(t, payload, expected, "payload mismatch")
-// })
-//
-// t.Run("non-owner accessing private note", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", privateNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPAuthDo(t, req, anotherUser)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
-//
-// body, err := ioutil.ReadAll(res.Body)
-// if err != nil {
-// t.Fatal(errors.Wrap(err, "reading body"))
-// }
-//
-// assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
-// })
-//
-// t.Run("guest accessing public note", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", publicNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var payload presenters.Note
-// if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
-// t.Fatal(errors.Wrap(err, "decoding payload"))
-// }
-//
-// var n2Record database.Note
-// testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-//
-// expected := getExpectedNotePayload(n2Record, b1, user)
-// assert.DeepEqual(t, payload, expected, "payload mismatch")
-// })
-//
-// t.Run("guest accessing private note", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", privateNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
-//
-// body, err := ioutil.ReadAll(res.Body)
-// if err != nil {
-// t.Fatal(errors.Wrap(err, "reading body"))
-// }
-//
-// assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
-// })
-//
-// t.Run("nonexistent", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", "someRandomString")
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
-//
-// body, err := ioutil.ReadAll(res.Body)
-// if err != nil {
-// t.Fatal(errors.Wrap(err, "reading body"))
-// }
-//
-// assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
-// })
-//
-// t.Run("deleted", func(t *testing.T) {
-// // Execute
-// url := fmt.Sprintf("/notes/%s", deletedNote.UUID)
-// req := testutils.MakeReq(server.URL, "GET", url, "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
-//
-// body, err := ioutil.ReadAll(res.Body)
-// if err != nil {
-// t.Fatal(errors.Wrap(err, "reading body"))
-// }
-//
-// assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
-// })
-// }
diff --git a/pkg/server/api/routes.go b/pkg/server/api/routes.go
deleted file mode 100644
index 71e913db..00000000
--- a/pkg/server/api/routes.go
+++ /dev/null
@@ -1,116 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "net/http"
-// "os"
-//
-// "github.com/dnote/dnote/pkg/server/app"
-// "github.com/dnote/dnote/pkg/server/database"
-// "github.com/dnote/dnote/pkg/server/middleware"
-// "github.com/gorilla/mux"
-// "github.com/pkg/errors"
-// )
-//
-// // API is a web API configuration
-// type API struct {
-// App *app.App
-// }
-//
-// // init sets up the application based on the configuration
-// func (a *API) init() error {
-// if err := a.App.Validate(); err != nil {
-// return errors.Wrap(err, "validating the app parameters")
-// }
-//
-// return nil
-// }
-//
-// func applyMiddleware(h http.HandlerFunc, rateLimit bool) http.Handler {
-// ret := h
-// ret = middleware.Logging(ret)
-//
-// if rateLimit && os.Getenv("GO_ENV") != "TEST" {
-// ret = middleware.Limit(ret)
-// }
-//
-// return ret
-// }
-//
-// // NewRouter creates and returns a new router
-// func NewRouter(a *API) (*mux.Router, error) {
-// if err := a.init(); err != nil {
-// return nil, errors.Wrap(err, "initializing app")
-// }
-//
-// proOnly := middleware.AuthParams{ProOnly: true}
-// app := a.App
-//
-// var routes = []middleware.Route{
-// // internal
-// {Method: "GET", Pattern: "/health", HandlerFunc: a.checkHealth, RateLimit: false},
-// {Method: "GET", Pattern: "/me", HandlerFunc: middleware.Auth(app, a.getMe, nil), RateLimit: true},
-// {Method: "POST", Pattern: "/verification-token", HandlerFunc: middleware.Auth(app, a.createVerificationToken, nil), RateLimit: true},
-// {Method: "PATCH", Pattern: "/verify-email", HandlerFunc: a.verifyEmail, RateLimit: true},
-// {Method: "POST", Pattern: "/reset-token", HandlerFunc: a.createResetToken, RateLimit: true},
-// {Method: "PATCH", Pattern: "/reset-password", HandlerFunc: a.resetPassword, RateLimit: true},
-// {Method: "PATCH", Pattern: "/account/profile", HandlerFunc: middleware.Auth(app, a.updateProfile, nil), RateLimit: true},
-// {Method: "PATCH", Pattern: "/account/password", HandlerFunc: middleware.Auth(app, a.updatePassword, nil), RateLimit: true},
-// {Method: "GET", Pattern: "/account/email-preference", HandlerFunc: middleware.TokenAuth(app, a.getEmailPreference, database.TokenTypeEmailPreference, nil), RateLimit: true},
-// {Method: "PATCH", Pattern: "/account/email-preference", HandlerFunc: middleware.TokenAuth(app, a.updateEmailPreference, database.TokenTypeEmailPreference, nil), RateLimit: true},
-// {Method: "GET", Pattern: "/notes", HandlerFunc: middleware.Auth(app, a.getNotes, nil), RateLimit: false},
-// {Method: "GET", Pattern: "/notes/{noteUUID}", HandlerFunc: a.getNote, RateLimit: true},
-// {Method: "GET", Pattern: "/calendar", HandlerFunc: middleware.Auth(app, a.getCalendar, nil), RateLimit: true},
-//
-// // v3
-// {Method: "GET", Pattern: "/v3/sync/fragment", HandlerFunc: middleware.Cors(middleware.Auth(app, a.GetSyncFragment, &proOnly)), RateLimit: false},
-// {Method: "GET", Pattern: "/v3/sync/state", HandlerFunc: middleware.Cors(middleware.Auth(app, a.GetSyncState, &proOnly)), RateLimit: false},
-// {Method: "OPTIONS", Pattern: "/v3/books", HandlerFunc: middleware.Cors(a.BooksOptions), RateLimit: true},
-// {Method: "GET", Pattern: "/v3/books", HandlerFunc: middleware.Cors(middleware.Auth(app, a.GetBooks, &proOnly)), RateLimit: true},
-// {Method: "GET", Pattern: "/v3/books/{bookUUID}", HandlerFunc: middleware.Cors(middleware.Auth(app, a.GetBook, &proOnly)), RateLimit: true},
-// {Method: "POST", Pattern: "/v3/books", HandlerFunc: middleware.Cors(middleware.Auth(app, a.CreateBook, &proOnly)), RateLimit: false},
-// {Method: "PATCH", Pattern: "/v3/books/{bookUUID}", HandlerFunc: middleware.Cors(middleware.Auth(app, a.UpdateBook, &proOnly)), RateLimit: false},
-// {Method: "DELETE", Pattern: "/v3/books/{bookUUID}", HandlerFunc: middleware.Cors(middleware.Auth(app, a.DeleteBook, &proOnly)), RateLimit: false},
-// {Method: "OPTIONS", Pattern: "/v3/notes", HandlerFunc: middleware.Cors(a.NotesOptions), RateLimit: true},
-// {Method: "POST", Pattern: "/v3/notes", HandlerFunc: middleware.Cors(middleware.Auth(app, a.CreateNote, &proOnly)), RateLimit: false},
-// {Method: "PATCH", Pattern: "/v3/notes/{noteUUID}", HandlerFunc: middleware.Auth(app, a.UpdateNote, &proOnly), RateLimit: false},
-// {Method: "DELETE", Pattern: "/v3/notes/{noteUUID}", HandlerFunc: middleware.Auth(app, a.DeleteNote, &proOnly), RateLimit: false},
-// {Method: "POST", Pattern: "/v3/signin", HandlerFunc: middleware.Cors(a.signin), RateLimit: true},
-// {Method: "OPTIONS", Pattern: "/v3/signout", HandlerFunc: middleware.Cors(a.signoutOptions), RateLimit: true},
-// {Method: "POST", Pattern: "/v3/signout", HandlerFunc: middleware.Cors(a.signout), RateLimit: true},
-// {Method: "POST", Pattern: "/v3/register", HandlerFunc: a.register, RateLimit: true},
-// }
-//
-// router := mux.NewRouter().StrictSlash(true)
-//
-// router.PathPrefix("/v1").Handler(applyMiddleware(middleware.NotSupported, true))
-// router.PathPrefix("/v2").Handler(applyMiddleware(middleware.NotSupported, true))
-//
-// for _, route := range routes {
-// handler := route.HandlerFunc
-//
-// router.
-// Methods(route.Method).
-// Path(route.Pattern).
-// Handler(applyMiddleware(handler, route.RateLimit))
-// }
-//
-// return router, nil
-// }
diff --git a/pkg/server/api/routes_test.go b/pkg/server/api/routes_test.go
deleted file mode 100644
index 7b8f5ec2..00000000
--- a/pkg/server/api/routes_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "fmt"
- "net/http"
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/config"
- "github.com/dnote/dnote/pkg/server/mailer"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-func TestNotSupportedVersions(t *testing.T) {
- testCases := []struct {
- path string
- }{
- // v1
- {
- path: "/v1",
- },
- {
- path: "/v1/foo",
- },
- {
- path: "/v1/bar/baz",
- },
- // v2
- {
- path: "/v2",
- },
- {
- path: "/v2/foo",
- },
- {
- path: "/v2/bar/baz",
- },
- }
-
- // setup
- server := MustNewServer(t, &app.App{
- DB: &gorm.DB{},
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- for _, tc := range testCases {
- t.Run(tc.path, func(t *testing.T) {
- // execute
- req := testutils.MakeReq(server.URL, "GET", tc.path, "")
- res := testutils.HTTPDo(t, req)
-
- // test
- assert.Equal(t, res.StatusCode, http.StatusGone, "status code mismatch")
- })
- }
-}
-
-func TestNewRouter_AppValidate(t *testing.T) {
- c := config.Load()
-
- configWithoutWebURL := config.Load()
- configWithoutWebURL.WebURL = ""
-
- testCases := []struct {
- app app.App
- expectedErr error
- }{
- {
- app: app.App{
- DB: &gorm.DB{},
- Clock: clock.NewMock(),
- EmailTemplates: mailer.Templates{},
- EmailBackend: &testutils.MockEmailbackendImplementation{},
- Config: c,
- },
- expectedErr: nil,
- },
- {
- app: app.App{
- DB: nil,
- Clock: clock.NewMock(),
- EmailTemplates: mailer.Templates{},
- EmailBackend: &testutils.MockEmailbackendImplementation{},
- Config: c,
- },
- expectedErr: app.ErrEmptyDB,
- },
- {
- app: app.App{
- DB: &gorm.DB{},
- Clock: nil,
- EmailTemplates: mailer.Templates{},
- EmailBackend: &testutils.MockEmailbackendImplementation{},
- Config: c,
- },
- expectedErr: app.ErrEmptyClock,
- },
- {
- app: app.App{
- DB: &gorm.DB{},
- Clock: clock.NewMock(),
- EmailTemplates: nil,
- EmailBackend: &testutils.MockEmailbackendImplementation{},
- Config: c,
- },
- expectedErr: app.ErrEmptyEmailTemplates,
- },
- {
- app: app.App{
- DB: &gorm.DB{},
- Clock: clock.NewMock(),
- EmailTemplates: mailer.Templates{},
- EmailBackend: nil,
- Config: c,
- },
- expectedErr: app.ErrEmptyEmailBackend,
- },
- {
- app: app.App{
- DB: &gorm.DB{},
- Clock: clock.NewMock(),
- EmailTemplates: mailer.Templates{},
- EmailBackend: &testutils.MockEmailbackendImplementation{},
- Config: configWithoutWebURL,
- },
- expectedErr: app.ErrEmptyWebURL,
- },
- }
-
- for idx, tc := range testCases {
- t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
- api := API{App: &tc.app}
- _, err := NewRouter(&api)
-
- assert.Equal(t, errors.Cause(err), tc.expectedErr, "error mismatch")
- })
- }
-}
diff --git a/pkg/server/api/testutils.go b/pkg/server/api/testutils.go
deleted file mode 100644
index e2541ab1..00000000
--- a/pkg/server/api/testutils.go
+++ /dev/null
@@ -1,48 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "net/http/httptest"
-// "testing"
-//
-// "github.com/dnote/dnote/pkg/server/app"
-// "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, appParams *app.App) *httptest.Server {
-// api := NewTestAPI(appParams)
-// r, err := NewRouter(&api)
-// if err != nil {
-// t.Fatal(errors.Wrap(err, "initializing server"))
-// }
-//
-// server := httptest.NewServer(r)
-//
-// return server
-// }
-//
-// // NewTestAPI returns a new API for test
-// func NewTestAPI(appParams *app.App) API {
-// a := app.NewTest(appParams)
-//
-// return API{App: &a}
-// }
diff --git a/pkg/server/api/user.go b/pkg/server/api/user.go
deleted file mode 100644
index 35b0c55c..00000000
--- a/pkg/server/api/user.go
+++ /dev/null
@@ -1,394 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "encoding/json"
-// "net/http"
-// "time"
-//
-// "github.com/dnote/dnote/pkg/server/database"
-// "github.com/dnote/dnote/pkg/server/middleware"
-// "github.com/dnote/dnote/pkg/server/helpers"
-// "github.com/dnote/dnote/pkg/server/log"
-// "github.com/dnote/dnote/pkg/server/mailer"
-// "github.com/dnote/dnote/pkg/server/presenters"
-// "github.com/dnote/dnote/pkg/server/session"
-// "github.com/dnote/dnote/pkg/server/token"
-// "github.com/jinzhu/gorm"
-// "github.com/pkg/errors"
-// "golang.org/x/crypto/bcrypt"
-// )
-//
-// type updateProfilePayload struct {
-// Email string `json:"email"`
-// Password string `json:"password"`
-// }
-//
-// // updateProfile updates user
-// func (a *API) updateProfile(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var account database.Account
-// if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
-// middleware.DoError(w, "getting account", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var params updateProfilePayload
-// err := json.NewDecoder(r.Body).Decode(¶ms)
-// if err != nil {
-// http.Error(w, errors.Wrap(err, "invalid params").Error(), http.StatusBadRequest)
-// return
-// }
-//
-// password := []byte(params.Password)
-// if err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), password); err != nil {
-// log.WithFields(log.Fields{
-// "user_id": user.ID,
-// }).Warn("invalid email update attempt")
-// http.Error(w, "Wrong password", http.StatusUnauthorized)
-// return
-// }
-//
-// // Validate
-// if len(params.Email) > 60 {
-// http.Error(w, "Email is too long", http.StatusBadRequest)
-// return
-// }
-//
-// tx := a.App.DB.Begin()
-// if err := tx.Save(&user).Error; err != nil {
-// tx.Rollback()
-// middleware.DoError(w, "saving user", err, http.StatusInternalServerError)
-// return
-// }
-//
-// // check if email was changed
-// if params.Email != account.Email.String {
-// account.EmailVerified = false
-// }
-// account.Email.String = params.Email
-//
-// if err := tx.Save(&account).Error; err != nil {
-// tx.Rollback()
-// middleware.DoError(w, "saving account", err, http.StatusInternalServerError)
-// return
-// }
-//
-// tx.Commit()
-//
-// a.respondWithSession(a.App.DB, w, user.ID, http.StatusOK)
-// }
-//
-// type updateEmailPayload struct {
-// NewEmail string `json:"new_email"`
-// NewCipherKeyEnc string `json:"new_cipher_key_enc"`
-// OldAuthKey string `json:"old_auth_key"`
-// NewAuthKey string `json:"new_auth_key"`
-// }
-//
-// func respondWithCalendar(db *gorm.DB, w http.ResponseWriter, userID int) {
-// rows, err := db.Table("notes").Select("COUNT(id), date(to_timestamp(added_on/1000000000)) AS added_date").
-// Where("user_id = ?", userID).
-// Group("added_date").
-// Order("added_date DESC").Rows()
-//
-// if err != nil {
-// middleware.DoError(w, "Failed to count lessons", err, http.StatusInternalServerError)
-// return
-// }
-//
-// payload := map[string]int{}
-//
-// for rows.Next() {
-// var count int
-// var d time.Time
-//
-// if err := rows.Scan(&count, &d); err != nil {
-// middleware.DoError(w, "counting notes", err, http.StatusInternalServerError)
-// }
-// payload[d.Format("2006-1-2")] = count
-// }
-//
-// middleware.RespondJSON(w, http.StatusOK, payload)
-// }
-//
-// func (a *API) getCalendar(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// respondWithCalendar(a.App.DB, w, user.ID)
-// }
-//
-// func (a *API) createVerificationToken(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var account database.Account
-// err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error
-// if err != nil {
-// middleware.DoError(w, "finding account", err, http.StatusInternalServerError)
-// return
-// }
-//
-// if account.EmailVerified {
-// http.Error(w, "Email already verified", http.StatusGone)
-// return
-// }
-// if account.Email.String == "" {
-// http.Error(w, "Email not set", http.StatusUnprocessableEntity)
-// return
-// }
-//
-// tok, err := token.Create(a.App.DB, account.UserID, database.TokenTypeEmailVerification)
-// if err != nil {
-// middleware.DoError(w, "saving token", err, http.StatusInternalServerError)
-// return
-// }
-//
-// if err := a.App.SendVerificationEmail(account.Email.String, tok.Value); err != nil {
-// if errors.Cause(err) == mailer.ErrSMTPNotConfigured {
-// middleware.RespondInvalidSMTPConfig(w)
-// } else {
-// middleware.DoError(w, errors.Wrap(err, "sending verification email").Error(), nil, http.StatusInternalServerError)
-// }
-//
-// return
-// }
-//
-// w.WriteHeader(http.StatusCreated)
-// }
-//
-// type verifyEmailPayload struct {
-// Token string `json:"token"`
-// }
-//
-// func (a *API) verifyEmail(w http.ResponseWriter, r *http.Request) {
-// var params verifyEmailPayload
-// if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
-// middleware.DoError(w, "decoding payload", err, http.StatusInternalServerError)
-// return
-// }
-//
-// var token database.Token
-// if err := a.App.DB.
-// Where("value = ? AND type = ?", params.Token, database.TokenTypeEmailVerification).
-// First(&token).Error; err != nil {
-// http.Error(w, "invalid token", http.StatusBadRequest)
-// return
-// }
-//
-// if token.UsedAt != nil {
-// http.Error(w, "invalid token", http.StatusBadRequest)
-// return
-// }
-//
-// // Expire after ttl
-// if time.Since(token.CreatedAt).Minutes() > 30 {
-// http.Error(w, "This link has been expired. Please request a new link.", http.StatusGone)
-// return
-// }
-//
-// var account database.Account
-// if err := a.App.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil {
-// middleware.DoError(w, "finding account", err, http.StatusInternalServerError)
-// return
-// }
-// if account.EmailVerified {
-// http.Error(w, "Already verified", http.StatusConflict)
-// return
-// }
-//
-// tx := a.App.DB.Begin()
-// account.EmailVerified = true
-// if err := tx.Save(&account).Error; err != nil {
-// tx.Rollback()
-// middleware.DoError(w, "updating email_verified", err, http.StatusInternalServerError)
-// return
-// }
-// if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil {
-// tx.Rollback()
-// middleware.DoError(w, "updating reset token", err, http.StatusInternalServerError)
-// return
-// }
-// tx.Commit()
-//
-// var user database.User
-// if err := a.App.DB.Where("id = ?", token.UserID).First(&user).Error; err != nil {
-// middleware.DoError(w, "finding user", err, http.StatusInternalServerError)
-// return
-// }
-//
-// s := session.New(user, account)
-// middleware.RespondJSON(w, http.StatusOK, s)
-// }
-//
-// type emailPreferernceParams struct {
-// InactiveReminder *bool `json:"inactive_reminder"`
-// ProductUpdate *bool `json:"product_update"`
-// }
-//
-// func (p emailPreferernceParams) getInactiveReminder() bool {
-// if p.InactiveReminder == nil {
-// return false
-// }
-//
-// return *p.InactiveReminder
-// }
-//
-// func (p emailPreferernceParams) getProductUpdate() bool {
-// if p.ProductUpdate == nil {
-// return false
-// }
-//
-// return *p.ProductUpdate
-// }
-//
-// func (a *API) updateEmailPreference(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var params emailPreferernceParams
-// if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
-// middleware.DoError(w, "decoding payload", err, http.StatusInternalServerError)
-// return
-// }
-//
-// var pref database.EmailPreference
-// if err := a.App.DB.Where(database.EmailPreference{UserID: user.ID}).FirstOrCreate(&pref).Error; err != nil {
-// middleware.DoError(w, "finding pref", err, http.StatusInternalServerError)
-// return
-// }
-//
-// tx := a.App.DB.Begin()
-//
-// if params.InactiveReminder != nil {
-// pref.InactiveReminder = params.getInactiveReminder()
-// }
-// if params.ProductUpdate != nil {
-// pref.ProductUpdate = params.getProductUpdate()
-// }
-//
-// if err := tx.Save(&pref).Error; err != nil {
-// tx.Rollback()
-// middleware.DoError(w, "saving pref", err, http.StatusInternalServerError)
-// return
-// }
-//
-// token, ok := r.Context().Value(helpers.KeyToken).(database.Token)
-// if ok {
-// // Mark token as used if the user was authenticated by token
-// if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil {
-// tx.Rollback()
-// middleware.DoError(w, "updating reset token", err, http.StatusInternalServerError)
-// return
-// }
-// }
-//
-// tx.Commit()
-//
-// middleware.RespondJSON(w, http.StatusOK, pref)
-// }
-//
-// func (a *API) getEmailPreference(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var pref database.EmailPreference
-// if err := a.App.DB.Where(database.EmailPreference{UserID: user.ID}).First(&pref).Error; err != nil {
-// middleware.DoError(w, "finding pref", err, http.StatusInternalServerError)
-// return
-// }
-//
-// presented := presenters.PresentEmailPreference(pref)
-// middleware.RespondJSON(w, http.StatusOK, presented)
-// }
-//
-// type updatePasswordPayload struct {
-// OldPassword string `json:"old_password"`
-// NewPassword string `json:"new_password"`
-// }
-//
-// func (a *API) updatePassword(w http.ResponseWriter, r *http.Request) {
-// user, ok := r.Context().Value(helpers.KeyUser).(database.User)
-// if !ok {
-// middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// var params updatePasswordPayload
-// if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
-// http.Error(w, err.Error(), http.StatusBadRequest)
-// return
-// }
-// if params.OldPassword == "" || params.NewPassword == "" {
-// http.Error(w, "invalid params", http.StatusBadRequest)
-// return
-// }
-//
-// var account database.Account
-// if err := a.App.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil {
-// middleware.DoError(w, "getting account", nil, http.StatusInternalServerError)
-// return
-// }
-//
-// password := []byte(params.OldPassword)
-// if err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), password); err != nil {
-// log.WithFields(log.Fields{
-// "user_id": user.ID,
-// }).Warn("invalid password update attempt")
-// http.Error(w, "Wrong password", http.StatusUnauthorized)
-// return
-// }
-//
-// if err := validatePassword(params.NewPassword); err != nil {
-// http.Error(w, err.Error(), http.StatusBadRequest)
-// return
-// }
-//
-// hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(params.NewPassword), bcrypt.DefaultCost)
-// if err != nil {
-// http.Error(w, errors.Wrap(err, "hashing password").Error(), http.StatusInternalServerError)
-// return
-// }
-//
-// if err := a.App.DB.Model(&account).Update("password", string(hashedNewPassword)).Error; err != nil {
-// http.Error(w, errors.Wrap(err, "updating password").Error(), http.StatusInternalServerError)
-// return
-// }
-//
-// w.WriteHeader(http.StatusOK)
-// }
diff --git a/pkg/server/api/user_test.go b/pkg/server/api/user_test.go
deleted file mode 100644
index c2135688..00000000
--- a/pkg/server/api/user_test.go
+++ /dev/null
@@ -1,691 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-// import (
-// "encoding/json"
-// "fmt"
-// "net/http"
-// "testing"
-// "time"
-//
-// "github.com/dnote/dnote/pkg/assert"
-// "github.com/dnote/dnote/pkg/clock"
-// "github.com/dnote/dnote/pkg/server/app"
-// "github.com/dnote/dnote/pkg/server/database"
-// "github.com/dnote/dnote/pkg/server/presenters"
-// "github.com/dnote/dnote/pkg/server/testutils"
-// "github.com/pkg/errors"
-// "golang.org/x/crypto/bcrypt"
-// )
-//
-// func TestUpdatePassword(t *testing.T) {
-// t.Run("success", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// testutils.SetupAccountData(user, "alice@example.com", "oldpassword")
-//
-// // Execute
-// dat := `{"old_password": "oldpassword", "new_password": "newpassword"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/password", dat)
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismsatch")
-//
-// var account database.Account
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-//
-// passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte("newpassword"))
-// assert.Equal(t, passwordErr, nil, "Password mismatch")
-// })
-//
-// t.Run("old password mismatch", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword")
-//
-// // Execute
-// dat := `{"old_password": "randompassword", "new_password": "newpassword"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/password", dat)
-// res := testutils.HTTPAuthDo(t, req, u)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "Status code mismsatch")
-//
-// var account database.Account
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&account), "finding account")
-// assert.Equal(t, a.Password.String, account.Password.String, "password should not have been updated")
-// })
-//
-// t.Run("password too short", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword")
-//
-// // Execute
-// dat := `{"old_password": "oldpassword", "new_password": "a"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/password", dat)
-// res := testutils.HTTPAuthDo(t, req, u)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status code mismsatch")
-//
-// var account database.Account
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&account), "finding account")
-// assert.Equal(t, a.Password.String, account.Password.String, "password should not have been updated")
-// })
-// }
-//
-// func TestCreateVerificationToken(t *testing.T) {
-// t.Run("success", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// emailBackend := testutils.MockEmailbackendImplementation{}
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// EmailBackend: &emailBackend,
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// testutils.SetupAccountData(user, "alice@example.com", "pass1234")
-//
-// // Execute
-// req := testutils.MakeReq(server.URL, "POST", "/verification-token", "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusCreated, "status code mismatch")
-//
-// var account database.Account
-// var token database.Token
-// var tokenCount int
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
-// testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token")
-//
-// assert.Equal(t, account.EmailVerified, false, "email_verified should not have been updated")
-// assert.NotEqual(t, token.Value, "", "token Value mismatch")
-// assert.Equal(t, tokenCount, 1, "token count mismatch")
-// assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token UsedAt mismatch")
-// assert.Equal(t, len(emailBackend.Emails), 1, "email queue count mismatch")
-// })
-//
-// t.Run("already verified", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// a := testutils.SetupAccountData(user, "alice@example.com", "pass1234")
-// a.EmailVerified = true
-// testutils.MustExec(t, testutils.DB.Save(&a), "preparing account")
-//
-// // Execute
-// req := testutils.MakeReq(server.URL, "POST", "/verification-token", "")
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusGone, "Status code mismatch")
-//
-// var account database.Account
-// var tokenCount int
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-// testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token")
-//
-// assert.Equal(t, account.EmailVerified, true, "email_verified should not have been updated")
-// assert.Equal(t, tokenCount, 0, "token count mismatch")
-// })
-// }
-//
-// func TestVerifyEmail(t *testing.T) {
-// t.Run("success", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// testutils.SetupAccountData(user, "alice@example.com", "pass1234")
-// tok := database.Token{
-// UserID: user.ID,
-// Type: database.TokenTypeEmailVerification,
-// Value: "someTokenValue",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// dat := `{"token": "someTokenValue"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/verify-email", dat)
-//
-// // Execute
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "Status code mismatch")
-//
-// var account database.Account
-// var token database.Token
-// var tokenCount int
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
-// testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token")
-//
-// assert.Equal(t, account.EmailVerified, true, "email_verified mismatch")
-// assert.NotEqual(t, token.Value, "", "token value should not have been updated")
-// assert.Equal(t, tokenCount, 1, "token count mismatch")
-// assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
-// })
-//
-// t.Run("used token", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// testutils.SetupAccountData(user, "alice@example.com", "pass1234")
-//
-// usedAt := time.Now().Add(time.Hour * -11).UTC()
-// tok := database.Token{
-// UserID: user.ID,
-// Type: database.TokenTypeEmailVerification,
-// Value: "someTokenValue",
-// UsedAt: &usedAt,
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// dat := `{"token": "someTokenValue"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/verify-email", dat)
-//
-// // Execute
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusBadRequest, "")
-//
-// var account database.Account
-// var token database.Token
-// var tokenCount int
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
-// testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token")
-//
-// assert.Equal(t, account.EmailVerified, false, "email_verified mismatch")
-// assert.NotEqual(t, token.UsedAt, nil, "token used_at mismatch")
-// assert.Equal(t, tokenCount, 1, "token count mismatch")
-// assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
-// })
-//
-// t.Run("expired token", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// testutils.SetupAccountData(user, "alice@example.com", "pass1234")
-//
-// tok := database.Token{
-// UserID: user.ID,
-// Type: database.TokenTypeEmailVerification,
-// Value: "someTokenValue",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-// testutils.MustExec(t, testutils.DB.Model(&tok).Update("created_at", time.Now().Add(time.Minute*-31)), "Failed to prepare token created_at")
-//
-// dat := `{"token": "someTokenValue"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/verify-email", dat)
-//
-// // Execute
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusGone, "")
-//
-// var account database.Account
-// var token database.Token
-// var tokenCount int
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
-// testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token")
-//
-// assert.Equal(t, account.EmailVerified, false, "email_verified mismatch")
-// assert.Equal(t, tokenCount, 1, "token count mismatch")
-// assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token should have not been used")
-// })
-//
-// t.Run("already verified", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// user := testutils.SetupUserData()
-// a := testutils.SetupAccountData(user, "alice@example.com", "oldpass1234")
-// a.EmailVerified = true
-// testutils.MustExec(t, testutils.DB.Save(&a), "preparing account")
-//
-// tok := database.Token{
-// UserID: user.ID,
-// Type: database.TokenTypeEmailVerification,
-// Value: "someTokenValue",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// dat := `{"token": "someTokenValue"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/verify-email", dat)
-//
-// // Execute
-// res := testutils.HTTPAuthDo(t, req, user)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusConflict, "")
-//
-// var account database.Account
-// var token database.Token
-// var tokenCount int
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", user.ID).First(&account), "finding account")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ? AND type = ?", user.ID, database.TokenTypeEmailVerification).First(&token), "finding token")
-// testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&tokenCount), "counting token")
-//
-// assert.Equal(t, account.EmailVerified, true, "email_verified mismatch")
-// assert.Equal(t, tokenCount, 1, "token count mismatch")
-// assert.Equal(t, token.UsedAt, (*time.Time)(nil), "token should have not been used")
-// })
-// }
-//
-// func TestUpdateEmail(t *testing.T) {
-// t.Run("success", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// a := testutils.SetupAccountData(u, "alice@example.com", "pass1234")
-// a.EmailVerified = true
-// testutils.MustExec(t, testutils.DB.Save(&a), "updating email_verified")
-//
-// // Execute
-// dat := `{"email": "alice-new@example.com", "password": "pass1234"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/profile", dat)
-// res := testutils.HTTPAuthDo(t, req, u)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var user database.User
-// var account database.Account
-// testutils.MustExec(t, testutils.DB.Where("id = ?", u.ID).First(&user), "finding user")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&account), "finding account")
-//
-// assert.Equal(t, account.Email.String, "alice-new@example.com", "email mismatch")
-// assert.Equal(t, account.EmailVerified, false, "EmailVerified mismatch")
-// })
-//
-// t.Run("password mismatch", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// a := testutils.SetupAccountData(u, "alice@example.com", "pass1234")
-// a.EmailVerified = true
-// testutils.MustExec(t, testutils.DB.Save(&a), "updating email_verified")
-//
-// // Execute
-// dat := `{"email": "alice-new@example.com", "password": "wrongpassword"}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/profile", dat)
-// res := testutils.HTTPAuthDo(t, req, u)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "Status code mismsatch")
-//
-// var user database.User
-// var account database.Account
-// testutils.MustExec(t, testutils.DB.Where("id = ?", u.ID).First(&user), "finding user")
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&account), "finding account")
-//
-// assert.Equal(t, account.Email.String, "alice@example.com", "email mismatch")
-// assert.Equal(t, account.EmailVerified, true, "EmailVerified mismatch")
-// })
-// }
-//
-// func TestUpdateEmailPreference(t *testing.T) {
-// t.Run("with login", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// testutils.SetupEmailPreferenceData(u, false)
-//
-// // Execute
-// dat := `{"inactive_reminder": true}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/email-preference", dat)
-// res := testutils.HTTPAuthDo(t, req, u)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var preference database.EmailPreference
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding account")
-// assert.Equal(t, preference.InactiveReminder, true, "preference mismatch")
-// })
-//
-// t.Run("with an unused token", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// testutils.SetupEmailPreferenceData(u, false)
-// tok := database.Token{
-// UserID: u.ID,
-// Type: database.TokenTypeEmailPreference,
-// Value: "someTokenValue",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// // Execute
-// dat := `{"inactive_reminder": true}`
-// url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
-// req := testutils.MakeReq(server.URL, "PATCH", url, dat)
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var preference database.EmailPreference
-// var preferenceCount int
-// var token database.Token
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding preference")
-// testutils.MustExec(t, testutils.DB.Model(database.EmailPreference{}).Count(&preferenceCount), "counting preference")
-// testutils.MustExec(t, testutils.DB.Where("id = ?", tok.ID).First(&token), "failed to find token")
-//
-// assert.Equal(t, preferenceCount, 1, "preference count mismatch")
-// assert.Equal(t, preference.InactiveReminder, true, "email mismatch")
-// assert.NotEqual(t, token.UsedAt, (*time.Time)(nil), "token should have been used")
-// })
-//
-// t.Run("with nonexistent token", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// testutils.SetupEmailPreferenceData(u, true)
-// tok := database.Token{
-// UserID: u.ID,
-// Type: database.TokenTypeEmailPreference,
-// Value: "someTokenValue",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// dat := `{"inactive_reminder": false}`
-// url := fmt.Sprintf("/account/email-preference?token=%s", "someNonexistentToken")
-// req := testutils.MakeReq(server.URL, "PATCH", url, dat)
-//
-// // Execute
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
-//
-// var preference database.EmailPreference
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding preference")
-// assert.Equal(t, preference.InactiveReminder, true, "email mismatch")
-// })
-//
-// t.Run("with expired token", func(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// testutils.SetupEmailPreferenceData(u, true)
-//
-// usedAt := time.Now().Add(-11 * time.Minute)
-// tok := database.Token{
-// UserID: u.ID,
-// Type: database.TokenTypeEmailPreference,
-// Value: "someTokenValue",
-// UsedAt: &usedAt,
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// // Execute
-// dat := `{"inactive_reminder": false}`
-// url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
-// req := testutils.MakeReq(server.URL, "PATCH", url, dat)
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
-//
-// var preference database.EmailPreference
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding preference")
-// assert.Equal(t, preference.InactiveReminder, true, "email mismatch")
-// })
-//
-// t.Run("with a used but unexpired token", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// testutils.SetupEmailPreferenceData(u, true)
-// usedAt := time.Now().Add(-9 * time.Minute)
-// tok := database.Token{
-// UserID: u.ID,
-// Type: database.TokenTypeEmailPreference,
-// Value: "someTokenValue",
-// UsedAt: &usedAt,
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// dat := `{"inactive_reminder": false}`
-// url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
-// req := testutils.MakeReq(server.URL, "PATCH", url, dat)
-//
-// // Execute
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var preference database.EmailPreference
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding preference")
-// assert.Equal(t, preference.InactiveReminder, false, "InactiveReminder mismatch")
-// })
-//
-// t.Run("no user and no token", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// testutils.SetupEmailPreferenceData(u, true)
-//
-// // Execute
-// dat := `{"inactive_reminder": false}`
-// req := testutils.MakeReq(server.URL, "PATCH", "/account/email-preference", dat)
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
-//
-// var preference database.EmailPreference
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding preference")
-// assert.Equal(t, preference.InactiveReminder, true, "email mismatch")
-// })
-//
-// t.Run("create a record if not exists", func(t *testing.T) {
-//
-// defer testutils.ClearData(testutils.DB)
-//
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// tok := database.Token{
-// UserID: u.ID,
-// Type: database.TokenTypeEmailPreference,
-// Value: "someTokenValue",
-// }
-// testutils.MustExec(t, testutils.DB.Save(&tok), "preparing token")
-//
-// // Execute
-// dat := `{"inactive_reminder": false}`
-// url := fmt.Sprintf("/account/email-preference?token=%s", "someTokenValue")
-// req := testutils.MakeReq(server.URL, "PATCH", url, dat)
-// res := testutils.HTTPDo(t, req)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var preferenceCount int
-// testutils.MustExec(t, testutils.DB.Model(database.EmailPreference{}).Count(&preferenceCount), "counting preference")
-// assert.Equal(t, preferenceCount, 1, "preference count mismatch")
-//
-// var preference database.EmailPreference
-// testutils.MustExec(t, testutils.DB.Where("user_id = ?", u.ID).First(&preference), "finding preference")
-// assert.Equal(t, preference.InactiveReminder, false, "email mismatch")
-// })
-// }
-//
-// func TestGetEmailPreference(t *testing.T) {
-// defer testutils.ClearData(testutils.DB)
-// // Setup
-// server := MustNewServer(t, &app.App{
-//
-// Clock: clock.NewMock(),
-// })
-// defer server.Close()
-//
-// u := testutils.SetupUserData()
-// pref := testutils.SetupEmailPreferenceData(u, true)
-//
-// // Execute
-// req := testutils.MakeReq(server.URL, "GET", "/account/email-preference", "")
-// res := testutils.HTTPAuthDo(t, req, u)
-//
-// // Test
-// assert.StatusCodeEquals(t, res, http.StatusOK, "")
-//
-// var got presenters.EmailPreference
-// if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
-// t.Fatal(errors.Wrap(err, "decoding payload"))
-// }
-//
-// expected := presenters.EmailPreference{
-// InactiveReminder: pref.InactiveReminder,
-// ProductUpdate: pref.ProductUpdate,
-// CreatedAt: presenters.FormatTS(pref.CreatedAt),
-// UpdatedAt: presenters.FormatTS(pref.UpdatedAt),
-// }
-// assert.DeepEqual(t, got, expected, "payload mismatch")
-// }
diff --git a/pkg/server/api/v3_auth.go b/pkg/server/api/v3_auth.go
deleted file mode 100644
index 580f82e1..00000000
--- a/pkg/server/api/v3_auth.go
+++ /dev/null
@@ -1,226 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "encoding/json"
- "net/http"
- "time"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/handlers"
- "github.com/dnote/dnote/pkg/server/log"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
- "golang.org/x/crypto/bcrypt"
-)
-
-// ErrLoginFailure is an error for failed login
-var ErrLoginFailure = errors.New("Wrong email and password combination")
-
-// SessionResponse is a response containing a session information
-type SessionResponse struct {
- Key string `json:"key"`
- ExpiresAt int64 `json:"expires_at"`
-}
-
-func setSessionCookie(w http.ResponseWriter, key string, expires time.Time) {
- cookie := http.Cookie{
- Name: "id",
- Value: key,
- Expires: expires,
- Path: "/",
- HttpOnly: true,
- }
- http.SetCookie(w, &cookie)
-}
-
-func touchLastLoginAt(db *gorm.DB, user database.User) error {
- t := time.Now()
- if err := db.Model(&user).Update(database.User{LastLoginAt: &t}).Error; err != nil {
- return errors.Wrap(err, "updating last_login_at")
- }
-
- return nil
-}
-
-type signinPayload struct {
- Email string `json:"email"`
- Password string `json:"password"`
-}
-
-func (a *API) signin(w http.ResponseWriter, r *http.Request) {
- var params signinPayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
- if params.Email == "" || params.Password == "" {
- http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
- return
- }
-
- var account database.Account
- conn := a.App.DB.Where("email = ?", params.Email).First(&account)
- if conn.RecordNotFound() {
- http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
- return
- } else if conn.Error != nil {
- handlers.DoError(w, "getting user", err, http.StatusInternalServerError)
- return
- }
-
- password := []byte(params.Password)
- err = bcrypt.CompareHashAndPassword([]byte(account.Password.String), password)
- if err != nil {
- http.Error(w, ErrLoginFailure.Error(), http.StatusUnauthorized)
- return
- }
-
- var user database.User
- err = a.App.DB.Where("id = ?", account.UserID).First(&user).Error
- if err != nil {
- handlers.DoError(w, "finding user", err, http.StatusInternalServerError)
- return
- }
-
- err = a.App.TouchLastLoginAt(user, a.App.DB)
- if err != nil {
- http.Error(w, errors.Wrap(err, "touching login timestamp").Error(), http.StatusInternalServerError)
- return
- }
-
- a.respondWithSession(a.App.DB, w, account.UserID, http.StatusOK)
-}
-
-func (a *API) signoutOptions(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "POST")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
-
-func (a *API) signout(w http.ResponseWriter, r *http.Request) {
- key, err := handlers.GetCredential(r)
- if err != nil {
- handlers.DoError(w, "getting credential", nil, http.StatusInternalServerError)
- return
- }
-
- if key == "" {
- w.WriteHeader(http.StatusNoContent)
- return
- }
-
- err = a.App.DeleteSession(key)
- if err != nil {
- handlers.DoError(w, "deleting session", nil, http.StatusInternalServerError)
- return
- }
-
- handlers.UnsetSessionCookie(w)
- w.WriteHeader(http.StatusNoContent)
-}
-
-type registerPayload struct {
- Email string `json:"email"`
- Password string `json:"password"`
-}
-
-func validateRegisterPayload(p registerPayload) error {
- if p.Email == "" {
- return errors.New("email is required")
- }
- if len(p.Password) < 8 {
- return errors.New("Password should be longer than 8 characters")
- }
-
- return nil
-}
-
-func parseRegisterPaylaod(r *http.Request) (registerPayload, error) {
- var ret registerPayload
- if err := json.NewDecoder(r.Body).Decode(&ret); err != nil {
- return ret, errors.Wrap(err, "decoding json")
- }
-
- return ret, nil
-}
-
-func (a *API) register(w http.ResponseWriter, r *http.Request) {
- if a.App.Config.DisableRegistration {
- handlers.RespondForbidden(w)
- return
- }
-
- params, err := parseRegisterPaylaod(r)
- if err != nil {
- http.Error(w, "invalid payload", http.StatusBadRequest)
- return
- }
- if err := validateRegisterPayload(params); err != nil {
- http.Error(w, err.Error(), http.StatusBadRequest)
- return
- }
-
- var count int
- if err := a.App.DB.Model(database.Account{}).Where("email = ?", params.Email).Count(&count).Error; err != nil {
- handlers.DoError(w, "checking duplicate user", err, http.StatusInternalServerError)
- return
- }
- if count > 0 {
- http.Error(w, "Duplicate email", http.StatusBadRequest)
- return
- }
-
- user, err := a.App.CreateUser(params.Email, params.Password)
- if err != nil {
- handlers.DoError(w, "creating user", err, http.StatusInternalServerError)
- return
- }
-
- a.respondWithSession(a.App.DB, w, user.ID, http.StatusCreated)
-
- if err := a.App.SendWelcomeEmail(params.Email); err != nil {
- log.ErrorWrap(err, "sending welcome email")
- }
-}
-
-// respondWithSession makes a HTTP response with the session from the user with the given userID.
-// It sets the HTTP-Only cookie for browser clients and also sends a JSON response for non-browser clients.
-func (a *API) respondWithSession(db *gorm.DB, w http.ResponseWriter, userID int, statusCode int) {
- session, err := a.App.CreateSession(userID)
- if err != nil {
- handlers.DoError(w, "creating session", nil, http.StatusBadRequest)
- return
- }
-
- setSessionCookie(w, session.Key, session.ExpiresAt)
-
- response := SessionResponse{
- Key: session.Key,
- ExpiresAt: session.ExpiresAt.Unix(),
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.WriteHeader(statusCode)
- if err := json.NewEncoder(w).Encode(response); err != nil {
- handlers.DoError(w, "encoding response", err, http.StatusInternalServerError)
- return
- }
-}
diff --git a/pkg/server/api/v3_auth_test.go b/pkg/server/api/v3_auth_test.go
deleted file mode 100644
index 2aa4761e..00000000
--- a/pkg/server/api/v3_auth_test.go
+++ /dev/null
@@ -1,482 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "testing"
- "time"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/config"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
- "github.com/pkg/errors"
- "golang.org/x/crypto/bcrypt"
-)
-
-func assertSessionResp(t *testing.T, res *http.Response) {
- // after register, should sign in user
- var got SessionResponse
- if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var sessionCount int
- var session database.Session
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Count(&sessionCount), "counting session")
- testutils.MustExec(t, testutils.DB.First(&session), "getting session")
-
- assert.Equal(t, sessionCount, 1, "sessionCount mismatch")
- assert.Equal(t, got.Key, session.Key, "session Key mismatch")
- assert.Equal(t, got.ExpiresAt, session.ExpiresAt.Unix(), "session ExpiresAt mismatch")
-
- c := testutils.GetCookieByName(res.Cookies(), "id")
- assert.Equal(t, c.Value, session.Key, "session key mismatch")
- assert.Equal(t, c.Path, "/", "session path mismatch")
- assert.Equal(t, c.HttpOnly, true, "session HTTPOnly mismatch")
- assert.Equal(t, c.Expires.Unix(), session.ExpiresAt.Unix(), "session Expires mismatch")
-}
-
-func TestRegister(t *testing.T) {
- testCases := []struct {
- email string
- password string
- onPremise bool
- expectedPro bool
- }{
- {
- email: "alice@example.com",
- password: "pass1234",
- onPremise: false,
- expectedPro: false,
- },
- {
- email: "bob@example.com",
- password: "Y9EwmjH@Jq6y5a64MSACUoM4w7SAhzvY",
- onPremise: false,
- expectedPro: false,
- },
- {
- email: "chuck@example.com",
- password: "e*H@kJi^vXbWEcD9T5^Am!Y@7#Po2@PC",
- onPremise: false,
- expectedPro: false,
- },
- // on premise
- {
- email: "dan@example.com",
- password: "e*H@kJi^vXbWEcD9T5^Am!Y@7#Po2@PC",
- onPremise: true,
- expectedPro: true,
- },
- }
-
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("register %s %s", tc.email, tc.password), func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- c := config.Load()
- c.SetOnPremise(tc.onPremise)
-
- // Setup
- emailBackend := testutils.MockEmailbackendImplementation{}
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- EmailBackend: &emailBackend,
- Config: c,
- })
- defer server.Close()
-
- dat := fmt.Sprintf(`{"email": "%s", "password": "%s"}`, tc.email, tc.password)
- req := testutils.MakeReq(server.URL, "POST", "/v3/register", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusCreated, "")
-
- var account database.Account
- testutils.MustExec(t, testutils.DB.Where("email = ?", tc.email).First(&account), "finding account")
- assert.Equal(t, account.Email.String, tc.email, "Email mismatch")
- assert.NotEqual(t, account.UserID, 0, "UserID mismatch")
- passwordErr := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte(tc.password))
- assert.Equal(t, passwordErr, nil, "Password mismatch")
-
- var user database.User
- testutils.MustExec(t, testutils.DB.Where("id = ?", account.UserID).First(&user), "finding user")
- assert.Equal(t, user.Cloud, tc.expectedPro, "Cloud mismatch")
- assert.Equal(t, user.MaxUSN, 0, "MaxUSN mismatch")
-
- // welcome email
- assert.Equalf(t, len(emailBackend.Emails), 1, "email queue count mismatch")
- assert.DeepEqual(t, emailBackend.Emails[0].To, []string{tc.email}, "email to mismatch")
-
- // after register, should sign in user
- assertSessionResp(t, res)
- })
- }
-}
-
-func TestRegisterMissingParams(t *testing.T) {
- t.Run("missing email", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- dat := fmt.Sprintf(`{"password": %s}`, "SLMZFM5RmSjA5vfXnG5lPOnrpZSbtmV76cnAcrlr2yU")
- req := testutils.MakeReq(server.URL, "POST", "/v3/register", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status mismatch")
-
- var accountCount, userCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Account{}).Count(&accountCount), "counting account")
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).Count(&userCount), "counting user")
-
- assert.Equal(t, accountCount, 0, "accountCount mismatch")
- assert.Equal(t, userCount, 0, "userCount mismatch")
- })
-
- t.Run("missing password", func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- dat := fmt.Sprintf(`{"email": "%s"}`, "alice@example.com")
- req := testutils.MakeReq(server.URL, "POST", "/v3/register", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "Status mismatch")
-
- var accountCount, userCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Account{}).Count(&accountCount), "counting account")
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).Count(&userCount), "counting user")
-
- assert.Equal(t, accountCount, 0, "accountCount mismatch")
- assert.Equal(t, userCount, 0, "userCount mismatch")
- })
-}
-
-func TestRegisterDuplicateEmail(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- testutils.SetupAccountData(u, "alice@example.com", "somepassword")
-
- dat := `{"email": "alice@example.com", "password": "foobarbaz"}`
- req := testutils.MakeReq(server.URL, "POST", "/v3/register", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusBadRequest, "status code mismatch")
-
- var accountCount, userCount, verificationTokenCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Account{}).Count(&accountCount), "counting account")
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).Count(&userCount), "counting user")
- testutils.MustExec(t, testutils.DB.Model(&database.Token{}).Count(&verificationTokenCount), "counting verification token")
-
- var user database.User
- testutils.MustExec(t, testutils.DB.Where("id = ?", u.ID).First(&user), "finding user")
-
- assert.Equal(t, accountCount, 1, "account count mismatch")
- assert.Equal(t, userCount, 1, "user count mismatch")
- assert.Equal(t, verificationTokenCount, 0, "verification_token should not have been created")
- assert.Equal(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
-}
-
-func TestRegisterDisabled(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- c := config.Load()
- c.DisableRegistration = true
-
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- Config: c,
- })
- defer server.Close()
-
- dat := `{"email": "alice@example.com", "password": "foobarbaz"}`
- req := testutils.MakeReq(server.URL, "POST", "/v3/register", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusForbidden, "status code mismatch")
-
- var accountCount, userCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Account{}).Count(&accountCount), "counting account")
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).Count(&userCount), "counting user")
-
- assert.Equal(t, accountCount, 0, "account count mismatch")
- assert.Equal(t, userCount, 0, "user count mismatch")
-}
-
-func TestSignIn(t *testing.T) {
- t.Run("success", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- testutils.SetupAccountData(u, "alice@example.com", "pass1234")
-
- dat := `{"email": "alice@example.com", "password": "pass1234"}`
- req := testutils.MakeReq(server.URL, "POST", "/v3/signin", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var user database.User
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).First(&user), "finding user")
- assert.NotEqual(t, user.LastLoginAt, nil, "LastLoginAt mismatch")
-
- // after register, should sign in user
- assertSessionResp(t, res)
- })
-
- t.Run("wrong password", func(t *testing.T) {
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- testutils.SetupAccountData(u, "alice@example.com", "pass1234")
-
- dat := `{"email": "alice@example.com", "password": "wrongpassword1234"}`
- req := testutils.MakeReq(server.URL, "POST", "/v3/signin", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
-
- var user database.User
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).First(&user), "finding user")
- assert.Equal(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
-
- var sessionCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Count(&sessionCount), "counting session")
- assert.Equal(t, sessionCount, 0, "sessionCount mismatch")
- })
-
- t.Run("wrong email", func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- u := testutils.SetupUserData()
- testutils.SetupAccountData(u, "alice@example.com", "pass1234")
-
- dat := `{"email": "bob@example.com", "password": "pass1234"}`
- req := testutils.MakeReq(server.URL, "POST", "/v3/signin", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
-
- var user database.User
- testutils.MustExec(t, testutils.DB.Model(&database.User{}).First(&user), "finding user")
- assert.DeepEqual(t, user.LastLoginAt, (*time.Time)(nil), "LastLoginAt mismatch")
-
- var sessionCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Count(&sessionCount), "counting session")
- assert.Equal(t, sessionCount, 0, "sessionCount mismatch")
- })
-
- t.Run("nonexistent email", func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- dat := `{"email": "nonexistent@example.com", "password": "pass1234"}`
- req := testutils.MakeReq(server.URL, "POST", "/v3/signin", dat)
-
- // Execute
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusUnauthorized, "")
-
- var sessionCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Count(&sessionCount), "counting session")
- assert.Equal(t, sessionCount, 0, "sessionCount mismatch")
- })
-}
-
-func TestSignout(t *testing.T) {
- t.Run("authenticated", func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- aliceUser := testutils.SetupUserData()
- testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234")
- anotherUser := testutils.SetupUserData()
-
- session1 := database.Session{
- Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=",
- UserID: aliceUser.ID,
- ExpiresAt: time.Now().Add(time.Hour * 24),
- }
- testutils.MustExec(t, testutils.DB.Save(&session1), "preparing session1")
- session2 := database.Session{
- Key: "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=",
- UserID: anotherUser.ID,
- ExpiresAt: time.Now().Add(time.Hour * 24),
- }
- testutils.MustExec(t, testutils.DB.Save(&session2), "preparing session2")
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- // Execute
- req := testutils.MakeReq(server.URL, "POST", "/v3/signout", "")
- req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU="))
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusNoContent, "Status mismatch")
-
- var sessionCount int
- var s2 database.Session
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Count(&sessionCount), "counting session")
- testutils.MustExec(t, testutils.DB.Where("key = ?", "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=").First(&s2), "getting s2")
-
- assert.Equal(t, sessionCount, 1, "sessionCount mismatch")
-
- c := testutils.GetCookieByName(res.Cookies(), "id")
- assert.Equal(t, c.Value, "", "session key mismatch")
- assert.Equal(t, c.Path, "/", "session path mismatch")
- assert.Equal(t, c.HttpOnly, true, "session HTTPOnly mismatch")
- if c.Expires.After(time.Now()) {
- t.Error("session cookie is not expired")
- }
- })
-
- t.Run("unauthenticated", func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- aliceUser := testutils.SetupUserData()
- testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234")
- anotherUser := testutils.SetupUserData()
-
- session1 := database.Session{
- Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=",
- UserID: aliceUser.ID,
- ExpiresAt: time.Now().Add(time.Hour * 24),
- }
- testutils.MustExec(t, testutils.DB.Save(&session1), "preparing session1")
- session2 := database.Session{
- Key: "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=",
- UserID: anotherUser.ID,
- ExpiresAt: time.Now().Add(time.Hour * 24),
- }
- testutils.MustExec(t, testutils.DB.Save(&session2), "preparing session2")
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- // Execute
- req := testutils.MakeReq(server.URL, "POST", "/v3/signout", "")
- res := testutils.HTTPDo(t, req)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusNoContent, "Status mismatch")
-
- var sessionCount int
- var postSession1, postSession2 database.Session
- testutils.MustExec(t, testutils.DB.Model(&database.Session{}).Count(&sessionCount), "counting session")
- testutils.MustExec(t, testutils.DB.Where("key = ?", "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=").First(&postSession1), "getting postSession1")
- testutils.MustExec(t, testutils.DB.Where("key = ?", "MDCpbvCRg7W2sH6S870wqLqZDZTObYeVd0PzOekfo/A=").First(&postSession2), "getting postSession2")
-
- // two existing sessions should remain
- assert.Equal(t, sessionCount, 2, "sessionCount mismatch")
-
- c := testutils.GetCookieByName(res.Cookies(), "id")
- assert.Equal(t, c, (*http.Cookie)(nil), "id cookie should have not been set")
- })
-}
diff --git a/pkg/server/api/v3_books.go b/pkg/server/api/v3_books.go
deleted file mode 100644
index 34985bb5..00000000
--- a/pkg/server/api/v3_books.go
+++ /dev/null
@@ -1,267 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
- "net/url"
-
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/handlers"
- "github.com/dnote/dnote/pkg/server/helpers"
- "github.com/dnote/dnote/pkg/server/presenters"
- "github.com/gorilla/mux"
- "github.com/jinzhu/gorm"
- "github.com/pkg/errors"
-)
-
-type createBookPayload struct {
- Name string `json:"name"`
-}
-
-// CreateBookResp is the response from create book api
-type CreateBookResp struct {
- Book presenters.Book `json:"book"`
-}
-
-func validateCreateBookPayload(p createBookPayload) error {
- if p.Name == "" {
- return errors.New("name is required")
- }
-
- return nil
-}
-
-// CreateBook creates a new book
-func (a *API) CreateBook(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- return
- }
-
- var params createBookPayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
-
- err = validateCreateBookPayload(params)
- if err != nil {
- handlers.DoError(w, "validating payload", err, http.StatusBadRequest)
- return
- }
-
- var bookCount int
- err = a.App.DB.Model(database.Book{}).
- Where("user_id = ? AND label = ?", user.ID, params.Name).
- Count(&bookCount).Error
- if err != nil {
- handlers.DoError(w, "checking duplicate", err, http.StatusInternalServerError)
- return
- }
- if bookCount > 0 {
- http.Error(w, "duplicate book exists", http.StatusConflict)
- return
- }
-
- book, err := a.App.CreateBook(user, params.Name)
- if err != nil {
- handlers.DoError(w, "inserting book", err, http.StatusInternalServerError)
- }
- resp := CreateBookResp{
- Book: presenters.PresentBook(book),
- }
- handlers.RespondJSON(w, http.StatusCreated, resp)
-}
-
-// BooksOptions is a handler for OPTIONS endpoint for notes
-func (a *API) BooksOptions(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
-
-func respondWithBooks(db *gorm.DB, userID int, query url.Values, w http.ResponseWriter) {
- var books []database.Book
- conn := db.Where("user_id = ? AND NOT deleted", userID).Order("label ASC")
- name := query.Get("name")
- encryptedStr := query.Get("encrypted")
-
- if name != "" {
- part := fmt.Sprintf("%%%s%%", name)
- conn = conn.Where("LOWER(label) LIKE ?", part)
- }
- if encryptedStr != "" {
- var encrypted bool
- if encryptedStr == "true" {
- encrypted = true
- } else {
- encrypted = false
- }
-
- conn = conn.Where("encrypted = ?", encrypted)
- }
-
- if err := conn.Find(&books).Error; err != nil {
- handlers.DoError(w, "finding books", err, http.StatusInternalServerError)
- return
- }
-
- presentedBooks := presenters.PresentBooks(books)
- handlers.RespondJSON(w, http.StatusOK, presentedBooks)
-}
-
-// GetBooks returns books for the user
-func (a *API) GetBooks(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- return
- }
-
- query := r.URL.Query()
-
- respondWithBooks(a.App.DB, user.ID, query, w)
-}
-
-// GetBook returns a book for the user
-func (a *API) GetBook(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- return
- }
-
- vars := mux.Vars(r)
- bookUUID := vars["bookUUID"]
-
- var book database.Book
- conn := a.App.DB.Where("uuid = ? AND user_id = ?", bookUUID, user.ID).First(&book)
-
- if conn.RecordNotFound() {
- w.WriteHeader(http.StatusNotFound)
- return
- }
- if err := conn.Error; err != nil {
- handlers.DoError(w, "finding book", err, http.StatusInternalServerError)
- return
- }
-
- p := presenters.PresentBook(book)
- handlers.RespondJSON(w, http.StatusOK, p)
-}
-
-type updateBookPayload struct {
- Name *string `json:"name"`
-}
-
-// UpdateBookResp is the response from create book api
-type UpdateBookResp struct {
- Book presenters.Book `json:"book"`
-}
-
-// UpdateBook updates a book
-func (a *API) UpdateBook(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- return
- }
-
- vars := mux.Vars(r)
- uuid := vars["bookUUID"]
-
- tx := a.App.DB.Begin()
-
- var book database.Book
- if err := tx.Where("user_id = ? AND uuid = ?", user.ID, uuid).First(&book).Error; err != nil {
- handlers.DoError(w, "finding book", err, http.StatusInternalServerError)
- return
- }
-
- var params updateBookPayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
-
- book, err = a.App.UpdateBook(tx, user, book, params.Name)
- if err != nil {
- tx.Rollback()
- handlers.DoError(w, "updating a book", err, http.StatusInternalServerError)
- }
-
- tx.Commit()
-
- resp := UpdateBookResp{
- Book: presenters.PresentBook(book),
- }
- handlers.RespondJSON(w, http.StatusOK, resp)
-}
-
-// DeleteBookResp is the response from create book api
-type DeleteBookResp struct {
- Status int `json:"status"`
- Book presenters.Book `json:"book"`
-}
-
-// DeleteBook removes a book
-func (a *API) DeleteBook(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- return
- }
-
- vars := mux.Vars(r)
- uuid := vars["bookUUID"]
-
- tx := a.App.DB.Begin()
-
- var book database.Book
- if err := tx.Where("user_id = ? AND uuid = ?", user.ID, uuid).First(&book).Error; err != nil {
- handlers.DoError(w, "finding book", err, http.StatusInternalServerError)
- return
- }
-
- var notes []database.Note
- if err := tx.Where("book_uuid = ? AND NOT deleted", uuid).Order("usn ASC").Find(¬es).Error; err != nil {
- handlers.DoError(w, "finding notes", err, http.StatusInternalServerError)
- return
- }
-
- for _, note := range notes {
- if _, err := a.App.DeleteNote(tx, user, note); err != nil {
- handlers.DoError(w, "deleting a note", err, http.StatusInternalServerError)
- return
- }
- }
- b, err := a.App.DeleteBook(tx, user, book)
- if err != nil {
- handlers.DoError(w, "deleting book", err, http.StatusInternalServerError)
- return
- }
-
- tx.Commit()
-
- resp := DeleteBookResp{
- Status: http.StatusOK,
- Book: presenters.PresentBook(b),
- }
- handlers.RespondJSON(w, http.StatusOK, resp)
-}
diff --git a/pkg/server/api/v3_notes.go b/pkg/server/api/v3_notes.go
deleted file mode 100644
index c38ced5a..00000000
--- a/pkg/server/api/v3_notes.go
+++ /dev/null
@@ -1,220 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "encoding/json"
- "fmt"
- "net/http"
-
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/handlers"
- "github.com/dnote/dnote/pkg/server/helpers"
- "github.com/dnote/dnote/pkg/server/presenters"
- "github.com/gorilla/mux"
- "github.com/pkg/errors"
-)
-
-type updateNotePayload struct {
- BookUUID *string `json:"book_uuid"`
- Content *string `json:"content"`
- Public *bool `json:"public"`
-}
-
-type updateNoteResp struct {
- Status int `json:"status"`
- Result presenters.Note `json:"result"`
-}
-
-func validateUpdateNotePayload(p updateNotePayload) bool {
- return p.BookUUID != nil || p.Content != nil || p.Public != nil
-}
-
-// UpdateNote updates note
-func (a *API) UpdateNote(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- noteUUID := vars["noteUUID"]
-
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var params updateNotePayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handlers.DoError(w, "decoding params", err, http.StatusInternalServerError)
- return
- }
-
- if ok := validateUpdateNotePayload(params); !ok {
- handlers.DoError(w, "Invalid payload", nil, http.StatusBadRequest)
- return
- }
-
- var note database.Note
- if err := a.App.DB.Where("uuid = ? AND user_id = ?", noteUUID, user.ID).First(¬e).Error; err != nil {
- handlers.DoError(w, "finding note", err, http.StatusInternalServerError)
- return
- }
-
- tx := a.App.DB.Begin()
-
- note, err = a.App.UpdateNote(tx, user, note, &app.UpdateNoteParams{
- BookUUID: params.BookUUID,
- Content: params.Content,
- Public: params.Public,
- })
- if err != nil {
- tx.Rollback()
- handlers.DoError(w, "updating note", err, http.StatusInternalServerError)
- return
- }
-
- var book database.Book
- if err := tx.Where("uuid = ? AND user_id = ?", note.BookUUID, user.ID).First(&book).Error; err != nil {
- tx.Rollback()
- handlers.DoError(w, fmt.Sprintf("finding book %s to preload", note.BookUUID), err, http.StatusInternalServerError)
- return
- }
-
- tx.Commit()
-
- // preload associations
- note.User = user
- note.Book = book
-
- resp := updateNoteResp{
- Status: http.StatusOK,
- Result: presenters.PresentNote(note),
- }
- handlers.RespondJSON(w, http.StatusOK, resp)
-}
-
-type deleteNoteResp struct {
- Status int `json:"status"`
- Result presenters.Note `json:"result"`
-}
-
-// DeleteNote removes note
-func (a *API) DeleteNote(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- noteUUID := vars["noteUUID"]
-
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var note database.Note
- if err := a.App.DB.Where("uuid = ? AND user_id = ?", noteUUID, user.ID).Preload("Book").First(¬e).Error; err != nil {
- handlers.DoError(w, "finding note", err, http.StatusInternalServerError)
- return
- }
-
- tx := a.App.DB.Begin()
-
- n, err := a.App.DeleteNote(tx, user, note)
- if err != nil {
- tx.Rollback()
- handlers.DoError(w, "deleting note", err, http.StatusInternalServerError)
- return
- }
-
- tx.Commit()
-
- resp := deleteNoteResp{
- Status: http.StatusNoContent,
- Result: presenters.PresentNote(n),
- }
- handlers.RespondJSON(w, http.StatusOK, resp)
-}
-
-type createNotePayload struct {
- BookUUID string `json:"book_uuid"`
- Content string `json:"content"`
- AddedOn *int64 `json:"added_on"`
- EditedOn *int64 `json:"edited_on"`
-}
-
-func validateCreateNotePayload(p createNotePayload) error {
- if p.BookUUID == "" {
- return errors.New("bookUUID is required")
- }
-
- return nil
-}
-
-// CreateNoteResp is a response for creating a note
-type CreateNoteResp struct {
- Result presenters.Note `json:"result"`
-}
-
-// CreateNote creates a note
-func (a *API) CreateNote(w http.ResponseWriter, r *http.Request) {
- user, ok := r.Context().Value(helpers.KeyUser).(database.User)
- if !ok {
- handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError)
- return
- }
-
- var params createNotePayload
- err := json.NewDecoder(r.Body).Decode(¶ms)
- if err != nil {
- handlers.DoError(w, "decoding payload", err, http.StatusInternalServerError)
- return
- }
-
- err = validateCreateNotePayload(params)
- if err != nil {
- handlers.DoError(w, "validating payload", err, http.StatusBadRequest)
- return
- }
-
- var book database.Book
- if err := a.App.DB.Where("uuid = ? AND user_id = ?", params.BookUUID, user.ID).First(&book).Error; err != nil {
- handlers.DoError(w, "finding book", err, http.StatusInternalServerError)
- return
- }
-
- client := getClientType(r)
- note, err := a.App.CreateNote(user, params.BookUUID, params.Content, params.AddedOn, params.EditedOn, false, client)
- if err != nil {
- handlers.DoError(w, "creating note", err, http.StatusInternalServerError)
- return
- }
-
- // preload associations
- note.User = user
- note.Book = book
-
- resp := CreateNoteResp{
- Result: presenters.PresentNote(note),
- }
- handlers.RespondJSON(w, http.StatusCreated, resp)
-}
-
-// NotesOptions is a handler for OPTIONS endpoint for notes
-func (a *API) NotesOptions(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Access-Control-Allow-Methods", "POST")
- w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version")
-}
diff --git a/pkg/server/api/v3_notes_test.go b/pkg/server/api/v3_notes_test.go
deleted file mode 100644
index df30beed..00000000
--- a/pkg/server/api/v3_notes_test.go
+++ /dev/null
@@ -1,394 +0,0 @@
-/* Copyright (C) 2019, 2020, 2021 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 api
-
-import (
- "fmt"
- "net/http"
- "testing"
-
- "github.com/dnote/dnote/pkg/assert"
- "github.com/dnote/dnote/pkg/clock"
- "github.com/dnote/dnote/pkg/server/app"
- "github.com/dnote/dnote/pkg/server/database"
- "github.com/dnote/dnote/pkg/server/testutils"
-)
-
-func TestCreateNote(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- USN: 58,
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
-
- // Execute
- dat := fmt.Sprintf(`{"book_uuid": "%s", "content": "note content"}`, b1.UUID)
- req := testutils.MakeReq(server.URL, "POST", "/v3/notes", dat)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusCreated, "")
-
- var noteRecord database.Note
- var bookRecord database.Book
- var userRecord database.User
- var bookCount, noteCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books")
- testutils.MustExec(t, testutils.DB.Model(&database.Note{}).Count(¬eCount), "counting notes")
- testutils.MustExec(t, testutils.DB.First(¬eRecord), "finding note")
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
- testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record")
-
- assert.Equalf(t, bookCount, 1, "book count mismatch")
- assert.Equalf(t, noteCount, 1, "note count mismatch")
-
- assert.Equal(t, bookRecord.Label, b1.Label, "book name mismatch")
- assert.Equal(t, bookRecord.UUID, b1.UUID, "book uuid mismatch")
- assert.Equal(t, bookRecord.UserID, b1.UserID, "book user_id mismatch")
- assert.Equal(t, bookRecord.USN, 58, "book usn mismatch")
-
- assert.NotEqual(t, noteRecord.UUID, "", "note uuid should have been generated")
- assert.Equal(t, noteRecord.BookUUID, b1.UUID, "note book_uuid mismatch")
- assert.Equal(t, noteRecord.Body, "note content", "note content mismatch")
- assert.Equal(t, noteRecord.USN, 102, "note usn mismatch")
-}
-
-func TestUpdateNote(t *testing.T) {
- updatedBody := "some updated content"
-
- b1UUID := "37868a8e-a844-4265-9a4f-0be598084733"
- b2UUID := "8f3bd424-6aa5-4ed5-910d-e5b38ab09f8c"
-
- testCases := []struct {
- payload string
- noteUUID string
- noteBookUUID string
- noteBody string
- notePublic bool
- noteDeleted bool
- expectedNoteBody string
- expectedNoteBookName string
- expectedNoteBookUUID string
- expectedNotePublic bool
- }{
- {
- payload: fmt.Sprintf(`{
- "content": "%s"
- }`, updatedBody),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b1UUID,
- expectedNoteBody: "some updated content",
- expectedNoteBookName: "css",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "book_uuid": "%s"
- }`, b1UUID),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b1UUID,
- expectedNoteBody: "original content",
- expectedNoteBookName: "css",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "book_uuid": "%s"
- }`, b2UUID),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b2UUID,
- expectedNoteBody: "original content",
- expectedNoteBookName: "js",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "book_uuid": "%s",
- "content": "%s"
- }`, b2UUID, updatedBody),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b2UUID,
- expectedNoteBody: "some updated content",
- expectedNoteBookName: "js",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "book_uuid": "%s",
- "content": "%s"
- }`, b1UUID, updatedBody),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "",
- noteDeleted: true,
- expectedNoteBookUUID: b1UUID,
- expectedNoteBody: updatedBody,
- expectedNoteBookName: "js",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "public": %t
- }`, true),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b1UUID,
- expectedNoteBody: "original content",
- expectedNoteBookName: "css",
- expectedNotePublic: true,
- },
- {
- payload: fmt.Sprintf(`{
- "public": %t
- }`, false),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: true,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b1UUID,
- expectedNoteBody: "original content",
- expectedNoteBookName: "css",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "content": "%s",
- "public": %t
- }`, updatedBody, false),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: true,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b1UUID,
- expectedNoteBody: updatedBody,
- expectedNoteBookName: "css",
- expectedNotePublic: false,
- },
- {
- payload: fmt.Sprintf(`{
- "book_uuid": "%s",
- "content": "%s",
- "public": %t
- }`, b2UUID, updatedBody, true),
- noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053",
- noteBookUUID: b1UUID,
- notePublic: false,
- noteBody: "original content",
- noteDeleted: false,
- expectedNoteBookUUID: b2UUID,
- expectedNoteBody: updatedBody,
- expectedNoteBookName: "js",
- expectedNotePublic: true,
- },
- }
-
- for idx, tc := range testCases {
- t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
-
- b1 := database.Book{
- UUID: b1UUID,
- UserID: user.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- b2 := database.Book{
- UUID: b2UUID,
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
-
- note := database.Note{
- UserID: user.ID,
- UUID: tc.noteUUID,
- BookUUID: tc.noteBookUUID,
- Body: tc.noteBody,
- Deleted: tc.noteDeleted,
- Public: tc.notePublic,
- }
- testutils.MustExec(t, testutils.DB.Save(¬e), "preparing note")
-
- // Execute
- endpoint := fmt.Sprintf("/v3/notes/%s", note.UUID)
- req := testutils.MakeReq(server.URL, "PATCH", endpoint, tc.payload)
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "status code mismatch for test case")
-
- var bookRecord database.Book
- var noteRecord database.Note
- var userRecord database.User
- var noteCount, bookCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books")
- testutils.MustExec(t, testutils.DB.Model(&database.Note{}).Count(¬eCount), "counting notes")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", note.UUID).First(¬eRecord), "finding note")
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
- testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record")
-
- assert.Equalf(t, bookCount, 2, "book count mismatch")
- assert.Equalf(t, noteCount, 1, "note count mismatch")
-
- assert.Equal(t, noteRecord.UUID, tc.noteUUID, "note uuid mismatch for test case")
- assert.Equal(t, noteRecord.Body, tc.expectedNoteBody, "note content mismatch for test case")
- assert.Equal(t, noteRecord.BookUUID, tc.expectedNoteBookUUID, "note book_uuid mismatch for test case")
- assert.Equal(t, noteRecord.Public, tc.expectedNotePublic, "note public mismatch for test case")
- assert.Equal(t, noteRecord.USN, 102, "note usn mismatch for test case")
-
- assert.Equal(t, userRecord.MaxUSN, 102, "user max_usn mismatch for test case")
- })
- }
-}
-
-func TestDeleteNote(t *testing.T) {
- b1UUID := "37868a8e-a844-4265-9a4f-0be598084733"
-
- testCases := []struct {
- content string
- deleted bool
- originalUSN int
- expectedUSN int
- expectedMaxUSN int
- }{
- {
- content: "n1 content",
- deleted: false,
- originalUSN: 12,
- expectedUSN: 982,
- expectedMaxUSN: 982,
- },
- {
- content: "",
- deleted: true,
- originalUSN: 12,
- expectedUSN: 982,
- expectedMaxUSN: 982,
- },
- }
-
- for _, tc := range testCases {
- t.Run(fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T) {
-
- defer testutils.ClearData(testutils.DB)
-
- // Setup
- server := MustNewServer(t, &app.App{
-
- Clock: clock.NewMock(),
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 981), "preparing user max_usn")
-
- b1 := database.Book{
- UUID: b1UUID,
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- note := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- Body: tc.content,
- Deleted: tc.deleted,
- USN: tc.originalUSN,
- }
- testutils.MustExec(t, testutils.DB.Save(¬e), "preparing note")
-
- // Execute
- endpoint := fmt.Sprintf("/v3/notes/%s", note.UUID)
- req := testutils.MakeReq(server.URL, "DELETE", endpoint, "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- var bookRecord database.Book
- var noteRecord database.Note
- var userRecord database.User
- var bookCount, noteCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books")
- testutils.MustExec(t, testutils.DB.Model(&database.Note{}).Count(¬eCount), "counting notes")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", note.UUID).First(¬eRecord), "finding note")
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
- testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record")
-
- assert.Equalf(t, bookCount, 1, "book count mismatch")
- assert.Equalf(t, noteCount, 1, "note count mismatch")
-
- assert.Equal(t, noteRecord.UUID, note.UUID, "note uuid mismatch for test case")
- assert.Equal(t, noteRecord.Body, "", "note content mismatch for test case")
- assert.Equal(t, noteRecord.Deleted, true, "note deleted mismatch for test case")
- assert.Equal(t, noteRecord.BookUUID, note.BookUUID, "note book_uuid mismatch for test case")
- assert.Equal(t, noteRecord.UserID, note.UserID, "note user_id mismatch for test case")
- assert.Equal(t, noteRecord.USN, tc.expectedUSN, "note usn mismatch for test case")
-
- assert.Equal(t, userRecord.MaxUSN, tc.expectedMaxUSN, "user max_usn mismatch for test case")
- })
- }
-}
diff --git a/pkg/server/controllers/books_test.go b/pkg/server/controllers/books_test.go
index 6c8525ef..d60db1e5 100644
--- a/pkg/server/controllers/books_test.go
+++ b/pkg/server/controllers/books_test.go
@@ -23,9 +23,7 @@ import (
"fmt"
"io/ioutil"
"net/http"
- "net/url"
"testing"
- // "time"
"github.com/dnote/dnote/pkg/assert"
"github.com/dnote/dnote/pkg/clock"
@@ -38,165 +36,151 @@ import (
)
func TestGetBooks(t *testing.T) {
- testutils.RunForWebAndAPI(t, "get notes", func(t *testing.T, target testutils.EndpointType) {
- defer testutils.ClearData(testutils.DB)
+ defer testutils.ClearData(testutils.DB)
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- Config: config.Config{
- PageTemplateDir: "../views",
- },
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- anotherUser := testutils.SetupUserData()
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- USN: 1123,
- Deleted: false,
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- b2 := database.Book{
- UserID: user.ID,
- Label: "css",
- USN: 1125,
- Deleted: false,
- }
- testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
- b3 := database.Book{
- UserID: anotherUser.ID,
- Label: "css",
- USN: 1128,
- Deleted: false,
- }
- testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
- b4 := database.Book{
- UserID: user.ID,
- Label: "",
- USN: 1129,
- Deleted: true,
- }
- testutils.MustExec(t, testutils.DB.Save(&b4), "preparing b4")
-
- // Execute
- var endpoint string
- if target == testutils.EndpointWeb {
- endpoint = "/books"
- } else {
- endpoint = "/api/v3/books"
- }
-
- req := testutils.MakeReq(server.URL, "GET", endpoint, "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- if target == testutils.EndpointAPI {
- var payload []presenters.Book
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var b1Record, b2Record database.Book
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
- testutils.MustExec(t, testutils.DB.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
- testutils.MustExec(t, testutils.DB.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
-
- expected := []presenters.Book{
- {
- UUID: b2Record.UUID,
- CreatedAt: b2Record.CreatedAt,
- UpdatedAt: b2Record.UpdatedAt,
- Label: b2Record.Label,
- USN: b2Record.USN,
- },
- {
- UUID: b1Record.UUID,
- CreatedAt: b1Record.CreatedAt,
- UpdatedAt: b1Record.UpdatedAt,
- Label: b1Record.Label,
- USN: b1Record.USN,
- },
- }
-
- assert.DeepEqual(t, payload, expected, "payload mismatch")
- }
+ // Setup
+ server := MustNewServer(t, &app.App{
+ Clock: clock.NewMock(),
+ Config: config.Config{
+ PageTemplateDir: "../views",
+ },
})
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+ anotherUser := testutils.SetupUserData()
+ testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ USN: 1123,
+ Deleted: false,
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ USN: 1125,
+ Deleted: false,
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "css",
+ USN: 1128,
+ Deleted: false,
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
+ b4 := database.Book{
+ UserID: user.ID,
+ Label: "",
+ USN: 1129,
+ Deleted: true,
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b4), "preparing b4")
+
+ // Execute
+ endpoint := "/api/v3/books"
+
+ req := testutils.MakeReq(server.URL, "GET", endpoint, "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload []presenters.Book
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var b1Record, b2Record database.Book
+ testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
+ testutils.MustExec(t, testutils.DB.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
+ testutils.MustExec(t, testutils.DB.Where("id = ?", b2.ID).First(&b2Record), "finding b2")
+
+ expected := []presenters.Book{
+ {
+ UUID: b2Record.UUID,
+ CreatedAt: b2Record.CreatedAt,
+ UpdatedAt: b2Record.UpdatedAt,
+ Label: b2Record.Label,
+ USN: b2Record.USN,
+ },
+ {
+ UUID: b1Record.UUID,
+ CreatedAt: b1Record.CreatedAt,
+ UpdatedAt: b1Record.UpdatedAt,
+ Label: b1Record.Label,
+ USN: b1Record.USN,
+ },
+ }
+
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
}
func TestGetBooksByName(t *testing.T) {
- testutils.RunForWebAndAPI(t, "get notes", func(t *testing.T, target testutils.EndpointType) {
- defer testutils.ClearData(testutils.DB)
+ defer testutils.ClearData(testutils.DB)
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- Config: config.Config{
- PageTemplateDir: "../views",
- },
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- anotherUser := testutils.SetupUserData()
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- b2 := database.Book{
- UserID: user.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
- b3 := database.Book{
- UserID: anotherUser.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
-
- // Execute
- var endpoint string
- if target == testutils.EndpointWeb {
- endpoint = "/books?name=js"
- } else {
- endpoint = "/api/v3/books?name=js"
- }
-
- req := testutils.MakeReq(server.URL, "GET", endpoint, "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- if target == testutils.EndpointAPI {
- var payload []presenters.Book
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var b1Record database.Book
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
-
- expected := []presenters.Book{
- {
- UUID: b1Record.UUID,
- CreatedAt: b1Record.CreatedAt,
- UpdatedAt: b1Record.UpdatedAt,
- Label: b1Record.Label,
- USN: b1Record.USN,
- },
- }
-
- assert.DeepEqual(t, payload, expected, "payload mismatch")
- }
+ // Setup
+ server := MustNewServer(t, &app.App{
+ Clock: clock.NewMock(),
+ Config: config.Config{
+ PageTemplateDir: "../views",
+ },
})
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+ anotherUser := testutils.SetupUserData()
+ testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
+
+ // Execute
+ endpoint := "/api/v3/books?name=js"
+
+ req := testutils.MakeReq(server.URL, "GET", endpoint, "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload []presenters.Book
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var b1Record database.Book
+ testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&b1Record), "finding b1")
+
+ expected := []presenters.Book{
+ {
+ UUID: b1Record.UUID,
+ CreatedAt: b1Record.CreatedAt,
+ UpdatedAt: b1Record.UpdatedAt,
+ Label: b1Record.Label,
+ USN: b1Record.USN,
+ },
+ }
+
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
}
func TestGetBook(t *testing.T) {
@@ -212,7 +196,9 @@ func TestGetBook(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
anotherUser := testutils.SetupUserData()
+ testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234")
b1 := database.Book{
UserID: user.ID,
@@ -270,7 +256,9 @@ func TestGetBookNonOwner(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
nonOwner := testutils.SetupUserData()
+ testutils.SetupAccountData(nonOwner, "bob@test.com", "pass1234")
b1 := database.Book{
UserID: user.ID,
@@ -294,7 +282,7 @@ func TestGetBookNonOwner(t *testing.T) {
}
func TestCreateBook(t *testing.T) {
- testutils.RunForWebAndAPI(t, "success", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("success", func(t *testing.T) {
defer testutils.ClearData(testutils.DB)
// Setup
@@ -307,16 +295,10 @@ func TestCreateBook(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
- var req *http.Request
- if target == testutils.EndpointWeb {
- dat := url.Values{}
- dat.Set("name", "js")
- req = testutils.MakeFormReq(server.URL, "POST", "/books", dat)
- } else {
- req = testutils.MakeReq(server.URL, "POST", "/api/v3/books", `{"name": "js"}`)
- }
+ req := testutils.MakeReq(server.URL, "POST", "/api/v3/books", `{"name": "js"}`)
// Execute
res := testutils.HTTPAuthDo(t, req, user)
@@ -343,26 +325,24 @@ func TestCreateBook(t *testing.T) {
assert.Equal(t, bookRecord.USN, maxUSN, "book user_id mismatch")
assert.Equal(t, userRecord.MaxUSN, maxUSN, "user max_usn mismatch")
- if target == testutils.EndpointAPI {
- var got createBookResp
- if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
- t.Fatal(errors.Wrap(err, "decoding"))
- }
- expected := createBookResp{
- Book: presenters.Book{
- UUID: bookRecord.UUID,
- USN: bookRecord.USN,
- CreatedAt: bookRecord.CreatedAt,
- UpdatedAt: bookRecord.UpdatedAt,
- Label: "js",
- },
- }
-
- assert.DeepEqual(t, got, expected, "payload mismatch")
+ var got createBookResp
+ if err := json.NewDecoder(res.Body).Decode(&got); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding"))
}
+ expected := createBookResp{
+ Book: presenters.Book{
+ UUID: bookRecord.UUID,
+ USN: bookRecord.USN,
+ CreatedAt: bookRecord.CreatedAt,
+ UpdatedAt: bookRecord.UpdatedAt,
+ Label: "js",
+ },
+ }
+
+ assert.DeepEqual(t, got, expected, "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "duplicate", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("duplicate", func(t *testing.T) {
defer testutils.ClearData(testutils.DB)
// Setup
@@ -375,6 +355,7 @@ func TestCreateBook(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
b1 := database.Book{
@@ -385,14 +366,7 @@ func TestCreateBook(t *testing.T) {
testutils.MustExec(t, testutils.DB.Save(&b1), "preparing book data")
// Execute
- var req *http.Request
- if target == testutils.EndpointWeb {
- dat := url.Values{}
- dat.Set("name", "js")
- req = testutils.MakeFormReq(server.URL, "POST", "/books", dat)
- } else {
- req = testutils.MakeReq(server.URL, "POST", "/api/v3/books", `{"name": "js"}`)
- }
+ req := testutils.MakeReq(server.URL, "POST", "/api/v3/books", `{"name": "js"}`)
res := testutils.HTTPAuthDo(t, req, user)
// Test
@@ -459,7 +433,7 @@ func TestUpdateBook(t *testing.T) {
}
for idx, tc := range testCases {
- testutils.RunForWebAndAPI(t, fmt.Sprintf("test case %d", idx), func(t *testing.T, target testutils.EndpointType) {
+ t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
defer testutils.ClearData(testutils.DB)
// Setup
@@ -472,6 +446,7 @@ func TestUpdateBook(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
b1 := database.Book{
@@ -489,14 +464,8 @@ func TestUpdateBook(t *testing.T) {
testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
// Execute
- var req *http.Request
- if target == testutils.EndpointWeb {
- endpoint := fmt.Sprintf("/books/%s", tc.bookUUID)
- req = testutils.MakeFormReq(server.URL, "PATCH", endpoint, tc.payload.ToURLValues())
- } else {
- endpoint := fmt.Sprintf("/api/v3/books/%s", tc.bookUUID)
- req = testutils.MakeReq(server.URL, "PATCH", endpoint, tc.payload.ToJSON(t))
- }
+ endpoint := fmt.Sprintf("/api/v3/books/%s", tc.bookUUID)
+ req := testutils.MakeReq(server.URL, "PATCH", endpoint, tc.payload.ToJSON(t))
res := testutils.HTTPAuthDo(t, req, user)
// Test
@@ -551,7 +520,7 @@ func TestDeleteBook(t *testing.T) {
}
for _, tc := range testCases {
- testutils.RunForWebAndAPI(t, fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T, target testutils.EndpointType) {
+ t.Run(fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T) {
defer testutils.ClearData(testutils.DB)
// Setup
@@ -564,8 +533,10 @@ func TestDeleteBook(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 58), "preparing user max_usn")
anotherUser := testutils.SetupUserData()
+ testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234")
testutils.MustExec(t, testutils.DB.Model(&anotherUser).Update("max_usn", 109), "preparing another user max_usn")
b1 := database.Book{
@@ -637,12 +608,7 @@ func TestDeleteBook(t *testing.T) {
testutils.MustExec(t, testutils.DB.Save(&n5), "preparing a note data")
// Execute
- var endpoint string
- if target == testutils.EndpointWeb {
- endpoint = fmt.Sprintf("/books/%s", b2.UUID)
- } else {
- endpoint = fmt.Sprintf("/api/v3/books/%s", b2.UUID)
- }
+ endpoint := fmt.Sprintf("/api/v3/books/%s", b2.UUID)
req := testutils.MakeReq(server.URL, "DELETE", endpoint, "")
res := testutils.HTTPAuthDo(t, req, user)
diff --git a/pkg/server/controllers/notes_test.go b/pkg/server/controllers/notes_test.go
index 73411f5c..c6fa1ffe 100644
--- a/pkg/server/controllers/notes_test.go
+++ b/pkg/server/controllers/notes_test.go
@@ -23,7 +23,6 @@ import (
"fmt"
"io/ioutil"
"net/http"
- "net/url"
"testing"
"time"
@@ -57,127 +56,120 @@ func getExpectedNotePayload(n database.Note, b database.Book, u database.User) p
}
func TestGetNotes(t *testing.T) {
- testutils.RunForWebAndAPI(t, "get notes", func(t *testing.T, target testutils.EndpointType) {
- defer testutils.ClearData(testutils.DB)
+ defer testutils.ClearData(testutils.DB)
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- Config: config.Config{
- PageTemplateDir: "../views",
- },
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- anotherUser := testutils.SetupUserData()
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
- b2 := database.Book{
- UserID: user.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
- b3 := database.Book{
- UserID: anotherUser.ID,
- Label: "css",
- }
- testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
-
- n1 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- Body: "n1 content",
- USN: 11,
- Deleted: false,
- AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
- }
- testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
- n2 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- Body: "n2 content",
- USN: 14,
- Deleted: false,
- AddedOn: time.Date(2018, time.August, 11, 22, 0, 0, 0, time.UTC).UnixNano(),
- }
- testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2")
- n3 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- Body: "n3 content",
- USN: 17,
- Deleted: false,
- AddedOn: time.Date(2017, time.January, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
- }
- testutils.MustExec(t, testutils.DB.Save(&n3), "preparing n3")
- n4 := database.Note{
- UserID: user.ID,
- BookUUID: b2.UUID,
- Body: "n4 content",
- USN: 18,
- Deleted: false,
- AddedOn: time.Date(2018, time.September, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
- }
- testutils.MustExec(t, testutils.DB.Save(&n4), "preparing n4")
- n5 := database.Note{
- UserID: anotherUser.ID,
- BookUUID: b3.UUID,
- Body: "n5 content",
- USN: 19,
- Deleted: false,
- AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
- }
- testutils.MustExec(t, testutils.DB.Save(&n5), "preparing n5")
- n6 := database.Note{
- UserID: user.ID,
- BookUUID: b1.UUID,
- Body: "",
- USN: 11,
- Deleted: true,
- AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
- }
- testutils.MustExec(t, testutils.DB.Save(&n6), "preparing n6")
-
- // Execute
- var endpoint string
- if target == testutils.EndpointWeb {
- endpoint = "/"
- } else {
- endpoint = "/api/v3/notes"
- }
-
- req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("%s?year=2018&month=8", endpoint), "")
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusOK, "")
-
- if target == testutils.EndpointAPI {
- var payload GetNotesResponse
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var n2Record, n1Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", n2.UUID).First(&n2Record), "finding n2Record")
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", n1.UUID).First(&n1Record), "finding n1Record")
-
- expected := GetNotesResponse{
- Notes: []presenters.Note{
- getExpectedNotePayload(n2Record, b1, user),
- getExpectedNotePayload(n1Record, b1, user),
- },
- Total: 2,
- }
-
- assert.DeepEqual(t, payload, expected, "payload mismatch")
- }
+ // Setup
+ server := MustNewServer(t, &app.App{
+ Clock: clock.NewMock(),
+ Config: config.Config{
+ PageTemplateDir: "../views",
+ },
})
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+ anotherUser := testutils.SetupUserData()
+ testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
+ b2 := database.Book{
+ UserID: user.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2")
+ b3 := database.Book{
+ UserID: anotherUser.ID,
+ Label: "css",
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3")
+
+ n1 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n1 content",
+ USN: 11,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1")
+ n2 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n2 content",
+ USN: 14,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.August, 11, 22, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2")
+ n3 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "n3 content",
+ USN: 17,
+ Deleted: false,
+ AddedOn: time.Date(2017, time.January, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, testutils.DB.Save(&n3), "preparing n3")
+ n4 := database.Note{
+ UserID: user.ID,
+ BookUUID: b2.UUID,
+ Body: "n4 content",
+ USN: 18,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.September, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, testutils.DB.Save(&n4), "preparing n4")
+ n5 := database.Note{
+ UserID: anotherUser.ID,
+ BookUUID: b3.UUID,
+ Body: "n5 content",
+ USN: 19,
+ Deleted: false,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, testutils.DB.Save(&n5), "preparing n5")
+ n6 := database.Note{
+ UserID: user.ID,
+ BookUUID: b1.UUID,
+ Body: "",
+ USN: 11,
+ Deleted: true,
+ AddedOn: time.Date(2018, time.August, 10, 23, 0, 0, 0, time.UTC).UnixNano(),
+ }
+ testutils.MustExec(t, testutils.DB.Save(&n6), "preparing n6")
+
+ // Execute
+ endpoint := "/api/v3/notes"
+
+ req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("%s?year=2018&month=8", endpoint), "")
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusOK, "")
+
+ var payload GetNotesResponse
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
+ }
+
+ var n2Record, n1Record database.Note
+ testutils.MustExec(t, testutils.DB.Where("uuid = ?", n2.UUID).First(&n2Record), "finding n2Record")
+ testutils.MustExec(t, testutils.DB.Where("uuid = ?", n1.UUID).First(&n1Record), "finding n1Record")
+
+ expected := GetNotesResponse{
+ Notes: []presenters.Note{
+ getExpectedNotePayload(n2Record, b1, user),
+ getExpectedNotePayload(n1Record, b1, user),
+ },
+ Total: 2,
+ }
+
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
}
func TestGetNote(t *testing.T) {
@@ -222,246 +214,217 @@ func TestGetNote(t *testing.T) {
}
testutils.MustExec(t, testutils.DB.Save(&deletedNote), "preparing deletedNote")
- getURL := func(noteUUID string, target testutils.EndpointType) string {
- if target == testutils.EndpointWeb {
- return fmt.Sprintf("/notes/%s", noteUUID)
- }
-
+ getURL := func(noteUUID string) string {
return fmt.Sprintf("/api/v3/notes/%s", noteUUID)
}
- testutils.RunForWebAndAPI(t, "owner accessing private note", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("owner accessing private note", func(t *testing.T) {
// Execute
- url := getURL(publicNote.UUID, target)
+ url := getURL(publicNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPAuthDo(t, req, user)
// Test
assert.StatusCodeEquals(t, res, http.StatusOK, "")
- if target == testutils.EndpointAPI {
- var payload presenters.Note
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var n2Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-
- expected := getExpectedNotePayload(n2Record, b1, user)
- assert.DeepEqual(t, payload, expected, "payload mismatch")
+ var payload presenters.Note
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
}
+
+ var n2Record database.Note
+ testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
+
+ expected := getExpectedNotePayload(n2Record, b1, user)
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "owner accessing public note", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("owner accessing public note", func(t *testing.T) {
// Execute
- url := getURL(publicNote.UUID, target)
+ url := getURL(publicNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPAuthDo(t, req, user)
// Test
assert.StatusCodeEquals(t, res, http.StatusOK, "")
- if target == testutils.EndpointAPI {
- var payload presenters.Note
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var n2Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-
- expected := getExpectedNotePayload(n2Record, b1, user)
- assert.DeepEqual(t, payload, expected, "payload mismatch")
+ var payload presenters.Note
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
}
+
+ var n2Record database.Note
+ testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
+
+ expected := getExpectedNotePayload(n2Record, b1, user)
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "non-owner accessing public note", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("non-owner accessing public note", func(t *testing.T) {
// Execute
- url := getURL(publicNote.UUID, target)
+ url := getURL(publicNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPAuthDo(t, req, anotherUser)
// Test
assert.StatusCodeEquals(t, res, http.StatusOK, "")
- if target == testutils.EndpointAPI {
- var payload presenters.Note
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var n2Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-
- expected := getExpectedNotePayload(n2Record, b1, user)
- assert.DeepEqual(t, payload, expected, "payload mismatch")
+ var payload presenters.Note
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
}
+
+ var n2Record database.Note
+ testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
+
+ expected := getExpectedNotePayload(n2Record, b1, user)
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "non-owner accessing private note", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("non-owner accessing private note", func(t *testing.T) {
// Execute
- url := getURL(privateNote.UUID, target)
+ url := getURL(privateNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPAuthDo(t, req, anotherUser)
// Test
assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
- if target == testutils.EndpointAPI {
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(errors.Wrap(err, "reading body"))
- }
-
- assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "reading body"))
}
+
+ assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "guest accessing public note", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("guest accessing public note", func(t *testing.T) {
// Execute
- url := getURL(publicNote.UUID, target)
+ url := getURL(publicNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPDo(t, req)
// Test
assert.StatusCodeEquals(t, res, http.StatusOK, "")
- if target == testutils.EndpointAPI {
- var payload presenters.Note
- if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
- t.Fatal(errors.Wrap(err, "decoding payload"))
- }
-
- var n2Record database.Note
- testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
-
- expected := getExpectedNotePayload(n2Record, b1, user)
- assert.DeepEqual(t, payload, expected, "payload mismatch")
+ var payload presenters.Note
+ if err := json.NewDecoder(res.Body).Decode(&payload); err != nil {
+ t.Fatal(errors.Wrap(err, "decoding payload"))
}
+
+ var n2Record database.Note
+ testutils.MustExec(t, testutils.DB.Where("uuid = ?", publicNote.UUID).First(&n2Record), "finding n2Record")
+
+ expected := getExpectedNotePayload(n2Record, b1, user)
+ assert.DeepEqual(t, payload, expected, "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "guest accessing private note", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("guest accessing private note", func(t *testing.T) {
// Execute
- url := getURL(privateNote.UUID, target)
+ url := getURL(privateNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPDo(t, req)
// Test
assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
- if target == testutils.EndpointAPI {
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(errors.Wrap(err, "reading body"))
- }
-
- assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "reading body"))
}
+
+ assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "nonexistent", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("nonexistent", func(t *testing.T) {
// Execute
- url := getURL("somerandomstring", target)
+ url := getURL("somerandomstring")
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPAuthDo(t, req, user)
// Test
assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
- if target == testutils.EndpointAPI {
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(errors.Wrap(err, "reading body"))
- }
-
- assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "reading body"))
}
+
+ assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
})
- testutils.RunForWebAndAPI(t, "deleted", func(t *testing.T, target testutils.EndpointType) {
+ t.Run("deleted", func(t *testing.T) {
// Execute
- url := getURL(deletedNote.UUID, target)
+ url := getURL(deletedNote.UUID)
req := testutils.MakeReq(server.URL, "GET", url, "")
res := testutils.HTTPAuthDo(t, req, user)
// Test
assert.StatusCodeEquals(t, res, http.StatusNotFound, "")
- if target == testutils.EndpointAPI {
- body, err := ioutil.ReadAll(res.Body)
- if err != nil {
- t.Fatal(errors.Wrap(err, "reading body"))
- }
-
- assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
+ body, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatal(errors.Wrap(err, "reading body"))
}
+
+ assert.DeepEqual(t, string(body), "not found\n", "payload mismatch")
})
}
func TestCreateNote(t *testing.T) {
- testutils.RunForWebAndAPI(t, "success", func(t *testing.T, target testutils.EndpointType) {
- defer testutils.ClearData(testutils.DB)
+ defer testutils.ClearData(testutils.DB)
- // Setup
- server := MustNewServer(t, &app.App{
- Clock: clock.NewMock(),
- Config: config.Config{
- PageTemplateDir: "../views",
- },
- })
- defer server.Close()
-
- user := testutils.SetupUserData()
- testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
-
- b1 := database.Book{
- UserID: user.ID,
- Label: "js",
- USN: 58,
- }
- testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
-
- // Execute
-
- var req *http.Request
- if target == testutils.EndpointAPI {
- dat := fmt.Sprintf(`{"book_uuid": "%s", "content": "note content"}`, b1.UUID)
- req = testutils.MakeReq(server.URL, "POST", "/api/v3/notes", dat)
- } else {
- dat := url.Values{}
- dat.Set("book_uuid", b1.UUID)
- dat.Set("content", "note content")
- req = testutils.MakeFormReq(server.URL, "POST", "/notes", dat)
- }
- res := testutils.HTTPAuthDo(t, req, user)
-
- // Test
- assert.StatusCodeEquals(t, res, http.StatusCreated, "")
-
- var noteRecord database.Note
- var bookRecord database.Book
- var userRecord database.User
- var bookCount, noteCount int
- testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books")
- testutils.MustExec(t, testutils.DB.Model(&database.Note{}).Count(¬eCount), "counting notes")
- testutils.MustExec(t, testutils.DB.First(¬eRecord), "finding note")
- testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
- testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record")
-
- assert.Equalf(t, bookCount, 1, "book count mismatch")
- assert.Equalf(t, noteCount, 1, "note count mismatch")
-
- assert.Equal(t, bookRecord.Label, b1.Label, "book name mismatch")
- assert.Equal(t, bookRecord.UUID, b1.UUID, "book uuid mismatch")
- assert.Equal(t, bookRecord.UserID, b1.UserID, "book user_id mismatch")
- assert.Equal(t, bookRecord.USN, 58, "book usn mismatch")
-
- assert.NotEqual(t, noteRecord.UUID, "", "note uuid should have been generated")
- assert.Equal(t, noteRecord.BookUUID, b1.UUID, "note book_uuid mismatch")
- assert.Equal(t, noteRecord.Body, "note content", "note content mismatch")
- assert.Equal(t, noteRecord.USN, 102, "note usn mismatch")
+ // Setup
+ server := MustNewServer(t, &app.App{
+ Clock: clock.NewMock(),
+ Config: config.Config{
+ PageTemplateDir: "../views",
+ },
})
+ defer server.Close()
+
+ user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+ testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
+
+ b1 := database.Book{
+ UserID: user.ID,
+ Label: "js",
+ USN: 58,
+ }
+ testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1")
+
+ // Execute
+
+ dat := fmt.Sprintf(`{"book_uuid": "%s", "content": "note content"}`, b1.UUID)
+ req := testutils.MakeReq(server.URL, "POST", "/api/v3/notes", dat)
+ res := testutils.HTTPAuthDo(t, req, user)
+
+ // Test
+ assert.StatusCodeEquals(t, res, http.StatusCreated, "")
+
+ var noteRecord database.Note
+ var bookRecord database.Book
+ var userRecord database.User
+ var bookCount, noteCount int
+ testutils.MustExec(t, testutils.DB.Model(&database.Book{}).Count(&bookCount), "counting books")
+ testutils.MustExec(t, testutils.DB.Model(&database.Note{}).Count(¬eCount), "counting notes")
+ testutils.MustExec(t, testutils.DB.First(¬eRecord), "finding note")
+ testutils.MustExec(t, testutils.DB.Where("id = ?", b1.ID).First(&bookRecord), "finding book")
+ testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record")
+
+ assert.Equalf(t, bookCount, 1, "book count mismatch")
+ assert.Equalf(t, noteCount, 1, "note count mismatch")
+
+ assert.Equal(t, bookRecord.Label, b1.Label, "book name mismatch")
+ assert.Equal(t, bookRecord.UUID, b1.UUID, "book uuid mismatch")
+ assert.Equal(t, bookRecord.UserID, b1.UserID, "book user_id mismatch")
+ assert.Equal(t, bookRecord.USN, 58, "book usn mismatch")
+
+ assert.NotEqual(t, noteRecord.UUID, "", "note uuid should have been generated")
+ assert.Equal(t, noteRecord.BookUUID, b1.UUID, "note book_uuid mismatch")
+ assert.Equal(t, noteRecord.Body, "note content", "note content mismatch")
+ assert.Equal(t, noteRecord.USN, 102, "note usn mismatch")
}
func TestDeleteNote(t *testing.T) {
@@ -490,8 +453,8 @@ func TestDeleteNote(t *testing.T) {
},
}
- for _, tc := range testCases {
- testutils.RunForWebAndAPI(t, fmt.Sprintf("originally deleted %t", tc.deleted), func(t *testing.T, target testutils.EndpointType) {
+ for idx, tc := range testCases {
+ t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
defer testutils.ClearData(testutils.DB)
// Setup
@@ -504,6 +467,7 @@ func TestDeleteNote(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 981), "preparing user max_usn")
b1 := database.Book{
@@ -522,14 +486,8 @@ func TestDeleteNote(t *testing.T) {
testutils.MustExec(t, testutils.DB.Save(¬e), "preparing note")
// Execute
- var req *http.Request
- if target == testutils.EndpointAPI {
- endpoint := fmt.Sprintf("/api/v3/notes/%s", note.UUID)
- req = testutils.MakeReq(server.URL, "DELETE", endpoint, "")
- } else {
- endpoint := fmt.Sprintf("/notes/%s", note.UUID)
- req = testutils.MakeFormReq(server.URL, "DELETE", endpoint, nil)
- }
+ endpoint := fmt.Sprintf("/api/v3/notes/%s", note.UUID)
+ req := testutils.MakeReq(server.URL, "DELETE", endpoint, "")
res := testutils.HTTPAuthDo(t, req, user)
// Test
@@ -736,7 +694,7 @@ func TestUpdateNote(t *testing.T) {
}
for idx, tc := range testCases {
- testutils.RunForWebAndAPI(t, fmt.Sprintf("test case %d", idx), func(t *testing.T, target testutils.EndpointType) {
+ t.Run(fmt.Sprintf("test case %d", idx), func(t *testing.T) {
defer testutils.ClearData(testutils.DB)
// Setup
@@ -749,6 +707,8 @@ func TestUpdateNote(t *testing.T) {
defer server.Close()
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+
testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn")
b1 := database.Book{
@@ -777,13 +737,8 @@ func TestUpdateNote(t *testing.T) {
// Execute
var req *http.Request
- if target == testutils.EndpointWeb {
- endpoint := fmt.Sprintf("/notes/%s", note.UUID)
- req = testutils.MakeFormReq(server.URL, "PATCH", endpoint, tc.payload.ToURLValues())
- } else {
- endpoint := fmt.Sprintf("/api/v3/notes/%s", note.UUID)
- req = testutils.MakeReq(server.URL, "PATCH", endpoint, tc.payload.ToJSON(t))
- }
+ endpoint := fmt.Sprintf("/api/v3/notes/%s", note.UUID)
+ req = testutils.MakeReq(server.URL, "PATCH", endpoint, tc.payload.ToJSON(t))
res := testutils.HTTPAuthDo(t, req, user)
diff --git a/pkg/server/controllers/routes.go b/pkg/server/controllers/routes.go
index 2bb36572..785c9ad4 100644
--- a/pkg/server/controllers/routes.go
+++ b/pkg/server/controllers/routes.go
@@ -29,20 +29,11 @@ func NewWebRoutes(a *app.App, c *Controllers) []Route {
redirectGuest := &mw.AuthParams{RedirectGuestsToLogin: true}
ret := []Route{
- {"GET", "/", mw.Auth(a, c.Notes.Index, redirectGuest), true},
+ {"GET", "/", mw.Auth(a, c.Users.Settings, redirectGuest), true},
{"GET", "/login", mw.GuestOnly(a, c.Users.NewLogin), true},
{"POST", "/login", mw.GuestOnly(a, c.Users.Login), true},
{"POST", "/logout", c.Users.Logout, true},
- {"GET", "/notes/{noteUUID}", mw.Auth(a, c.Notes.Show, nil), true},
- {"POST", "/notes", mw.Auth(a, c.Notes.Create, nil), true},
- {"DELETE", "/notes/{noteUUID}", mw.Auth(a, c.Notes.Delete, nil), true},
- {"PATCH", "/notes/{noteUUID}", mw.Auth(a, c.Notes.Update, nil), true},
- {"GET", "/books", mw.Auth(a, c.Books.Index, redirectGuest), true},
- {"POST", "/books", mw.Auth(a, c.Books.Create, nil), true},
- {"PATCH", "/books/{bookUUID}", mw.Auth(a, c.Books.Update, nil), true},
- {"DELETE", "/books/{bookUUID}", mw.Auth(a, c.Books.Delete, nil), true},
- {"GET", "/settings", mw.Auth(a, c.Users.Settings, nil), true},
{"GET", "/password-reset", c.Users.PasswordResetView.ServeHTTP, true},
{"PATCH", "/password-reset", c.Users.PasswordReset, true},
{"GET", "/password-reset/{token}", c.Users.PasswordResetConfirm, true},
diff --git a/pkg/server/middleware/helpers_test.go b/pkg/server/middleware/helpers_test.go
index dd6fc447..1eb9fe7a 100644
--- a/pkg/server/middleware/helpers_test.go
+++ b/pkg/server/middleware/helpers_test.go
@@ -184,6 +184,8 @@ func TestAuthMiddleware(t *testing.T) {
defer testutils.ClearData(testutils.DB)
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+
session := database.Session{
Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=",
UserID: user.ID,
@@ -412,6 +414,8 @@ func TestAuthMiddleware_RedirectGuestsToLogin(t *testing.T) {
req := testutils.MakeReq(server.URL, "GET", "/", "")
user := testutils.SetupUserData()
+ testutils.SetupAccountData(user, "alice@test.com", "pass1234")
+
testutils.MustExec(t, testutils.DB.Model(&user).Update("cloud", false), "preparing session")
session := database.Session{
Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=",
diff --git a/pkg/server/views/layouts/navbar.gohtml b/pkg/server/views/layouts/navbar.gohtml
index 00487757..d7d0f78b 100644
--- a/pkg/server/views/layouts/navbar.gohtml
+++ b/pkg/server/views/layouts/navbar.gohtml
@@ -12,9 +12,6 @@
{{if .User}}