From 01a378c5b162c73052beb76ab34796fb66079df7 Mon Sep 17 00:00:00 2001 From: Sung Won Cho Date: Sun, 24 Apr 2022 10:54:39 +1000 Subject: [PATCH] Simplify by removing web interface (#590) * Implement MVC * Implement settings * Improve layout * Lock sass dependency --- Makefile | 2 + go.mod | 12 +- go.sum | 49 +- pkg/server/.gitignore | 1 + pkg/server/api/auth.go | 187 --- pkg/server/api/auth_test.go | 408 ----- pkg/server/api/helpers.go | 85 - pkg/server/api/notes.go | 324 ---- pkg/server/api/notes_test.go | 357 ----- pkg/server/api/routes.go | 116 -- pkg/server/api/routes_test.go | 161 -- pkg/server/api/user.go | 394 ----- pkg/server/api/user_test.go | 691 -------- pkg/server/api/v3_auth.go | 226 --- pkg/server/api/v3_auth_test.go | 482 ------ pkg/server/api/v3_books.go | 267 ---- pkg/server/api/v3_notes.go | 220 --- pkg/server/api/v3_notes_test.go | 394 ----- pkg/server/app/app.go | 1 + pkg/server/app/email.go | 8 + pkg/server/app/errors.go | 67 + pkg/server/app/notes.go | 144 ++ pkg/server/app/testutils.go | 7 + pkg/server/app/users.go | 62 +- pkg/server/app/users_test.go | 55 +- pkg/server/assets/js/build.sh | 24 + pkg/server/assets/js/src/main.js | 59 + pkg/server/assets/package-lock.json | 157 ++ pkg/server/assets/package.json | 12 + pkg/server/assets/static/500.html | 10 + .../assets/static/android-icon-144x144.png | Bin 0 -> 2448 bytes .../assets/static/android-icon-192x192.png | Bin 0 -> 1731 bytes .../assets/static/android-icon-36x36.png | Bin 0 -> 1406 bytes .../assets/static/android-icon-48x48.png | Bin 0 -> 1454 bytes .../assets/static/android-icon-72x72.png | Bin 0 -> 1583 bytes .../assets/static/android-icon-96x96.png | Bin 0 -> 1777 bytes .../assets/static/apple-icon-114x114.png | Bin 0 -> 2154 bytes .../assets/static/apple-icon-120x120.png | Bin 0 -> 2181 bytes .../assets/static/apple-icon-144x144.png | Bin 0 -> 2448 bytes .../assets/static/apple-icon-152x152.png | Bin 0 -> 2709 bytes .../assets/static/apple-icon-180x180.png | Bin 0 -> 3177 bytes pkg/server/assets/static/apple-icon-57x57.png | Bin 0 -> 1647 bytes pkg/server/assets/static/apple-icon-60x60.png | Bin 0 -> 1566 bytes pkg/server/assets/static/apple-icon-72x72.png | Bin 0 -> 1583 bytes pkg/server/assets/static/apple-icon-76x76.png | Bin 0 -> 1676 bytes .../assets/static/apple-icon-precomposed.png | Bin 0 -> 2303 bytes pkg/server/assets/static/apple-icon.png | Bin 0 -> 2303 bytes pkg/server/assets/static/browserconfig.xml | 2 + pkg/server/assets/static/favicon-16x16.png | Bin 0 -> 1061 bytes pkg/server/assets/static/favicon-32x32.png | Bin 0 -> 1339 bytes pkg/server/assets/static/favicon-96x96.png | Bin 0 -> 1777 bytes pkg/server/assets/static/favicon.ico | Bin 0 -> 1150 bytes pkg/server/assets/static/logo-512x512.png | Bin 0 -> 4155 bytes pkg/server/assets/static/manifest.json | 52 + pkg/server/assets/static/ms-icon-144x144.png | Bin 0 -> 2448 bytes pkg/server/assets/static/ms-icon-150x150.png | Bin 0 -> 2665 bytes pkg/server/assets/static/ms-icon-310x310.png | Bin 0 -> 8005 bytes pkg/server/assets/static/ms-icon-70x70.png | Bin 0 -> 1685 bytes pkg/server/assets/static/offline.html | 41 + pkg/server/assets/styles/build.sh | 24 + pkg/server/assets/styles/src/_books.scss | 11 + pkg/server/assets/styles/src/_bootstrap.scss | 176 +++ pkg/server/assets/styles/src/_buttons.scss | 182 +++ pkg/server/assets/styles/src/_font.scss | 111 ++ pkg/server/assets/styles/src/_global.scss | 85 + pkg/server/assets/styles/src/_grid.scss | 1108 +++++++++++++ pkg/server/assets/styles/src/_header.scss | 192 +++ pkg/server/assets/styles/src/_hljs.scss | 147 ++ pkg/server/assets/styles/src/_home.scss | 185 +++ pkg/server/assets/styles/src/_login.scss | 88 ++ pkg/server/assets/styles/src/_markdown.scss | 966 ++++++++++++ pkg/server/assets/styles/src/_marker.scss | 39 + pkg/server/assets/styles/src/_note.scss | 102 ++ pkg/server/assets/styles/src/_reboot.scss | 367 +++++ pkg/server/assets/styles/src/_rem.scss | 116 ++ pkg/server/assets/styles/src/_responsive.scss | 62 + pkg/server/assets/styles/src/_select.scss | 463 ++++++ pkg/server/assets/styles/src/_settings.scss | 147 ++ pkg/server/assets/styles/src/_shared.scss | 241 +++ pkg/server/assets/styles/src/_theme.scss | 47 + .../styles/src/_variables.scss} | 17 +- pkg/server/assets/styles/src/main.scss | 144 ++ pkg/server/buildinfo/info.go | 15 + pkg/server/config/config.go | 41 + pkg/server/consts/consts.go | 10 + pkg/server/context/user.go | 64 + pkg/server/controllers/books.go | 297 ++++ .../books_test.go} | 547 ++++--- pkg/server/controllers/controllers.go | 32 + pkg/server/controllers/health.go | 23 + .../{api => controllers}/health_test.go | 17 +- pkg/server/controllers/helpers.go | 315 ++++ .../{job/remind => controllers}/main_test.go | 2 +- pkg/server/controllers/notes.go | 446 ++++++ pkg/server/controllers/notes_test.go | 770 +++++++++ pkg/server/controllers/routes.go | 124 ++ pkg/server/controllers/routes_test.go | 77 + pkg/server/controllers/static.go | 35 + .../{api/v3_sync.go => controllers/sync.go} | 56 +- .../sync_test.go} | 2 +- pkg/server/{api => controllers}/testutils.go | 29 +- pkg/server/controllers/users.go | 718 +++++++++ pkg/server/controllers/users_test.go | 1388 +++++++++++++++++ pkg/server/database/errors.go | 12 + .../{handlers/main_test.go => helpers/url.go} | 23 +- pkg/server/helpers/url_test.go | 29 + pkg/server/helpers/{helpers.go => uuid.go} | 0 pkg/server/job/job.go | 26 - pkg/server/job/remind/inactive.go | 210 --- pkg/server/job/remind/inactive_test.go | 194 --- .../mailer/templates/src/reset_password.txt | 2 +- .../templates/src/reset_password_alert.txt | 2 +- .../src/subscription_confirmation.txt | 4 +- .../mailer/templates/src/verify_email.txt | 6 +- pkg/server/mailer/templates/src/welcome.txt | 2 +- pkg/server/main.go | 107 +- pkg/server/{handlers => middleware}/auth.go | 59 +- .../{handlers => middleware}/helpers.go | 13 +- .../{handlers => middleware}/helpers_test.go | 8 +- pkg/server/{handlers => middleware}/limit.go | 14 +- .../{handlers => middleware}/logging.go | 4 +- pkg/server/{api => middleware}/main_test.go | 2 +- pkg/server/middleware/middleware.go | 76 + pkg/server/net/writer.go | 31 + pkg/server/operations/notes.go | 4 +- pkg/server/operations/notes_test.go | 4 +- pkg/server/static/main.css | 12 + pkg/server/static/main.css.map | 1 + pkg/server/testutils/main.go | 80 + pkg/server/tmpl/data.go | 6 +- pkg/server/views/books/index.gohtml | 20 + pkg/server/views/books/show.gohtml | 4 + pkg/server/views/data.go | 152 ++ pkg/server/views/helpers.go | 176 +++ pkg/server/views/helpers_test.go | 183 +++ pkg/server/views/icons/book.gohtml | 17 + pkg/server/views/icons/caret.gohtml | 26 + pkg/server/views/icons/lock.gohtml | 10 + pkg/server/views/icons/logo.gohtml | 14 + pkg/server/views/icons/logo_with_text.gohtml | 26 + pkg/server/views/layouts/alert.gohtml | 9 + pkg/server/views/layouts/base.gohtml | 40 + pkg/server/views/layouts/css.gohtml | 5 + pkg/server/views/layouts/header.gohtml | 7 + pkg/server/views/layouts/js.gohtml | 5 + pkg/server/views/layouts/navbar.gohtml | 68 + pkg/server/views/notes/index.gohtml | 91 ++ pkg/server/views/notes/show.gohtml | 33 + pkg/server/views/partials/page_toolbar.gohtml | 5 + .../views/partials/settings_sidebar.gohtml | 23 + pkg/server/views/partials/time.gohtml | 13 + pkg/server/views/static/not_found.gohtml | 3 + pkg/server/views/time.go | 103 ++ .../views/users/email_verification.gohtml | 2 + pkg/server/views/users/login.gohtml | 76 + pkg/server/views/users/new.gohtml | 86 + pkg/server/views/users/password_reset.gohtml | 52 + .../views/users/password_reset_confirm.gohtml | 66 + pkg/server/views/users/settings.gohtml | 241 +++ pkg/server/views/users/settings_about.gohtml | 57 + pkg/server/views/view.go | 203 +++ pkg/server/web/handlers.go | 6 +- pkg/watcher/main.go | 21 +- scripts/server/test-local.sh | 4 +- scripts/server/test.sh | 10 +- scripts/vagrant/install_utils.sh | 7 + scripts/web/dev.sh | 40 +- test/cli/test-cli | Bin 0 -> 16934752 bytes 168 files changed, 12773 insertions(+), 5167 deletions(-) delete mode 100644 pkg/server/api/auth.go delete mode 100644 pkg/server/api/auth_test.go delete mode 100644 pkg/server/api/helpers.go delete mode 100644 pkg/server/api/notes.go delete mode 100644 pkg/server/api/notes_test.go delete mode 100644 pkg/server/api/routes.go delete mode 100644 pkg/server/api/routes_test.go delete mode 100644 pkg/server/api/user.go delete mode 100644 pkg/server/api/user_test.go delete mode 100644 pkg/server/api/v3_auth.go delete mode 100644 pkg/server/api/v3_auth_test.go delete mode 100644 pkg/server/api/v3_books.go delete mode 100644 pkg/server/api/v3_notes.go delete mode 100644 pkg/server/api/v3_notes_test.go create mode 100644 pkg/server/app/errors.go create mode 100755 pkg/server/assets/js/build.sh create mode 100644 pkg/server/assets/js/src/main.js create mode 100644 pkg/server/assets/package-lock.json create mode 100644 pkg/server/assets/package.json create mode 100644 pkg/server/assets/static/500.html create mode 100644 pkg/server/assets/static/android-icon-144x144.png create mode 100644 pkg/server/assets/static/android-icon-192x192.png create mode 100644 pkg/server/assets/static/android-icon-36x36.png create mode 100644 pkg/server/assets/static/android-icon-48x48.png create mode 100644 pkg/server/assets/static/android-icon-72x72.png create mode 100644 pkg/server/assets/static/android-icon-96x96.png create mode 100644 pkg/server/assets/static/apple-icon-114x114.png create mode 100644 pkg/server/assets/static/apple-icon-120x120.png create mode 100644 pkg/server/assets/static/apple-icon-144x144.png create mode 100644 pkg/server/assets/static/apple-icon-152x152.png create mode 100644 pkg/server/assets/static/apple-icon-180x180.png create mode 100644 pkg/server/assets/static/apple-icon-57x57.png create mode 100644 pkg/server/assets/static/apple-icon-60x60.png create mode 100644 pkg/server/assets/static/apple-icon-72x72.png create mode 100644 pkg/server/assets/static/apple-icon-76x76.png create mode 100644 pkg/server/assets/static/apple-icon-precomposed.png create mode 100644 pkg/server/assets/static/apple-icon.png create mode 100644 pkg/server/assets/static/browserconfig.xml create mode 100644 pkg/server/assets/static/favicon-16x16.png create mode 100644 pkg/server/assets/static/favicon-32x32.png create mode 100644 pkg/server/assets/static/favicon-96x96.png create mode 100644 pkg/server/assets/static/favicon.ico create mode 100644 pkg/server/assets/static/logo-512x512.png create mode 100644 pkg/server/assets/static/manifest.json create mode 100644 pkg/server/assets/static/ms-icon-144x144.png create mode 100644 pkg/server/assets/static/ms-icon-150x150.png create mode 100644 pkg/server/assets/static/ms-icon-310x310.png create mode 100644 pkg/server/assets/static/ms-icon-70x70.png create mode 100644 pkg/server/assets/static/offline.html create mode 100755 pkg/server/assets/styles/build.sh create mode 100644 pkg/server/assets/styles/src/_books.scss create mode 100644 pkg/server/assets/styles/src/_bootstrap.scss create mode 100644 pkg/server/assets/styles/src/_buttons.scss create mode 100644 pkg/server/assets/styles/src/_font.scss create mode 100644 pkg/server/assets/styles/src/_global.scss create mode 100644 pkg/server/assets/styles/src/_grid.scss create mode 100644 pkg/server/assets/styles/src/_header.scss create mode 100644 pkg/server/assets/styles/src/_hljs.scss create mode 100644 pkg/server/assets/styles/src/_home.scss create mode 100644 pkg/server/assets/styles/src/_login.scss create mode 100644 pkg/server/assets/styles/src/_markdown.scss create mode 100644 pkg/server/assets/styles/src/_marker.scss create mode 100644 pkg/server/assets/styles/src/_note.scss create mode 100644 pkg/server/assets/styles/src/_reboot.scss create mode 100644 pkg/server/assets/styles/src/_rem.scss create mode 100644 pkg/server/assets/styles/src/_responsive.scss create mode 100644 pkg/server/assets/styles/src/_select.scss create mode 100644 pkg/server/assets/styles/src/_settings.scss create mode 100644 pkg/server/assets/styles/src/_shared.scss create mode 100644 pkg/server/assets/styles/src/_theme.scss rename pkg/server/{api/health.go => assets/styles/src/_variables.scss} (76%) create mode 100644 pkg/server/assets/styles/src/main.scss create mode 100644 pkg/server/buildinfo/info.go create mode 100644 pkg/server/consts/consts.go create mode 100644 pkg/server/context/user.go create mode 100644 pkg/server/controllers/books.go rename pkg/server/{api/v3_books_test.go => controllers/books_test.go} (65%) create mode 100644 pkg/server/controllers/controllers.go create mode 100644 pkg/server/controllers/health.go rename pkg/server/{api => controllers}/health_test.go (80%) create mode 100644 pkg/server/controllers/helpers.go rename pkg/server/{job/remind => controllers}/main_test.go (97%) create mode 100644 pkg/server/controllers/notes.go create mode 100644 pkg/server/controllers/notes_test.go create mode 100644 pkg/server/controllers/routes.go create mode 100644 pkg/server/controllers/routes_test.go create mode 100644 pkg/server/controllers/static.go rename pkg/server/{api/v3_sync.go => controllers/sync.go} (83%) rename pkg/server/{api/v3_sync_test.go => controllers/sync_test.go} (99%) rename pkg/server/{api => controllers}/testutils.go (67%) create mode 100644 pkg/server/controllers/users.go create mode 100644 pkg/server/controllers/users_test.go create mode 100644 pkg/server/database/errors.go rename pkg/server/{handlers/main_test.go => helpers/url.go} (70%) create mode 100644 pkg/server/helpers/url_test.go rename pkg/server/helpers/{helpers.go => uuid.go} (100%) delete mode 100644 pkg/server/job/remind/inactive.go delete mode 100644 pkg/server/job/remind/inactive_test.go rename pkg/server/{handlers => middleware}/auth.go (76%) rename pkg/server/{handlers => middleware}/helpers.go (91%) rename pkg/server/{handlers => middleware}/helpers_test.go (98%) rename pkg/server/{handlers => middleware}/limit.go (92%) rename pkg/server/{handlers => middleware}/logging.go (97%) rename pkg/server/{api => middleware}/main_test.go (98%) create mode 100644 pkg/server/middleware/middleware.go create mode 100644 pkg/server/net/writer.go create mode 100644 pkg/server/static/main.css create mode 100644 pkg/server/static/main.css.map create mode 100644 pkg/server/views/books/index.gohtml create mode 100644 pkg/server/views/books/show.gohtml create mode 100644 pkg/server/views/data.go create mode 100644 pkg/server/views/helpers.go create mode 100644 pkg/server/views/helpers_test.go create mode 100644 pkg/server/views/icons/book.gohtml create mode 100644 pkg/server/views/icons/caret.gohtml create mode 100644 pkg/server/views/icons/lock.gohtml create mode 100644 pkg/server/views/icons/logo.gohtml create mode 100644 pkg/server/views/icons/logo_with_text.gohtml create mode 100644 pkg/server/views/layouts/alert.gohtml create mode 100644 pkg/server/views/layouts/base.gohtml create mode 100644 pkg/server/views/layouts/css.gohtml create mode 100644 pkg/server/views/layouts/header.gohtml create mode 100644 pkg/server/views/layouts/js.gohtml create mode 100644 pkg/server/views/layouts/navbar.gohtml create mode 100644 pkg/server/views/notes/index.gohtml create mode 100644 pkg/server/views/notes/show.gohtml create mode 100644 pkg/server/views/partials/page_toolbar.gohtml create mode 100644 pkg/server/views/partials/settings_sidebar.gohtml create mode 100644 pkg/server/views/partials/time.gohtml create mode 100644 pkg/server/views/static/not_found.gohtml create mode 100644 pkg/server/views/time.go create mode 100644 pkg/server/views/users/email_verification.gohtml create mode 100644 pkg/server/views/users/login.gohtml create mode 100644 pkg/server/views/users/new.gohtml create mode 100644 pkg/server/views/users/password_reset.gohtml create mode 100644 pkg/server/views/users/password_reset_confirm.gohtml create mode 100644 pkg/server/views/users/settings.gohtml create mode 100644 pkg/server/views/users/settings_about.gohtml create mode 100644 pkg/server/views/view.go create mode 100755 test/cli/test-cli diff --git a/Makefile b/Makefile index c7a3eccd..c0dedcdc 100644 --- a/Makefile +++ b/Makefile @@ -30,11 +30,13 @@ endif ifeq ($(CI), true) @(cd ${currentDir} && npm ci --cache $(NPM_CACHE_DIR) --prefer-offline --unsafe-perm=true) + @(cd ${currentDir}/pkg/server/assets && npm ci --cache $(NPM_CACHE_DIR) --prefer-offline --unsafe-perm=true) @(cd ${currentDir}/web && npm ci --cache $(NPM_CACHE_DIR) --prefer-offline --unsafe-perm=true) @(cd ${currentDir}/browser && npm ci --cache $(NPM_CACHE_DIR) --prefer-offline --unsafe-perm=true) @(cd ${currentDir}/jslib && npm ci --cache $(NPM_CACHE_DIR) --prefer-offline --unsafe-perm=true) else @(cd ${currentDir} && npm install) + @(cd ${currentDir}/pkg/server/assets && npm install) @(cd ${currentDir}/web && npm install) @(cd ${currentDir}/browser && npm install) @(cd ${currentDir}/jslib && npm install) diff --git a/go.mod b/go.mod index 14d37869..0150d739 100644 --- a/go.mod +++ b/go.mod @@ -8,34 +8,36 @@ require ( github.com/aymerick/douceur v0.2.0 github.com/dnote/actions v0.2.0 github.com/dnote/color v1.7.0 - github.com/dnote/xgo v0.0.0-20200205013105-40be7d6d43ff // indirect github.com/gobuffalo/packr/v2 v2.8.1 github.com/google/go-cmp v0.5.4 github.com/google/go-github v17.0.0+incompatible - github.com/google/go-querystring v1.0.0 // indirect github.com/google/uuid v1.1.3 - github.com/gorilla/css v1.0.0 // indirect + github.com/gorilla/csrf v1.6.2 github.com/gorilla/mux v1.8.0 + github.com/gorilla/schema v1.2.0 github.com/jinzhu/gorm v1.9.16 github.com/joho/godotenv v1.3.0 github.com/karrick/godirwalk v1.16.1 // indirect github.com/lib/pq v1.9.0 github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-sqlite3 v1.14.6 + github.com/nadproject/nad v0.0.0-20200124233812-f1a4e763ee2f github.com/pkg/errors v0.9.1 github.com/radovskyb/watcher v1.0.7 github.com/robfig/cron v1.2.0 + github.com/rogpeppe/go-internal v1.6.2 // indirect github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 github.com/sergi/go-diff v1.1.0 github.com/sirupsen/logrus v1.7.0 // indirect github.com/spf13/cobra v1.1.1 + github.com/yuin/goldmark v1.4.0 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect - golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect + golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect golang.org/x/sys v0.0.0-20201231184435-2d18734c6014 // indirect golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 - gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 238bdc3a..41625712 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= @@ -15,6 +16,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/PuerkitoBio/goquery v1.6.0 h1:j7taAbelrdcsOlGeMenZxc2AWXD5fieT1/znArdnx94= @@ -27,6 +29,7 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE= github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= @@ -73,6 +76,7 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= @@ -82,8 +86,6 @@ github.com/dnote/actions v0.2.0 h1:P1ut2/QRKwfAzIIB374vN9A4IanU94C/payEocvngYo= github.com/dnote/actions v0.2.0/go.mod h1:bBIassLhppVQdbC3iaE92SHBpM1HOVe+xZoAlj9ROxw= github.com/dnote/color v1.7.0 h1:8/QGLQKSU8/zcWQaHbMyC1hJRkKO/Uu9M89sH76ecHE= github.com/dnote/color v1.7.0/go.mod h1:75UcP/TH7CNvjQ5pwDumkUS3vkPdGggy7/3fT8MlxHM= -github.com/dnote/xgo v0.0.0-20200205013105-40be7d6d43ff h1:DJKdzouhr6u1NzuLbmSWeei9BagH3Nm4mSOzP0RMdc0= -github.com/dnote/xgo v0.0.0-20200205013105-40be7d6d43ff/go.mod h1:ruGZjl8WThApI7BAIKV2Q/PnJoudvd6Epjc3z79jWVg= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= @@ -113,12 +115,16 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gobuffalo/packr/v2 v2.8.1 h1:tkQpju6i3EtMXJ9uoF5GT6kB+LMTimDWD8Xvbz6zDVA= github.com/gobuffalo/packr/v2 v2.8.1/go.mod h1:c/PLlOuTU+p3SybaJATW3H6lX/iK7xEz5OeMf+NnJpg= @@ -158,18 +164,27 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.3 h1:twObb+9XcuH5B9V1TBCvvvZoO6iEdILi2a76PYn5rJI= github.com/google/uuid v1.1.3/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/csrf v1.6.2 h1:QqQ/OWwuFp4jMKgBFAzJVW3FMULdyUW7JoM4pEWuqKg= +github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -206,6 +221,7 @@ github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmK github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jinzhu/gorm v1.9.9/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -222,6 +238,9 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/justincampbell/bigduration v0.0.0-20160531141349-e45bf03c0666/go.mod h1:xqGOmDZzLOG7+q/CgsbXv10g4tgPsbjhmAxyaTJMvis= +github.com/justincampbell/timeago v0.0.0-20160528003754-027f40306f1d/go.mod h1:U7FWcK1jzZJnYuSnxP6efX3ZoHbK1CEpD0ThYyGNPNI= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/karrick/godirwalk v1.15.8/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= @@ -255,17 +274,17 @@ github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= -github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -281,6 +300,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nadproject/color v1.7.0/go.mod h1:p2KusS2iX8Q7ncpngDmtva/kZmiad9Hv5MFS4SLuCZQ= +github.com/nadproject/nad v0.0.0-20200124233812-f1a4e763ee2f h1:Vq2SFUt+Mrle7Irf7rLOnYBegSVF3tyNbsMnDomWfH8= +github.com/nadproject/nad v0.0.0-20200124233812-f1a4e763ee2f/go.mod h1:mGl2lRU9Xo49kzVYj46FwP+pEP/Um+nIqTdCmPHtI5k= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= @@ -354,13 +376,18 @@ github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= +github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubenv/sql-migrate v0.0.0-20190618074426-f4d34eae5a5c/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 h1:HXr/qUllAWv9riaI4zh2eXWKmCSDqVS/XH1MRHLKRwk= github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -398,6 +425,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stripe/stripe-go v61.7.1+incompatible/go.mod h1:A1dQZmO/QypXmsL0T8axYZkSN/uA/T/A64pfKdBAMiY= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= @@ -407,6 +435,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -493,8 +523,8 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -505,6 +535,7 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -552,6 +583,7 @@ golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -562,11 +594,14 @@ golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE= golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -581,6 +616,7 @@ google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -610,6 +646,7 @@ gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gomail.v2 v2.0.0-20150902115704-41f357289737/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gorp.v1 v1.7.2 h1:j3DWlAyGVv8whO7AcIWznQ2Yj7yJkn34B8s63GViAAw= diff --git a/pkg/server/.gitignore b/pkg/server/.gitignore index fa49c96a..9835f3e3 100644 --- a/pkg/server/.gitignore +++ b/pkg/server/.gitignore @@ -6,3 +6,4 @@ test-dnote /dist /build server +/static 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 60c990fd..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 d87c2ff7..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/handlers" - "github.com/dnote/dnote/pkg/server/helpers" - "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) - - handlers.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 := handlers.AuthWithSession(a.App.DB, r, nil) - if err != nil { - handlers.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 { - handlers.RespondNotFound(w) - return - } - if err != nil { - handlers.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 { - handlers.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 { - handlers.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 { - handlers.DoError(w, "finding notes", err, http.StatusInternalServerError) - return - } - } - - response := GetNotesResponse{ - Notes: presenters.PresentNotes(notes), - Total: total, - } - handlers.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 { - handlers.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 { - handlers.DoError(w, "finding notes", err, http.StatusInternalServerError) - return - } - - presented := presenters.PresentNotes(notes) - handlers.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 c47caf4e..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 d1b771bf..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/handlers" - "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 = handlers.Logging(ret) - - if rateLimit && os.Getenv("GO_ENV") != "TEST" { - ret = handlers.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 := handlers.AuthParams{ProOnly: true} - app := a.App - - var routes = []handlers.Route{ - // internal - {Method: "GET", Pattern: "/health", HandlerFunc: a.checkHealth, RateLimit: false}, - {Method: "GET", Pattern: "/me", HandlerFunc: handlers.Auth(app, a.getMe, nil), RateLimit: true}, - {Method: "POST", Pattern: "/verification-token", HandlerFunc: handlers.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: handlers.Auth(app, a.updateProfile, nil), RateLimit: true}, - {Method: "PATCH", Pattern: "/account/password", HandlerFunc: handlers.Auth(app, a.updatePassword, nil), RateLimit: true}, - {Method: "GET", Pattern: "/account/email-preference", HandlerFunc: handlers.TokenAuth(app, a.getEmailPreference, database.TokenTypeEmailPreference, nil), RateLimit: true}, - {Method: "PATCH", Pattern: "/account/email-preference", HandlerFunc: handlers.TokenAuth(app, a.updateEmailPreference, database.TokenTypeEmailPreference, nil), RateLimit: true}, - {Method: "GET", Pattern: "/notes", HandlerFunc: handlers.Auth(app, a.getNotes, nil), RateLimit: false}, - {Method: "GET", Pattern: "/notes/{noteUUID}", HandlerFunc: a.getNote, RateLimit: true}, - {Method: "GET", Pattern: "/calendar", HandlerFunc: handlers.Auth(app, a.getCalendar, nil), RateLimit: true}, - - // v3 - {Method: "GET", Pattern: "/v3/sync/fragment", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetSyncFragment, &proOnly)), RateLimit: false}, - {Method: "GET", Pattern: "/v3/sync/state", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetSyncState, &proOnly)), RateLimit: false}, - {Method: "OPTIONS", Pattern: "/v3/books", HandlerFunc: handlers.Cors(a.BooksOptions), RateLimit: true}, - {Method: "GET", Pattern: "/v3/books", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetBooks, &proOnly)), RateLimit: true}, - {Method: "GET", Pattern: "/v3/books/{bookUUID}", HandlerFunc: handlers.Cors(handlers.Auth(app, a.GetBook, &proOnly)), RateLimit: true}, - {Method: "POST", Pattern: "/v3/books", HandlerFunc: handlers.Cors(handlers.Auth(app, a.CreateBook, &proOnly)), RateLimit: false}, - {Method: "PATCH", Pattern: "/v3/books/{bookUUID}", HandlerFunc: handlers.Cors(handlers.Auth(app, a.UpdateBook, &proOnly)), RateLimit: false}, - {Method: "DELETE", Pattern: "/v3/books/{bookUUID}", HandlerFunc: handlers.Cors(handlers.Auth(app, a.DeleteBook, &proOnly)), RateLimit: false}, - {Method: "OPTIONS", Pattern: "/v3/notes", HandlerFunc: handlers.Cors(a.NotesOptions), RateLimit: true}, - {Method: "POST", Pattern: "/v3/notes", HandlerFunc: handlers.Cors(handlers.Auth(app, a.CreateNote, &proOnly)), RateLimit: false}, - {Method: "PATCH", Pattern: "/v3/notes/{noteUUID}", HandlerFunc: handlers.Auth(app, a.UpdateNote, &proOnly), RateLimit: false}, - {Method: "DELETE", Pattern: "/v3/notes/{noteUUID}", HandlerFunc: handlers.Auth(app, a.DeleteNote, &proOnly), RateLimit: false}, - {Method: "POST", Pattern: "/v3/signin", HandlerFunc: handlers.Cors(a.signin), RateLimit: true}, - {Method: "OPTIONS", Pattern: "/v3/signout", HandlerFunc: handlers.Cors(a.signoutOptions), RateLimit: true}, - {Method: "POST", Pattern: "/v3/signout", HandlerFunc: handlers.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(handlers.NotSupported, true)) - router.PathPrefix("/v2").Handler(applyMiddleware(handlers.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/user.go b/pkg/server/api/user.go deleted file mode 100644 index d4a8ec51..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/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/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 { - 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, "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() - handlers.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() - handlers.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 { - handlers.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 { - handlers.DoError(w, "counting notes", err, http.StatusInternalServerError) - } - payload[d.Format("2006-1-2")] = count - } - - handlers.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 { - handlers.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 { - handlers.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 { - handlers.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 { - handlers.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 { - handlers.RespondInvalidSMTPConfig(w) - } else { - handlers.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 { - handlers.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 { - handlers.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() - handlers.DoError(w, "updating email_verified", err, http.StatusInternalServerError) - return - } - if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { - tx.Rollback() - handlers.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 { - handlers.DoError(w, "finding user", err, http.StatusInternalServerError) - return - } - - s := session.New(user, account) - handlers.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 { - handlers.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) - return - } - - var params emailPreferernceParams - if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { - handlers.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 { - handlers.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() - handlers.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() - handlers.DoError(w, "updating reset token", err, http.StatusInternalServerError) - return - } - } - - tx.Commit() - - handlers.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 { - handlers.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 { - handlers.DoError(w, "finding pref", err, http.StatusInternalServerError) - return - } - - presented := presenters.PresentEmailPreference(pref) - handlers.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 { - handlers.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 { - handlers.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 95df0179..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/app/app.go b/pkg/server/app/app.go index c530d4fa..06e50010 100644 --- a/pkg/server/app/app.go +++ b/pkg/server/app/app.go @@ -46,6 +46,7 @@ type App struct { EmailTemplates mailer.Templates EmailBackend mailer.Backend Config config.Config + Files map[string][]byte } // Validate validates the app configuration diff --git a/pkg/server/app/email.go b/pkg/server/app/email.go index 64485340..5a377902 100644 --- a/pkg/server/app/email.go +++ b/pkg/server/app/email.go @@ -116,6 +116,10 @@ func (a *App) SendWelcomeEmail(email string) error { // SendPasswordResetEmail sends password reset email func (a *App) SendPasswordResetEmail(email, tokenValue string) error { + if email == "" { + return ErrEmailRequired + } + body, err := a.EmailTemplates.Execute(mailer.EmailTypeResetPassword, mailer.EmailKindText, mailer.EmailResetPasswordTmplData{ AccountEmail: email, Token: tokenValue, @@ -131,6 +135,10 @@ func (a *App) SendPasswordResetEmail(email, tokenValue string) error { } if err := a.EmailBackend.Queue("Reset your password", from, []string{email}, mailer.EmailKindText, body); err != nil { + if errors.Cause(err) == mailer.ErrSMTPNotConfigured { + return ErrInvalidSMTPConfig + } + return errors.Wrapf(err, "queueing email for %s", email) } diff --git a/pkg/server/app/errors.go b/pkg/server/app/errors.go new file mode 100644 index 00000000..d96b9fb2 --- /dev/null +++ b/pkg/server/app/errors.go @@ -0,0 +1,67 @@ +package app + +type appError string + +func (e appError) Error() string { + return string(e) +} + +func (e appError) Public() string { + return string(e) +} + +var ( + // ErrNotFound an error that indicates that the given resource is not found + ErrNotFound appError = "not found" + // ErrLoginInvalid is an error for invalid login + ErrLoginInvalid appError = "Wrong email and password combination" + + // ErrDuplicateEmail is an error for duplicate email + ErrDuplicateEmail appError = "duplicate email" + // ErrEmailRequired is an error for missing email + ErrEmailRequired appError = "Please enter an email" + // ErrPasswordRequired is an error for missing email + ErrPasswordRequired appError = "Please enter a password" + // ErrPasswordTooShort is an error for short password + ErrPasswordTooShort appError = "password should be longer than 8 characters" + // ErrPasswordConfirmationMismatch is an error for password ans password confirmation not matching + ErrPasswordConfirmationMismatch appError = "password confirmation does not match password" + + // ErrLoginRequired is an error for not authenticated + ErrLoginRequired appError = "login required" + + // ErrBookUUIDRequired is an error for note missing book uuid + ErrBookUUIDRequired appError = "book uuid required" + // ErrBookNameRequired is an error for note missing book name + ErrBookNameRequired appError = "book name required" + // ErrDuplicateBook is an error for duplicate book + ErrDuplicateBook appError = "duplicate book exists" + + // ErrEmptyUpdate is an error for empty update params + ErrEmptyUpdate appError = "update is empty" + + // ErrInvalidUUID is an error for invalid uuid + ErrInvalidUUID appError = "invalid uuid" + + // ErrInvalidSMTPConfig is an error for invalid SMTP configuration + ErrInvalidSMTPConfig appError = "SMTP is not configured" + + // ErrInvalidToken is an error for invalid token + ErrInvalidToken appError = "invalid token" + // ErrMissingToken is an error for missing token + ErrMissingToken appError = "missing token" + // ErrExpiredToken is an error for missing token + ErrExpiredToken appError = "This token has expired." + + // ErrPasswordResetTokenExpired is an error for expired password reset token + ErrPasswordResetTokenExpired appError = "this link has been expired. Please request a new password reset link." + // ErrInvalidPasswordChangeInput is an error for changing password + ErrInvalidPasswordChangeInput appError = "Both current and new passwords are required to change the password." + + ErrInvalidPassword appError = "Invalid currnet password." + // ErrEmailTooLong is an error for email length exceeding the limit + ErrEmailTooLong appError = "Email is too long." + + // ErrEmailAlreadyVerified is an error for trying to verify email that is already verified + ErrEmailAlreadyVerified appError = "Email is already verified." +) diff --git a/pkg/server/app/notes.go b/pkg/server/app/notes.go index 054a4989..83a24b85 100644 --- a/pkg/server/app/notes.go +++ b/pkg/server/app/notes.go @@ -19,6 +19,9 @@ package app import ( + "strings" + "time" + "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/helpers" "github.com/jinzhu/gorm" @@ -174,3 +177,144 @@ func (a *App) GetUserNoteByUUID(userID int, uuid string) (*database.Note, error) return &ret, nil } + +// GetNotesParams is params for finding notes +type GetNotesParams struct { + Year int + Month int + Page int + Books []string + Search string + Encrypted bool + PerPage int +} + +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 getNotesBaseQuery(db *gorm.DB, userID int, q GetNotesParams) *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 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 orderGetNotes(conn *gorm.DB) *gorm.DB { + return conn.Order("notes.updated_at DESC, notes.id DESC") +} + +func paginate(conn *gorm.DB, page, perPage int) *gorm.DB { + // Paginate + if page > 0 { + offset := perPage * (page - 1) + conn = conn.Offset(offset) + } + + conn = conn.Limit(perPage) + + return conn +} + +// GetNotesResult is the result of getting notes +type GetNotesResult struct { + Notes []database.Note + Total int +} + +// GetNotes returns a list of matching notes +func (a *App) GetNotes(userID int, params GetNotesParams) (GetNotesResult, error) { + conn := getNotesBaseQuery(a.DB, userID, params) + + var total int + if err := conn.Model(database.Note{}).Count(&total).Error; err != nil { + return GetNotesResult{}, errors.Wrap(err, "counting total") + } + + notes := []database.Note{} + if total != 0 { + conn = orderGetNotes(conn) + conn = database.PreloadNote(conn) + conn = paginate(conn, params.Page, params.PerPage) + + if err := conn.Find(¬es).Error; err != nil { + return GetNotesResult{}, errors.Wrap(err, "finding notes") + } + } + + res := GetNotesResult{ + Notes: notes, + Total: total, + } + + return res, nil +} diff --git a/pkg/server/app/testutils.go b/pkg/server/app/testutils.go index 6645e430..41091625 100644 --- a/pkg/server/app/testutils.go +++ b/pkg/server/app/testutils.go @@ -19,6 +19,7 @@ package app import ( + "fmt" "os" "github.com/dnote/dnote/pkg/clock" @@ -60,6 +61,12 @@ func NewTest(appParams *App) App { if appParams != nil && appParams.Config.DisableRegistration { a.Config.DisableRegistration = appParams.Config.DisableRegistration } + if appParams != nil && appParams.Config.PageTemplateDir != "" { + a.Config.PageTemplateDir = appParams.Config.PageTemplateDir + } + + fmt.Printf("%+v\n", appParams) + fmt.Printf("%+v\n", a) return a } diff --git a/pkg/server/app/users.go b/pkg/server/app/users.go index 9261dcb9..4c3a335f 100644 --- a/pkg/server/app/users.go +++ b/pkg/server/app/users.go @@ -20,6 +20,7 @@ package app import ( "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/log" "github.com/dnote/dnote/pkg/server/token" "github.com/jinzhu/gorm" "github.com/pkg/errors" @@ -48,9 +49,29 @@ func createEmailPreference(user database.User, tx *gorm.DB) error { } // CreateUser creates a user -func (a *App) CreateUser(email, password string) (database.User, error) { +func (a *App) CreateUser(email, password string, passwordConfirmation string) (database.User, error) { + if email == "" { + return database.User{}, ErrEmailRequired + } + + if len(password) < 8 { + return database.User{}, ErrPasswordTooShort + } + + if password != passwordConfirmation { + return database.User{}, ErrPasswordConfirmationMismatch + } + tx := a.DB.Begin() + var count int + if err := tx.Model(database.Account{}).Where("email = ?", email).Count(&count).Error; err != nil { + return database.User{}, errors.Wrap(err, "counting user") + } + if count > 0 { + return database.User{}, ErrDuplicateEmail + } + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { tx.Rollback() @@ -99,3 +120,42 @@ func (a *App) CreateUser(email, password string) (database.User, error) { return user, nil } + +// Authenticate authenticates a user +func (a *App) Authenticate(email, password string) (*database.User, error) { + var account database.Account + conn := a.DB.Where("email = ?", email).First(&account) + if conn.RecordNotFound() { + return nil, ErrNotFound + } else if conn.Error != nil { + return nil, conn.Error + } + + err := bcrypt.CompareHashAndPassword([]byte(account.Password.String), []byte(password)) + if err != nil { + return nil, ErrLoginInvalid + } + + var user database.User + err = a.DB.Where("id = ?", account.UserID).First(&user).Error + if err != nil { + return nil, errors.Wrap(err, "finding user") + } + + return &user, nil +} + +// SignIn signs in a user +func (a *App) SignIn(user *database.User) (*database.Session, error) { + err := a.TouchLastLoginAt(*user, a.DB) + if err != nil { + log.ErrorWrap(err, "touching login timestamp") + } + + session, err := a.CreateSession(user.ID) + if err != nil { + return nil, errors.Wrap(err, "creating session") + } + + return &session, nil +} diff --git a/pkg/server/app/users_test.go b/pkg/server/app/users_test.go index b78458f6..37ea4d6d 100644 --- a/pkg/server/app/users_test.go +++ b/pkg/server/app/users_test.go @@ -27,9 +27,10 @@ import ( "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/testutils" "github.com/pkg/errors" + "golang.org/x/crypto/bcrypt" ) -func TestCreateUser(t *testing.T) { +func TestCreateUser_ProValue(t *testing.T) { testCases := []struct { onPremise bool expectedPro bool @@ -54,7 +55,7 @@ func TestCreateUser(t *testing.T) { a := NewTest(&App{ Config: c, }) - if _, err := a.CreateUser("alice@example.com", "pass1234"); err != nil { + if _, err := a.CreateUser("alice@example.com", "pass1234", "pass1234"); err != nil { t.Fatal(errors.Wrap(err, "executing")) } @@ -68,3 +69,53 @@ func TestCreateUser(t *testing.T) { }) } } + +func TestCreateUser(t *testing.T) { + t.Run("success", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + c := config.Load() + a := NewTest(&App{ + Config: c, + }) + if _, err := a.CreateUser("alice@example.com", "pass1234", "pass1234"); err != nil { + t.Fatal(errors.Wrap(err, "executing")) + } + + var userCount int + testutils.MustExec(t, testutils.DB.Model(&database.User{}).Count(&userCount), "counting user") + assert.Equal(t, userCount, 1, "book count mismatch") + + var accountCount int + var accountRecord database.Account + testutils.MustExec(t, testutils.DB.Model(&database.Account{}).Count(&accountCount), "counting account") + testutils.MustExec(t, testutils.DB.First(&accountRecord), "finding account") + + assert.Equal(t, accountCount, 1, "account count mismatch") + assert.Equal(t, accountRecord.Email.String, "alice@example.com", "account email mismatch") + + passwordErr := bcrypt.CompareHashAndPassword([]byte(accountRecord.Password.String), []byte("pass1234")) + assert.Equal(t, passwordErr, nil, "Password mismatch") + }) + + t.Run("duplicate email", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + aliceUser := database.User{} + aliceAccount := database.Account{UserID: aliceUser.ID, Email: database.ToNullString("alice@example.com")} + testutils.MustExec(t, testutils.DB.Save(&aliceUser), "preparing a user") + testutils.MustExec(t, testutils.DB.Save(&aliceAccount), "preparing an account") + + a := NewTest(nil) + _, err := a.CreateUser("alice@example.com", "newpassword", "newpassword") + + assert.Equal(t, err, ErrDuplicateEmail, "error mismatch") + + var userCount, accountCount int + testutils.MustExec(t, testutils.DB.Model(&database.User{}).Count(&userCount), "counting user") + testutils.MustExec(t, testutils.DB.Model(&database.Account{}).Count(&accountCount), "counting account") + + assert.Equal(t, userCount, 1, "user count mismatch") + assert.Equal(t, accountCount, 1, "account count mismatch") + }) +} diff --git a/pkg/server/assets/js/build.sh b/pkg/server/assets/js/build.sh new file mode 100755 index 00000000..4c9cc599 --- /dev/null +++ b/pkg/server/assets/js/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# build.sh builds styles +set -ex + +dir=$(dirname "${BASH_SOURCE[0]}") +basePath="$dir/../../.." +serverDir="$dir/../.." +outputDir="$serverDir/static" +inputDir="$dir/src" + +task="cp $inputDir/main.js $outputDir" + + +if [[ "$1" == "true" ]]; then +( + cd "$basePath/watcher" && \ + go run main.go \ + --task="$task" \ + --context="$inputDir" \ + "$inputDir" +) +else + eval "$task" +fi diff --git a/pkg/server/assets/js/src/main.js b/pkg/server/assets/js/src/main.js new file mode 100644 index 00000000..7d32f867 --- /dev/null +++ b/pkg/server/assets/js/src/main.js @@ -0,0 +1,59 @@ +var getNextSibling = function (el, selector) { + var sibling = el.nextElementSibling; + + if (!selector) { + return sibling; + } + + while (sibling) { + if (sibling.matches(selector)) return sibling; + sibling = sibling.nextElementSibling; + } +}; + +var dropdownTriggerEls = document.getElementsByClassName('dropdown-trigger'); + +for (var i = 0; i < dropdownTriggerEls.length; i++) { + var dropdownTriggerEl = dropdownTriggerEls[i]; + + dropdownTriggerEl.addEventListener('click', function (e) { + var el = getNextSibling(e.target, '.dropdown-content'); + + el.classList.toggle('show'); + }); +} + +// Dropdown closer +window.onclick = function (e) { + // Close dropdown on click outside the dropdown content or trigger + function shouldClose(target) { + var dropdownContentEls = document.getElementsByClassName( + 'dropdown-content' + ); + + for (let i = 0; i < dropdownContentEls.length; ++i) { + var el = dropdownContentEls[i]; + if (el.contains(target)) { + return false; + } + } + for (let i = 0; i < dropdownTriggerEls.length; ++i) { + var el = dropdownTriggerEls[i]; + if (el.contains(target)) { + return false; + } + } + + return true; + } + + if (shouldClose(e.target)) { + var dropdowns = document.getElementsByClassName('dropdown-content'); + for (var i = 0; i < dropdowns.length; i++) { + var openDropdown = dropdowns[i]; + if (openDropdown.classList.contains('show')) { + openDropdown.classList.remove('show'); + } + } + } +}; diff --git a/pkg/server/assets/package-lock.json b/pkg/server/assets/package-lock.json new file mode 100644 index 00000000..101aff52 --- /dev/null +++ b/pkg/server/assets/package-lock.json @@ -0,0 +1,157 @@ +{ + "name": "assets", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0.tgz", + "integrity": "sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "sass": { + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.50.1.tgz", + "integrity": "sha512-noTnY41KnlW2A9P8sdwESpDmo+KBNkukI1i8+hOK3footBUcohNHtdOJbckp46XO95nuvcHDDZ+4tmOnpK3hjw==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } +} diff --git a/pkg/server/assets/package.json b/pkg/server/assets/package.json new file mode 100644 index 00000000..0edcd180 --- /dev/null +++ b/pkg/server/assets/package.json @@ -0,0 +1,12 @@ +{ + "name": "assets", + "version": "1.0.0", + "description": "assets", + "main": "index.js", + "scripts": {}, + "author": "Dnote", + "license": "AGPL-3.0-or-later", + "devDependencies": { + "sass": "^1.50.1" + } +} diff --git a/pkg/server/assets/static/500.html b/pkg/server/assets/static/500.html new file mode 100644 index 00000000..ccf85d20 --- /dev/null +++ b/pkg/server/assets/static/500.html @@ -0,0 +1,10 @@ + + + + + + + + 500 + + diff --git a/pkg/server/assets/static/android-icon-144x144.png b/pkg/server/assets/static/android-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..09b28d2f31989bb81fb84e6da6127bb371ae1735 GIT binary patch literal 2448 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 literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/android-icon-36x36.png b/pkg/server/assets/static/android-icon-36x36.png new file mode 100644 index 0000000000000000000000000000000000000000..7c9f770fb5eac33aff1d8b0d226f12a754e7e53a GIT binary patch literal 1406 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k3?#4J%UA`ZSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpg#8i zpAc7|0xc~qZEbB`U0od=9ZgM5BO@cA$Y2IQM^f%QpqGLE1p*)gk7*FQ@Z0$R|9^>j zZ;F6HnOzd(7Yw99fLTCDkWIY%*ol+nOkC6d?ci+v`T6~m)NK==eM(^6a6n~I;WbnJ z%TMRM*y(zKu~Smj?E%le78VJC3{e^{r)#K%th&RdZ8bG0Eak-ar*6*m%&XAGRF$*N*1K1r3j^_PD(3_oN~9lBy+p_(#)H+Z~y<_cQjUFVeInj zzW2BPo>?i@<|yEyP`M}e&8~w+`{e5NcRS4Y@vSdl_{gPsg=dxX;kAN4nzAZVrWC%D z?+$3$exV{{_K(b@!Wk+nHcq`Dk)g$T*_bya@2s2Yg=fzW_bIJhvSWkCyCwP$*P1cZ zTFRDB`F7}(*4`httZv<4Vbs1klP{X_^~-JL7xZT2+a$j+O;w4kyY3;?^zw>Wu{oSJ&SHi7cyVU8;r^x=l{gXxf zSnnF$w|$_iB)z(?85o$VC9V-ADTyViR>?)FK#IZ0z|d0Hz*N`BGQ`l(%Gl7#)KJ^N zz{-L1;F+74p6Z*Jo|&AjV5VoT zXQ6AU0JPEsWTl~zLZG3ULPkkRft9{~d3m{BCP+0916Awg7p326dkZv>K>}oANJeRH zl9iQ9esXDUYF>$zRRBU0q~!7%MGl}6NhBq{nYpPYl?AB`U{~oEc`~GB=A;6>ub-P&l9QjVpO#pbnVg?j ztdEEoedA;kGm~UdRKsKgOJf6bGxOwRv&58SGqaR513jP)J)rx1*|vcSPzFy| KKbLh*2~7a-L%|{d literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/android-icon-48x48.png b/pkg/server/assets/static/android-icon-48x48.png new file mode 100644 index 0000000000000000000000000000000000000000..d93d8cdaa37497a779c2bf1f515a278446540e4c GIT binary patch literal 1454 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq8sKd6mGxU^Rn*LA+qju0R{0KHmVJ z5LciAb#-+O4Gk?VElo{LeSLiq5fL699z8uhRaI49US6OSL>8!k7651|Nyfs}Yin!c z*N9&MrYz9QK=0!>6RRx5N&o-^OG#P^QmoPG07T)7dBgo)*UBS6rB{EOa*X z=eeW_ecjqzMg2W1zA`e(ZMmf{yZMgu-&M8s2l#^$^_X5X*ed{SV@&dPcga)Y4-^M- zI14-?iy0WWg+Z8+Vb&Z8pn?!j7sn8f<8P;ghaWPKaB#L~@3NQ>B`WH=v>~eSec;hX zrE9i@53l|CAKtxG(KdJTuV>8Pw|-xC|75wJfYOheMm0Xhg^QFq%(idYl*zr-`6z$4 z?2Ps+oKx-}{c0%I_-$!ko&e*m_b*d+Q6UB9MXy&|}<e7qGdwghzdO`mD0L!p^6AR0YCO+Z!}8Yu)Cnki(IloVL$>z9|8>t%ve12IswUVc&fowm0? z0~sVhCWd5`<|bKLx#TC8=BDPASXl)Cl@>D?F8{wH2AjWtqwOdBysOh|xDrHZe0vHa9o4NKQ3OHn21{FgG(#PBu$SNj5V}Ni)y`>d*ta U&zEf*sK90LboFyt=akR{0LpyI(*OVf literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/android-icon-72x72.png b/pkg/server/assets/static/android-icon-72x72.png new file mode 100644 index 0000000000000000000000000000000000000000..aa2e9876e6f3eae66384217780561587e9262b8d GIT binary patch literal 1583 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAifOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5)aMo8 z6XFU~prxgytE;Q2si~u*qoJXpt*x!1qGD`p3={_nLI99W5CByYRD-DiXd_S%qE}sA z9a8~T5lp+_B5(#)jg$*wHUIzr|DCrY>VTn{QxfDC45T4|nO{hdO|MXctjR6FmMZlFeAgPITAnxH$7b(Ln02py%8RK$U%hlLCj(evki%iE}CKiIvQGK zi<1s^DQJCbJN3W5?6QZMwqQbr`y!xqHS5vd4h>1HeM_!UC;)-=lbFSvb7|YJ7 zp?4-K-kx=>+v4jCWye3StgLc1ehbw9&CPo$BPq1)VrF`HsnqNRRTdt{x>Fb0Ua))o zo4sAMc)}LOmb6vQq3i8i%eZ9!GCkR#z`Y5Eewk0W{$KdW?LL1k^_s4@E$7ZHE&jF0 zxy2~b*hTci68(!M@3^v>4;0rnw|(x5Hi>0s$=x(r=tyFjM`4ksW0i7RTflnjO8K8> zXNexF-ss#GcVqSK zfs@meMRsq1Qej9^p+TMuX_+~xK=144=9T2+r|YLBmSraA=N0QCB1Ydh*~H8w+1%XF wB01GC*}&4+z}(C{IoT{RCE3g@CCxw&s6!9vK3}$Npc0?K)78&qol`;+07D1{E&u=k literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/android-icon-96x96.png b/pkg/server/assets/static/android-icon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..6d711b98bfcf06da7ea4884de74888b71b073747 GIT binary patch literal 1777 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#B(j9#r85lP9bN@+X1@ak-gWR1M z)}51i3FIgwdj$D1FjT2AFf_C ztVI~@wmpCI+SuvR60UPn7i4}t{rz`E^|v*$5#RrPRfzg9BPjMyIk(b&uA)5Y9pZDM zfAme7I62vxk(rTC?T&=b!?P?c+FN*7c{w>(ZQi-NjFo+H=&Mg3zs%xTAnD@fuFIt7 zTKDdh`3W|w)5=@b6h*}x-ak;eIlD{#aoi%HBN&ss-CbHmuV+06aySb-B8wRqxP?KO zkzv*x2?hoxYfl%)kc@k8Z@vr{3Y0na@wfCclZ#H;s)34Imn?G0P@U{moE^C7s*$Po z*$l-Z&$Vlw@%JC)=J_5scjswK+4Hsc&hGh9$2UhZtmWHthF5#OF8XZ7u;EvG{|~9z zeO&7-V+Juz*|JnQ|9w@n=o>mg%ta{yo7E#wouRPM#af)ljtm z_m!7&>(YMs@Jp|FF~7Aq#^K4I*0}g7j5mJOI4sdmbNHhqyWuOZp2DT_lP5~`+A|u~ zzJFN7(rRaPMmvxz;k~llf*bY>;p+qEEm~#A-|)NhwWH9Acf|}(>vp>`xZJmOcs-qA z(|qfz_0Bu`8NK8sAIN&#E7|pA?QKJ^dUgg0m0vFwSe6_=+h#k(j?HaXr``DzK>GM6 z_P~StI3HiF<+-zWy8RA&tt;-&nmHfIrm)Y@VOZHdRo{or$5yIv&v7~Cnf~*l^cwC= z{rYC-W#9c3obMRTzdN4hVSn(PdM=CQuXrae-qePUiP22)*Ec@XXIS`{o{Z3Q@=R2)oZ_1{_vmE`$cd#QWk=LsydpQtb4#b5q3Ubn!}Z(iwcO)BOBa+j0MnLgiEBhjN@7W> zRdP`(kYX@0FtpS)Fx54(3^6pcGB&g_HPkjRure^P{%E%WMMG|WN@iLmZVg^*+IWB( z+(0%I=ckpFCl;kLc;+Uir}`$QXC`MWnCY48S?F3S0If6uS!rmb5NK$okWo@nV5P5L zUS6)32~rKjK-GHrMd^3i-U1C|kN}w&l2MwQWM$=&pIn-onpa|F6#!IP%wV|u|89LW zHDEiftTIwF(=$pK3@wfL8H!qgYQ&MufvO45Oex6#DY?8xkprki5=n_~W^QUpWkD(f z*j4%kdFl3>!u-(8@eKj0VlXr{wKO!cG&MAI+R2gxRKkyBPH<*bDuaQO)09PaZ-7!^ zNK&Ceo(yT3IjKPJ>*wZ`mwpY-#FRC%p}>|+|VL9)iBw>(%8V< m%se^SEHNe7%q%6%Ko6)x59mH$wr!v~i^0>?&t;ucLK6V!j!wJ) literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/apple-icon-114x114.png b/pkg/server/assets/static/apple-icon-114x114.png new file mode 100644 index 0000000000000000000000000000000000000000..e6b7fece234c57eb935fe9a8fda21038a32a116e GIT binary patch literal 2154 zcmeAS@N?(olHy`uVBq!ia0vp^MIg+<3?z3jm-+)KmUKs7M+SzC{oH>NS%G}U;vjb? zhIQv;UIIA^$sR$z3=CCj3=9n|3=F@3LJcn%7)lKo7+xhXFj&oCU=S~uvn$XBsPB4! zPlzi}ftHq*rlzL0wzh_bhKh=cs;X*eXsDi^o`HdZj*gC+nwq-0y1u@?w6wITsj0ZQ zxS^q;uCA`Jv9X+-98eah3#c7v2#o-;;Xu3a*n+GQ&W1DKdf_4vqtLi$=A+3%0tBuG z7-$eSoB@|Y(}<}Is0A2|Km&n64cCR00W=D$X>i48vj6}8ua;Q60~oclOM?7@fixyy zWMg5}E}A^0J3@fT^USXg9XwuFpV@tj|DJU1;k`RCD?+)K+zn%9KRKcA*iVkR%FIgh ze@o3?DZ#yg#rcu6>EVB~B#jTvIk=29rPiI*L?y9?k*V%)6VJk2!R>ijOT(GAbuo9c zL~>4loWR7)#MJQd4R5_L-={4ani~0g3;)<;-u&`H-O!O=`ol|OpxYReyxm>q6z(~s z4&-nactjR6FmMZlFeAgPIT8#E%n_b0jv*QM-ro8h%@ZiY_M!M(&Xv4(L5fqoB9^*( zbuV+-t=AnjWs#Fho+2mb#3iNs-`i*F?LJ#xrk9<5Zf5&)%lG-#&u{)Mxi{1H^5%4b zU(1^~pFUWlq!;CGdQ?KVdgGFkziquH!P{2eDVw)rQBiT|q`Xzx+irAijn-@q+LpUL zdur_v_*xltD;__wjx_Jf7O*{D*-uDyam8r!>eojd-p!A)xYRgs&|H-wh<9WshtJ%Q!> z@wBy}nt#Qs7VljCJ!w_wqI}Uws?2F?y*PQ==BlV^tksjhw4Lk3fu&)a!18EZI)K6L{F;=-IXvE zG(LH5cAMp+f7|W^>#Q$+aqaDs_zC4aZ@=7^%|2!M{hjs`=JX|tmnQsKsD0^VQ}|&| zzoJQ#=R8qSw>*i#{3CzDko`%Em(`!#zZX*?->nR-i>{C1=j*kvTs7(7%AAykn_u2B zp0j+<*PUfgTz{UHoLXZ#p;x78@`>9AIkMM(e0?)3ROV&%zWa`=hAfJH%GJSZ`6)4Jj+ff3Etad!n?_8$l6+OmIw-4@ z_v4f)(O)AwR=jwuu2#GyE7a>jF>|Tmy8ZT2LjOK}jg~#jvcLb|hu=9aE$sDv*DjyA zvi^R>+P_>+-w5jde!gAc)3qhv9mMATVXWD&^^t{9x(S%^R7+eVN>UO_QmvAUQh^kM zk%6J5u7Rnpk!6UXp_Q?rm8qe&fq|8Qf%Qkb4JaCN^HVa@DsgM@TGPe@)ZhlPp*TOS zq&%@GmBBMNF+J5cF+DRmTft1vT+c$+QUPeC3CK!ABZWXiGlh(jk^(Dz{qpj1y-bj5 zAO@<|%P&g5)Akl znwg$a!eD4=#LrOF3REMGWDZnKcxFmT21v=}HHsWSC6Y)=d^2-XODYRe8Nja6FUU)` z-xTJDW{z(NP!)rrsi~!*k)^4jq0>&5B%l(0By)l@t5O*ZoSddCvU>xR3PX|#4f142 z%gjjydS5>`uOufwT|X_cEHgPjuUH=uG5W^ICT1qd=H`YL$*G3P2A0MK=4R%}$!3Wu g$!2CLX$E>g9eP0b`Lb;TH5M2=UHx3vIVCg!09JGPPXGV_ literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/apple-icon-120x120.png b/pkg/server/assets/static/apple-icon-120x120.png new file mode 100644 index 0000000000000000000000000000000000000000..871eb9d0ecc63c859f7f3942c21df05027fa7a90 GIT binary patch literal 2181 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 R+U$QN| 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 literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/apple-icon-60x60.png b/pkg/server/assets/static/apple-icon-60x60.png new file mode 100644 index 0000000000000000000000000000000000000000..9dbf917e1d90d5373987b75dcd65e1a2e0acc8f0 GIT binary patch literal 1566 zcmeAS@N?(olHy`uVBq!ia0vp^HXzKw3=&b&bO2H;>5jgR3=A9lx&I`x0{M)^LGDfr z>(0r%1acITJ%W507^>757#dm_7=8hT8eT9klo~KFyh>nTu$sZZAYL$MS0K<-#`plA z5LciAEiEl=ZEXz=4NXl=H8nL=RaJF$bz@^=pg4U1&|X?t1T@yj$OxCo`uh5~r1bRk za7mFQ{{R1fMNaW1U=SCV1o;I6X$X*a_jVCAJMsO;x1~a+M_(9OTzsv6?BDO>yEg1s zZoW3>l0lmZ3oc?Ra^gBudu(cN&S3x&m^n23R9lHF%l z-~adDm{b$3?OGbz0 zf8vVBO`Y1HdetssO_Z}oZmimjb5~!Oe{JM3cyL#)E zw)L7DFIH{l_K^umE%_c%ut|H-%7mn~;ssy7&IU>ZPZbrr=Hw7oap2F{mFsp*&^0XB z5Z-p$IAdFG*;0>5O&%p0xoz$#Pg>k{w{s;wb_lNn ztWZlj$4t{TOz&fEozYJy?5sI{I=($MwN%&lnz`A=t$&sOEqpuoZu|pVcIz7l`N}@* zKlH}_?}<7S-$(527FK<)<^6@0pHw~9wv*|pj+^GpD&o$^~sQj^}jh~264<6+C zO+Z!}8Yu)Cnki(IloVL$>z9|8>t%ve12IswUVc&fowm0?0~sVhCWd5`<|bKLx#TC8 z=BDPASXl)Cl@>D?F8{wH2AjWtqwOdBysOh|xDr zHZe0vHa9o4NKQ3OHn21{FgG(#PBu$SNj5V}Ni)y`>d*ta&zEf*sDNkiboFyt=akR{ E00!j45bDP46hOx7_4S6Fo+k-*%fF5)aMo8 z6XFU~prxgytE;Q2si~u*qoJXpt*x!1qGD`p3={_nLI99W5CByYRD-DiXd_S%qE}sA z9a8~T5lp+_B5(#)jg$*wHUIzr|DCrY>VTn{QxfDC45T4|nO{hdO|MXctjR6FmMZlFeAgPITAnxH$7b(Ln02py%8RK$U%hlLCj(evki%iE}CKiIvQGK zi<1s^DQJCbJN3W5?6QZMwqQbr`y!xqHS5vd4h>1HeM_!UC;)-=lbFSvb7|YJ7 zp?4-K-kx=>+v4jCWye3StgLc1ehbw9&CPo$BPq1)VrF`HsnqNRRTdt{x>Fb0Ua))o zo4sAMc)}LOmb6vQq3i8i%eZ9!GCkR#z`Y5Eewk0W{$KdW?LL1k^_s4@E$7ZHE&jF0 zxy2~b*hTci68(!M@3^v>4;0rnw|(x5Hi>0s$=x(r=tyFjM`4ksW0i7RTflnjO8K8> zXNexF-ss#GcVqSK zfs@meMRsq1Qej9^p+TMuX_+~xK=144=9T2+r|YLBmSraA=N0QCB1Ydh*~H8w+1%XF wB01GC*}&4+z}(C{IoT{RCE3g@CCxw&s6!9vK3}$Npc0?K)78&qol`;+07D1{E&u=k literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/apple-icon-76x76.png b/pkg/server/assets/static/apple-icon-76x76.png new file mode 100644 index 0000000000000000000000000000000000000000..5e4af4bc80d6bff26e8a67e907680cc462508130 GIT binary patch literal 1676 zcmeAS@N?(olHy`uVBq!ia0vp^J|N7&3?x5zE|dgPEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyaaL-l0AZa85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP+xX{ zPlzi}fu^RWmX?-=hK9DbHjoVgKoLp+&=8We18pZsJzO!wBDi%xFTkZR8JMz|BAB}H zh`{Z|qXr`N|NnoUwAKP(aF>?^`2_=MU<|NxGV*7w-ncf8x9`Wd_@1B7{{5cP`D?mB z>H8hq-U*jIPvYT9|MVh`S?Fl2#2LTHh(B!vsE>F{9twjdtiUNhqKw;b0CY8yxmEv`%ibgj6E3q z@P9*Q?8M7~*HeGh_8dL0V<|3_K0SPm!6i|Dr&AlJo||L6e^=U}W3iqEeX06SR8GX3 zBt3bf{L#kZ_@(JZLQSW`c_gm3$|UdLc6^wXA+&hcgk&A-S6VT1 zcF-d{=Su6w*-O{l_1ex8%fkHM>T2tSp6B1%{+^n8QjeGU#GQq^B+M=@O$_>~XZO3> zb4KD8vzK~Jq1&#kzEyiokA>f>OR{=Job9Uj_2LP;XKcE8sjteef4Ge<%z}SUt@`Rizxfq*9$Rqt;C!Qte^+q5Gidmxp7Zq=^Plx?-aRFm)3klw zU(zj)*uD6|H10E3o^mP2-rab#H?pKK*`RKphWGJTIWtnH_g%H)KdmEO_c9`arMvd; zv29y&5*6Q0H@IF^yZ+&P$Cbb4UN$OyxcJY+2b-c|lf|#q{p(_z@%{Jt`;9lHju!TQ z)J(r?zgz6M#nQAx?|?}~wZt`|BqgyV)hf9t6-Y4{85mmX8kp)DS%w%IS{WN!nHp*v z7+4t?SbwzJfTAHcKP5A*61N7gHEldV4Q?PCiu2P-$`gxH89Z|n(^GvD(=(H^70mR^ z^(=HP6@XTnfUGn$QV29OQ^+VODX`MlFE20G%LJ(gVxVfh{G#+bZEt}FGDv_-49O_X zO|r6b$xklLP0cH@vI+nyEoLxW{(rYVni{a3R#q9QnduoN42G6Q{0v2{KsDk>=0Mei zXQq^7fRtQbqsReNB8jBLH#0Z2q_QBD0qiRMg1mJ5O<{g$=JvSVrG(TZfgTe~DWM4f6{|U@ literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/apple-icon-precomposed.png b/pkg/server/assets/static/apple-icon-precomposed.png new file mode 100644 index 0000000000000000000000000000000000000000..f3410b4e444689670c06fcceec564e901abb66e7 GIT binary patch literal 2303 zcmb`H3s93+7RT?G@CXD!!AD|g2rf?rk^l)3p@|p-q`c!I)>cCD5mJbW5Z)M4*8-iX z(yFZ>AQZdGR7F6`ilR`drWS?T#TW9b;s|0y3W6kjVK1_i$-1+%)0y6xd+#~t{?7m0 z``tU=XVH-%ODtDg0sxkTh4NzI+`iBWcsQcCN+z64#2X_v0?=4WoD-NrAIXRf2?F(9 z_M_0D3PWQf0LXIzK(!A5Eexqf0LY{R@GKDkb{PPxv%Jh_O3AKmYr|FL3Zu%N4Paad0+jh;2Yjbihi)8vSv6 z_Vvq7iKX4q8I@V(Tqy3U%}@G!H}7x*aaDKmJzr;ia(-g(yNE@2m(-MX-$bM}eb@Bd z*%i08PCNhnmzK$?QN(Ejn_*^gD30>b>ly6<^>Z;+oR)C-;FrF~K1@Y6NnO1@&bq#A z-}l6Vl3yQfi9#L|2AVxa$atR+muu=X_JrY+cE7`&AxYsOyrX4ssG9cv3{Z!Y}w#jP-q&-rRBQx=JL zsxG`!l+<4l2d1%8sjO!MJ1B|c;<)b3BiWf)TZEaXfFsHt+|>wk zG4}9jwDYo|zS3B3+Wd{eyuTI08vju8aV|Qtc@M=A^yy*+u_U(`CFAe_@klLiXrKPOUA&UfIh^8LwS_hOqCB=*&DLZU{E7OvtxQ}%#^+T9NZ zmkr%1wN>#syFjUu4~Dh7&2pxX1L}iPJ8o_D6kep;ZIUx{C`+Z_Sc7uqb>vm>$$$Dv z#oy{0kzws;5j9wvy2Up_JxgrD?jx~So&8zt47i;ebHd^8pcFde7kTTuoqkb!7E`E@&AwqA+sdulgr8G9byXP%d9h|E)NR`|^j5 z5Nx#U6ZJ&C^D(RSdmfFRd%AF6PVXbcP!z)C?=~0Y-p-d5RFl*49Fh&kc&2A-xK?J8-TjKUSX zu9N<}eNH1YZ6H5TUce1^SG%g3I3aITf1On;=at&i`H$T-vJ%?Xi}B4?XR7a)?IdQ7 zSFZ1wbjUXuym&%?dG(Cn-i+`u#dsj&@{_@bds?sYL>TE~eM0>nCDx@EnvLqz$pdq@ zLqm1E&FDb?fgcj}$7hKLjx=U8;0~XHH|DGe-lh-<6ewGmj=~9e(;5Drbf%}bKcB&1 z`!Lu{#yUEkO{Z^|c(36<5~MPbBsuqgCu})gWCas8FI32oB`dN8=_m-33dCrHKr9ia zQdwSpUh6&msj#H)LP>@b}SEN~ZWTXW}eoCe^ ziOuH8gqc!wmx9fXhG+(0oSeTzdy9Eflg&;+CE^qXU^2Yhe~FHP%qk3qCks@UQ zu`j^)L4<6CL`bBlB3F)rH@l+AcZoSoe~o-gCxQB8VZk3Q%L2sWI>ihDBG1m^AY;8e1(2~jP*j4A*B2J h(EV6`LLo~a60%sLWcqi~g94}p!h$1t^+Aby{{>+JBEkRw literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/apple-icon.png b/pkg/server/assets/static/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f3410b4e444689670c06fcceec564e901abb66e7 GIT binary patch literal 2303 zcmb`H3s93+7RT?G@CXD!!AD|g2rf?rk^l)3p@|p-q`c!I)>cCD5mJbW5Z)M4*8-iX z(yFZ>AQZdGR7F6`ilR`drWS?T#TW9b;s|0y3W6kjVK1_i$-1+%)0y6xd+#~t{?7m0 z``tU=XVH-%ODtDg0sxkTh4NzI+`iBWcsQcCN+z64#2X_v0?=4WoD-NrAIXRf2?F(9 z_M_0D3PWQf0LXIzK(!A5Eexqf0LY{R@GKDkb{PPxv%Jh_O3AKmYr|FL3Zu%N4Paad0+jh;2Yjbihi)8vSv6 z_Vvq7iKX4q8I@V(Tqy3U%}@G!H}7x*aaDKmJzr;ia(-g(yNE@2m(-MX-$bM}eb@Bd z*%i08PCNhnmzK$?QN(Ejn_*^gD30>b>ly6<^>Z;+oR)C-;FrF~K1@Y6NnO1@&bq#A z-}l6Vl3yQfi9#L|2AVxa$atR+muu=X_JrY+cE7`&AxYsOyrX4ssG9cv3{Z!Y}w#jP-q&-rRBQx=JL zsxG`!l+<4l2d1%8sjO!MJ1B|c;<)b3BiWf)TZEaXfFsHt+|>wk zG4}9jwDYo|zS3B3+Wd{eyuTI08vju8aV|Qtc@M=A^yy*+u_U(`CFAe_@klLiXrKPOUA&UfIh^8LwS_hOqCB=*&DLZU{E7OvtxQ}%#^+T9NZ zmkr%1wN>#syFjUu4~Dh7&2pxX1L}iPJ8o_D6kep;ZIUx{C`+Z_Sc7uqb>vm>$$$Dv z#oy{0kzws;5j9wvy2Up_JxgrD?jx~So&8zt47i;ebHd^8pcFde7kTTuoqkb!7E`E@&AwqA+sdulgr8G9byXP%d9h|E)NR`|^j5 z5Nx#U6ZJ&C^D(RSdmfFRd%AF6PVXbcP!z)C?=~0Y-p-d5RFl*49Fh&kc&2A-xK?J8-TjKUSX zu9N<}eNH1YZ6H5TUce1^SG%g3I3aITf1On;=at&i`H$T-vJ%?Xi}B4?XR7a)?IdQ7 zSFZ1wbjUXuym&%?dG(Cn-i+`u#dsj&@{_@bds?sYL>TE~eM0>nCDx@EnvLqz$pdq@ zLqm1E&FDb?fgcj}$7hKLjx=U8;0~XHH|DGe-lh-<6ewGmj=~9e(;5Drbf%}bKcB&1 z`!Lu{#yUEkO{Z^|c(36<5~MPbBsuqgCu})gWCas8FI32oB`dN8=_m-33dCrHKr9ia zQdwSpUh6&msj#H)LP>@b}SEN~ZWTXW}eoCe^ ziOuH8gqc!wmx9fXhG+(0oSeTzdy9Eflg&;+CE^qXU^2Yhe~FHP%qk3qCks@UQ zu`j^)L4<6CL`bBlB3F)rH@l+AcZoSoe~o-gCxQB8VZk3Q%L2sWI>ihDBG1m^AY;8e1(2~jP*j4A*B2J h(EV6`LLo~a60%sLWcqi~g94}p!h$1t^+Aby{{>+JBEkRw literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/browserconfig.xml b/pkg/server/assets/static/browserconfig.xml new file mode 100644 index 00000000..c5541482 --- /dev/null +++ b/pkg/server/assets/static/browserconfig.xml @@ -0,0 +1,2 @@ + +#ffffff \ No newline at end of file diff --git a/pkg/server/assets/static/favicon-16x16.png b/pkg/server/assets/static/favicon-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..798fbd5c4c35fe8841edeb104b8cf497bf0eb73f GIT binary patch literal 1061 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl<6e(pbstUx|vage(c z!@6@aFM%9|WRD45bDP46hOx7_4S6Fo+k-*%fF5lsFLJ z6XFV_wT3MC|Ns9oc8+;KKiHK7`2hpRaoY45UJl2;{QKOyKjr+!tgrq1g1E{iPio%Y zH>HCkp`zTRvr@2_Z|S)+F&DZSc?H-rk61_UsjjWr>pt=1xmTq?lNgh{-CgYMk_|u{ z&H|6fVg?3oVGw3ym^DWNDA?}l;us=vdF$z$Vh0UGS|1kA5(@Ek6!u!Vvu#$=wD0w6 z)Fy?0xcjz#EpNjM!TXv&Q}<>rU9~1^!K;S28yUMUWR*)?$XhR8xbP#R>?#SjWFNK5 z9^UE2OPM1dT#5~QGCy(e$!$^FyuTkj*z%|1m!)0@a8knFCc5 zo|#gT0a9{#jUoq7i6oK|-^|?9lFEWq2C%F23-Z$KH--73nd2J*RK;LuYHDd{WNB(> z=(Lk138;i0$(-QKs#FF8C#NZk?A`#S!jPmwgFG41GILUa-q+8~E6K@E*H23<%S_JC zE7nIujJ|QQiJ3{Vxw)Z5a;jmnfu*s5xtV!#vRPtEvYA;*nt>irhaS*!lvI6;x#X;^) z4C~IxyacIC_6YK2V5m}KU}$JzVE6?TYIwoGP-?)y@G60U!DgvkM%9@&*+S=Mc?r;J?AL)Prg66v2o+_IJ?Jv9zpL*Kdj;VPZh-l_i;SWm4I+lFy#F-Nr2m5($ zNa(Jeq`)PzHaAC7QsV6!*&G3fM8MJ8bw^ zIVu-0iLU&>Q6eD6IXOLGOIMfJAr`}^fJ(N1H!N}-lZ4_?V*yvKe!iM4gq3f)x z6O^X_Lrb;9HKHUXu_VKa*w7#dm`8(NtfY8x0>85mf9wA+B9 zAvZrIGp!Q02Cp@3JU|U@ARCJF(@M${i&7apa}(23eG}6&ld~1f^vv}vbS)KtR+@mU zG&E8OG&EDlC@Cqh($_C9FW1WisRm-8YQ6lT^gC^Dfd(>2fJ_X@D9uf>vU15!F3nBN zE3vW)04gnJFkJqBw?3L0u$@*`8L64+86^ycmPY&xMXf+J;z;H|)r4oJlw^RETwbHd z0aPN1q{KHfH?^d)Ae8~^D*b}Ibo)(VerV?Sh5%JD7@C?|8X8%e8X7w7WJv-l;YTtj zII}91!NAFB$|AcrK&db!sn8%#hP2F_RG|0ubMs1a^3(Ox63a4^^Ye=J5fP(roNQud zl5B2nXpx+1m~3EaY+!C?o}6r!n38N}mXc|ja BiyQy| literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/favicon-96x96.png b/pkg/server/assets/static/favicon-96x96.png new file mode 100644 index 0000000000000000000000000000000000000000..6d711b98bfcf06da7ea4884de74888b71b073747 GIT binary patch literal 1777 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD3?#3*wSy#B(j9#r85lP9bN@+X1@ak-gWR1M z)}51i3FIgwdj$D1FjT2AFf_C ztVI~@wmpCI+SuvR60UPn7i4}t{rz`E^|v*$5#RrPRfzg9BPjMyIk(b&uA)5Y9pZDM zfAme7I62vxk(rTC?T&=b!?P?c+FN*7c{w>(ZQi-NjFo+H=&Mg3zs%xTAnD@fuFIt7 zTKDdh`3W|w)5=@b6h*}x-ak;eIlD{#aoi%HBN&ss-CbHmuV+06aySb-B8wRqxP?KO zkzv*x2?hoxYfl%)kc@k8Z@vr{3Y0na@wfCclZ#H;s)34Imn?G0P@U{moE^C7s*$Po z*$l-Z&$Vlw@%JC)=J_5scjswK+4Hsc&hGh9$2UhZtmWHthF5#OF8XZ7u;EvG{|~9z zeO&7-V+Juz*|JnQ|9w@n=o>mg%ta{yo7E#wouRPM#af)ljtm z_m!7&>(YMs@Jp|FF~7Aq#^K4I*0}g7j5mJOI4sdmbNHhqyWuOZp2DT_lP5~`+A|u~ zzJFN7(rRaPMmvxz;k~llf*bY>;p+qEEm~#A-|)NhwWH9Acf|}(>vp>`xZJmOcs-qA z(|qfz_0Bu`8NK8sAIN&#E7|pA?QKJ^dUgg0m0vFwSe6_=+h#k(j?HaXr``DzK>GM6 z_P~StI3HiF<+-zWy8RA&tt;-&nmHfIrm)Y@VOZHdRo{or$5yIv&v7~Cnf~*l^cwC= z{rYC-W#9c3obMRTzdN4hVSn(PdM=CQuXrae-qePUiP22)*Ec@XXIS`{o{Z3Q@=R2)oZ_1{_vmE`$cd#QWk=LsydpQtb4#b5q3Ubn!}Z(iwcO)BOBa+j0MnLgiEBhjN@7W> zRdP`(kYX@0FtpS)Fx54(3^6pcGB&g_HPkjRure^P{%E%WMMG|WN@iLmZVg^*+IWB( z+(0%I=ckpFCl;kLc;+Uir}`$QXC`MWnCY48S?F3S0If6uS!rmb5NK$okWo@nV5P5L zUS6)32~rKjK-GHrMd^3i-U1C|kN}w&l2MwQWM$=&pIn-onpa|F6#!IP%wV|u|89LW zHDEiftTIwF(=$pK3@wfL8H!qgYQ&MufvO45Oex6#DY?8xkprki5=n_~W^QUpWkD(f z*j4%kdFl3>!u-(8@eKj0VlXr{wKO!cG&MAI+R2gxRKkyBPH<*bDuaQO)09PaZ-7!^ zNK&Ceo(yT3IjKPJ>*wZ`mwpY-#FRC%p}>|+|VL9)iBw>(%8V< m%se^SEHNe7%q%6%Ko6)x59mH$wr!v~i^0>?&t;ucLK6V!j!wJ) literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/favicon.ico b/pkg/server/assets/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c71572aab7836379227c521fcaea2282b2e607ff GIT binary patch literal 1150 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 NFJ86i+Cov{zX66w`||(* literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/manifest.json b/pkg/server/assets/static/manifest.json new file mode 100644 index 00000000..874b25a6 --- /dev/null +++ b/pkg/server/assets/static/manifest.json @@ -0,0 +1,52 @@ +{ + "name": "Dnote", + "short_name": "Dnote", + "icons": [ + { + "src": "ASSET_BASE_PLACEHOLDER\/android-icon-36x36.png", + "sizes": "36x36", + "type": "image\/png", + "density": "0.75" + }, + { + "src": "ASSET_BASE_PLACEHOLDER\/android-icon-48x48.png", + "sizes": "48x48", + "type": "image\/png", + "density": "1.0" + }, + { + "src": "ASSET_BASE_PLACEHOLDER\/android-icon-72x72.png", + "sizes": "72x72", + "type": "image\/png", + "density": "1.5" + }, + { + "src": "ASSET_BASE_PLACEHOLDER\/android-icon-96x96.png", + "sizes": "96x96", + "type": "image\/png", + "density": "2.0" + }, + { + "src": "ASSET_BASE_PLACEHOLDER\/android-icon-144x144.png", + "sizes": "144x144", + "type": "image\/png", + "density": "3.0" + }, + { + "src": "ASSET_BASE_PLACEHOLDER\/android-icon-192x192.png", + "sizes": "192x192", + "type": "image\/png", + "density": "4.0" + }, + { + "src": "ASSET_BASE_PLACEHOLDER\/logo-512x512.png", + "sizes": "512x512", + "type": "image\/png", + "density": "4.0" + } + ], + "start_url": "/", + "display": "standalone", + "background_color": "#072a40", + "theme_color": "#ffffff" +} diff --git a/pkg/server/assets/static/ms-icon-144x144.png b/pkg/server/assets/static/ms-icon-144x144.png new file mode 100644 index 0000000000000000000000000000000000000000..09b28d2f31989bb81fb84e6da6127bb371ae1735 GIT binary patch literal 2448 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 literal 0 HcmV?d00001 diff --git a/pkg/server/assets/static/offline.html b/pkg/server/assets/static/offline.html new file mode 100644 index 00000000..0aadab72 --- /dev/null +++ b/pkg/server/assets/static/offline.html @@ -0,0 +1,41 @@ + + + + + + + Page Not Found | Dnote + + + +
+

You are offline

+

+ Please check you connection and try again. +

+
+ + diff --git a/pkg/server/assets/styles/build.sh b/pkg/server/assets/styles/build.sh new file mode 100755 index 00000000..cde19626 --- /dev/null +++ b/pkg/server/assets/styles/build.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# build.sh builds styles +set -ex + +dir=$(dirname "${BASH_SOURCE[0]}") +serverDir="$dir/../.." +outputDir="$serverDir/static" +inputDir="$dir/src" + +rm -rf "${outputDir:?}/*" + +"$dir/../node_modules/.bin/sass" --version + +task="$dir/../node_modules/.bin/sass \ + --style compressed \ + --source-map \ + $inputDir:$outputDir" + +# compile first then watch +eval "$task" + +if [[ "$1" == "true" ]]; then + eval "$task --watch --poll" +fi diff --git a/pkg/server/assets/styles/src/_books.scss b/pkg/server/assets/styles/src/_books.scss new file mode 100644 index 00000000..9dcc5a1a --- /dev/null +++ b/pkg/server/assets/styles/src/_books.scss @@ -0,0 +1,11 @@ +.books-page { + .books-content { + padding: rem(16px) rem(24px); + margin-top: rem(16px); + + h1 { + border-bottom: 1px solid $lighter-gray; + margin-bottom: rem(12px); + } + } +} diff --git a/pkg/server/assets/styles/src/_bootstrap.scss b/pkg/server/assets/styles/src/_bootstrap.scss new file mode 100644 index 00000000..ea4a69de --- /dev/null +++ b/pkg/server/assets/styles/src/_bootstrap.scss @@ -0,0 +1,176 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +// From Bootstrap <4.3.1 +// MIT Licensed - https://github.com/twbs/bootstrap/blob/master/LICENSE +.form-control { + display: block; + width: 100%; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +.alert { + position: relative; + padding: 1.75rem 1.25rem; + border: 1px solid transparent; +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; +} + +.alert-dismissible { + // padding-right: 4rem; +} + +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; +} + +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} + +.alert-primary hr { + border-top-color: #9fcdff; +} + +.alert-primary .alert-link { + color: #002752; +} + +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; +} + +.alert-secondary hr { + border-top-color: #c8cbcf; +} + +.alert-secondary .alert-link { + color: #202326; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-success hr { + border-top-color: #b1dfbb; +} + +.alert-success .alert-link { + color: #0b2e13; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.alert-info hr { + border-top-color: #abdde5; +} + +.alert-info .alert-link { + color: #062c33; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} + +.alert-warning hr { + border-top-color: #ffe8a1; +} + +.alert-warning .alert-link { + color: #533f03; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.alert-danger hr { + border-top-color: #f1b0b7; +} + +.alert-danger .alert-link { + color: #491217; +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} + +.alert-light hr { + border-top-color: #ececf6; +} + +.alert-light .alert-link { + color: #686868; +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} + +.alert-dark hr { + border-top-color: #b9bbbe; +} + +.alert-dark .alert-link { + color: #040505; +} + +// custom +.alert-slim { + padding: 0.75rem 1.25rem; +} diff --git a/pkg/server/assets/styles/src/_buttons.scss b/pkg/server/assets/styles/src/_buttons.scss new file mode 100644 index 00000000..6178c0ce --- /dev/null +++ b/pkg/server/assets/styles/src/_buttons.scss @@ -0,0 +1,182 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +@import './theme'; +@import './rem'; +@import './font'; + +@mixin button($text-color, $background-color) { + color: $text-color; + background-color: $background-color; + + &:not(:disabled):hover { + color: $text-color; + background-color: darken($background-color, 5%); + box-shadow: 0px 0px 4px 2px #cacaca; + } +} + +@mixin button-outline($color, $border-color) { + background: transparent; + color: $color; + + &:not(.button-no-ui) { + border-color: $border-color; + border-width: 2px; + } + + &:not(:disabled):hover { + color: $color; + box-shadow: 0px 0px 4px 2px #cacaca; + } +} + +.button { + position: relative; + display: inline-block; + text-align: center; + white-space: nowrap; + vertical-align: middle; + user-select: none; + border-image: initial; + transition-property: color, box-shadow; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + + text-decoration: none; + cursor: pointer; + + &:not(.button-no-ui) { + border-width: 1px; + border-style: solid; + border-color: transparent; + } + + &:not(:disabled):hover { + text-decoration: none; + } + + &:disabled { + cursor: not-allowed; + opacity: 0.6; + } + + &:focus { + outline: 2px dotted #9c9c9c; + } +} + +button:disabled { + cursor: not-allowed; + opacity: 0.6; +} + +.button-small { + @include font-size('small'); + padding: rem(4px) rem(12px); +} + +.button-normal { + // @include font-size('small'); + padding: rem(8px) rem(16px); +} + +.button-large { + @include font-size('medium'); + + padding: rem(8px) rem(24px); + + @include breakpoint(md) { + padding: rem(12px) rem(36px); + } + + @include breakpoint(lg) { + padding: rem(12px) rem(48px); + } +} + +.button-xlarge { + @include font-size('x-large'); + + padding: rem(16px) rem(24px); + + @include breakpoint(md) { + padding: rem(12px) rem(36px); + } + + @include breakpoint(lg) { + padding: rem(16px) rem(48px); + } +} + +.button-first { + @include button(#ffffff, #333745); +} + +.button-first-outline { + @include button-outline(#333745, #333745); +} + +.button-second { + @include button($black, $second); +} + +.button-second-outline { + @include button-outline($black, $second); +} + +.button-third { + @include button(#ffffff, $third); +} + +.button-third-outline { + @include button-outline($third, $third); +} + +.button-danger { + @include button-outline($danger-text, $danger-text); + font-weight: 600; +} + +.button-stretch { + width: 100%; +} + +.button ~ .button { + margin-left: rem(12px); +} + +.button-no-ui { + border: none; + background: none; + text-align: left; + cursor: pointer; +} + +.button-no-padding { + padding: 0; +} + +.button-link { + color: $link; + + &:hover { + color: $link-hover; + text-decoration: underline; + } +} diff --git a/pkg/server/assets/styles/src/_font.scss b/pkg/server/assets/styles/src/_font.scss new file mode 100644 index 00000000..1f0b90d5 --- /dev/null +++ b/pkg/server/assets/styles/src/_font.scss @@ -0,0 +1,111 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +@import './responsive'; + +$lowDecay: 0.1; +$medDecay: 0.15; +$highDecay: 0.2; + +// font-size is a mixin for pre-defined font-size values in rem. +// It also includes px as a fallback for older browsers. +@mixin font-size($size, $responsive: true) { + $smSizeValue: 16; + $mdSizeValue: 16; + $lgSizeValue: 16; + + @if $size == 'x-small' { + $baseSize: 13; + + $smSizeValue: $baseSize; + $mdSizeValue: $baseSize; + $lgSizeValue: $baseSize; + } @else if $size == 'small' { + $baseSize: 14; + + $smSizeValue: $baseSize; + $mdSizeValue: $baseSize; + $lgSizeValue: $baseSize; + } @else if $size == 'regular' { + $baseSize: 16; + + $smSizeValue: $baseSize * (1 - $lowDecay); + $mdSizeValue: $baseSize * (1 - $lowDecay); + $lgSizeValue: $baseSize; + } @else if $size == 'medium' { + $baseSize: 18; + + $smSizeValue: $baseSize; + $mdSizeValue: $baseSize; + $lgSizeValue: $baseSize; + } @else if $size == 'large' { + $baseSize: 20; + + $smSizeValue: $baseSize; + $mdSizeValue: $baseSize; + $lgSizeValue: $baseSize; + } @else if $size == 'x-large' { + $baseSize: 24; + + $smSizeValue: $baseSize * (1 - $lowDecay * 2); + $mdSizeValue: $baseSize * (1 - $lowDecay); + $lgSizeValue: $baseSize; + } @else if $size == '2x-large' { + $baseSize: 32; + + $smSizeValue: $baseSize * (1 - $lowDecay * 2); + $mdSizeValue: $baseSize * (1 - $lowDecay); + $lgSizeValue: $baseSize; + } @else if $size == '3x-large' { + $baseSize: 36; + + $smSizeValue: $baseSize * (1 - $medDecay * 2); + $mdSizeValue: $baseSize * (1 - $medDecay); + $lgSizeValue: $baseSize; + } @else if $size == '4x-large' { + $baseSize: 48; + + $smSizeValue: $baseSize * (1 - $medDecay * 2); + $mdSizeValue: $baseSize * (1 - $medDecay); + $lgSizeValue: $baseSize; + } @else if $size == '5x-large' { + $baseSize: 56; + + $smSizeValue: $baseSize * (1 - $highDecay * 2); + $mdSizeValue: $baseSize * (1 - $highDecay); + $lgSizeValue: $baseSize; + } + + @if $responsive == true { + font-size: $smSizeValue * 1px; + font-size: $smSizeValue * 0.1rem; + + @include breakpoint(md) { + font-size: $mdSizeValue * 1px; + font-size: $mdSizeValue * 0.1rem; + } + + @include breakpoint(lg) { + font-size: $lgSizeValue * 1px; + font-size: $lgSizeValue * 0.1rem; + } + } @else { + font-size: $lgSizeValue * 1px; + font-size: $lgSizeValue * 0.1rem; + } +} diff --git a/pkg/server/assets/styles/src/_global.scss b/pkg/server/assets/styles/src/_global.scss new file mode 100644 index 00000000..d7650265 --- /dev/null +++ b/pkg/server/assets/styles/src/_global.scss @@ -0,0 +1,85 @@ +.main { + position: relative; + display: flex; + flex-direction: column; + background: $lighter-gray; + min-height: calc(100vh - #{$header-height}); + // margin-bottom: $footer-height; + + &.nofooter { + margin-bottom: 0; + } + + &.noheader:not(.nofooter) { + min-height: calc(100vh - #{$footer-height}); + } + &.nofooter:not(.noheader) { + min-height: calc(100vh - #{$header-height}); + } + &.nofooter.noheader { + min-height: 100vh; + } + + @include breakpoint(lg) { + margin-bottom: 0; + min-height: calc(100vh - #{$header-height}); + } +} + +/* partials */ +.partial--time { + color: $gray; + @include font-size('small'); + + .mobile-text { + @include breakpoint(md) { + display: none; + } + } + .text { + display: none; + + @include breakpoint(md) { + display: inherit; + } + } +} + +.partial--page-toolbar { + @include breakpoint(lg) { + height: rem(48px); + border-radius: rem(4px); + background: $light; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.14); + + &.bottom { + margin-top: rem(12px); + } + } +} + +/* icons */ +.icon--caret-right { + transform: rotate(-90deg); +} + +.icon--caret-left { + transform: rotate(90deg); +} + +// was originally used in note show +.frame { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); + background: white; + + &.collapsed { + .book-label { + // control the coloro of ellipsis when overflown + // color: $light-gray; + } + + .book-label a { + // color: $light-gray; + } + } +} diff --git a/pkg/server/assets/styles/src/_grid.scss b/pkg/server/assets/styles/src/_grid.scss new file mode 100644 index 00000000..66e07a28 --- /dev/null +++ b/pkg/server/assets/styles/src/_grid.scss @@ -0,0 +1,1108 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +/*! + * Bootstrap Grid v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +html { + box-sizing: border-box; + -ms-overflow-style: scrollbar; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +.container-wide { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container-wide { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container-wide { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container-wide { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container-wide { + max-width: 1040px; + } +} + +@media (min-width: 1440px) { + .container-wide { + max-width: 1280px; + } +} + +@media (min-width: 1800px) { + .container-wide { + max-width: 1660px; + } +} + +.container-fluid { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1280px; + } +} + +.row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*='col-'] { + padding-right: 0; + padding-left: 0; +} + +.col-1, +.col-2, +.col-3, +.col-4, +.col-5, +.col-6, +.col-7, +.col-8, +.col-9, +.col-10, +.col-11, +.col-12, +.col, +.col-auto, +.col-sm-1, +.col-sm-2, +.col-sm-3, +.col-sm-4, +.col-sm-5, +.col-sm-6, +.col-sm-7, +.col-sm-8, +.col-sm-9, +.col-sm-10, +.col-sm-11, +.col-sm-12, +.col-sm, +.col-sm-auto, +.col-md-1, +.col-md-2, +.col-md-3, +.col-md-4, +.col-md-5, +.col-md-6, +.col-md-7, +.col-md-8, +.col-md-9, +.col-md-10, +.col-md-11, +.col-md-12, +.col-md, +.col-md-auto, +.col-lg-1, +.col-lg-2, +.col-lg-3, +.col-lg-4, +.col-lg-5, +.col-lg-6, +.col-lg-7, +.col-lg-8, +.col-lg-9, +.col-lg-10, +.col-lg-11, +.col-lg-12, +.col-lg, +.col-lg-auto, +.col-xl-1, +.col-xl-2, +.col-xl-3, +.col-xl-4, +.col-xl-5, +.col-xl-6, +.col-xl-7, +.col-xl-8, +.col-xl-9, +.col-xl-10, +.col-xl-11, +.col-xl-12, +.col-xl, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; +} + +.col-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} + +.col-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.col-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} + +.col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} + +.col-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} + +.col-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} + +.col-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} + +.col-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -ms-flex-order: -1; + order: -1; +} + +.order-last { + -ms-flex-order: 13; + order: 13; +} + +.order-0 { + -ms-flex-order: 0; + order: 0; +} + +.order-1 { + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.333333%; +} + +.offset-2 { + margin-left: 16.666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.333333%; +} + +.offset-5 { + margin-left: 41.666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.333333%; +} + +.offset-8 { + margin-left: 66.666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.333333%; +} + +.offset-11 { + margin-left: 91.666667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-sm-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-sm-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-sm-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-sm-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-sm-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-sm-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-sm-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-sm-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-sm-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -ms-flex-order: -1; + order: -1; + } + .order-sm-last { + -ms-flex-order: 13; + order: 13; + } + .order-sm-0 { + -ms-flex-order: 0; + order: 0; + } + .order-sm-1 { + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.333333%; + } + .offset-sm-2 { + margin-left: 16.666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.333333%; + } + .offset-sm-5 { + margin-left: 41.666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.333333%; + } + .offset-sm-8 { + margin-left: 66.666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.333333%; + } + .offset-sm-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-md-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-md-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-md-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-md-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-md-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-md-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-md-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-md-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-md-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -ms-flex-order: -1; + order: -1; + } + .order-md-last { + -ms-flex-order: 13; + order: 13; + } + .order-md-0 { + -ms-flex-order: 0; + order: 0; + } + .order-md-1 { + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.333333%; + } + .offset-md-2 { + margin-left: 16.666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.333333%; + } + .offset-md-5 { + margin-left: 41.666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.333333%; + } + .offset-md-8 { + margin-left: 66.666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.333333%; + } + .offset-md-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-lg-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-lg-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-lg-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-lg-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-lg-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-lg-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-lg-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-lg-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-lg-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -ms-flex-order: -1; + order: -1; + } + .order-lg-last { + -ms-flex-order: 13; + order: 13; + } + .order-lg-0 { + -ms-flex-order: 0; + order: 0; + } + .order-lg-1 { + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.333333%; + } + .offset-lg-2 { + margin-left: 16.666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.333333%; + } + .offset-lg-5 { + margin-left: 41.666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.333333%; + } + .offset-lg-8 { + margin-left: 66.666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.333333%; + } + .offset-lg-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .col-xl-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-xl-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-xl-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-xl-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-xl-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-xl-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-xl-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-xl-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-xl-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -ms-flex-order: -1; + order: -1; + } + .order-xl-last { + -ms-flex-order: 13; + order: 13; + } + .order-xl-0 { + -ms-flex-order: 0; + order: 0; + } + .order-xl-1 { + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.333333%; + } + .offset-xl-2 { + margin-left: 16.666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.333333%; + } + .offset-xl-5 { + margin-left: 41.666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.333333%; + } + .offset-xl-8 { + margin-left: 66.666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.333333%; + } + .offset-xl-11 { + margin-left: 91.666667%; + } +} diff --git a/pkg/server/assets/styles/src/_header.scss b/pkg/server/assets/styles/src/_header.scss new file mode 100644 index 00000000..fbd971b5 --- /dev/null +++ b/pkg/server/assets/styles/src/_header.scss @@ -0,0 +1,192 @@ +@import './theme'; +@import './variables'; + +.header-wrapper { + padding: 0; + z-index: 2; + position: relative; + display: flex; + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); + background: $first; + align-items: stretch; + justify-content: space-between; + flex: 1; + flex-direction: column; + position: sticky; + top: 0; + z-index: 4; + height: $header-height; + + .container { + height: 100%; + } + + @include breakpoint(md) { + flex-direction: row; + } + + .header-content { + display: flex; + justify-content: space-between; + height: 100%; + } + + .left { + display: flex; + } + + .right { + display: flex; + } + + .search-wrapper { + align-items: center; + display: flex; + margin-left: rem(32px); + } + + .search-input { + width: rem(356px); + border: 0; + padding: 4px 12px; + border-radius: rem(4px); + @include font-size('small'); + } + + .brand { + display: flex; + align-items: center; + + &:hover { + text-decoration: none; + } + } + + .main-nav { + margin-left: rem(32px); + display: flex; + + .list { + display: flex; + } + + .item { + display: flex; + align-items: stretch; + } + + .nav-link { + @include font-size('small'); + display: flex; + font-weight: 600; + align-items: center; + padding: 0 rem(16px); + color: $white; + + &:hover { + color: $white; + text-decoration: none; + background: lighten($first, 10%); + } + } + + .nav-item { + @include font-size('small'); + font-weight: 600; + } + } + + .dropdown-trigger { + color: white; + padding: 16px; + font-size: 16px; + border: none; + cursor: pointer; + } + + .dropdown { + position: relative; + display: inline-block; + } + + .dropdown-content { + display: none; + position: absolute; + background-color: #f1f1f1; + width: rem(240px); + background: #fff; + border: 1px solid #d8d8d8; + border-radius: 4px; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.15); + top: calc(100% + 4px); + z-index: 1; + + &.show { + display: block; + } + + &.right-align { + right: 0; + } + } + + .account-dropdown { + .dropdown-trigger { + height: 100%; + } + + .account-dropdown-header { + @include font-size('small'); + color: $light-gray; + padding: rem(8px) rem(12px); + display: block; + margin-bottom: 0; + white-space: nowrap; + + svg { + fill: $light-gray; + } + + .email { + font-weight: 600; + white-space: normal; + word-break: break-all; + } + } + + .dropdown-link { + @include font-size('small'); + white-space: pre; + padding: rem(8px) rem(14px); + width: 100%; + display: block; + color: black; + + &:hover { + background: $lighter-gray; + text-decoration: none; + color: #0056b3; + } + + &.disabled { + color: #d4d4d4; + cursor: not-allowed; + } + + &:not(.disabled):focus { + background: $lighter-gray; + color: #0056b3; + outline: 1px dotted gray; + } + } + + .session-notice-wrapper { + display: flex; + align-items: center; + } + + .session-notice { + margin-left: rem(4px); + } + } +} diff --git a/pkg/server/assets/styles/src/_hljs.scss b/pkg/server/assets/styles/src/_hljs.scss new file mode 100644 index 00000000..03636f1a --- /dev/null +++ b/pkg/server/assets/styles/src/_hljs.scss @@ -0,0 +1,147 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +/* +highlight.js + +BSD 3-Clause License + +Copyright (c) 2006, Ivan Sagalaev. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +// github style + +.hljs { + display: block; + overflow-x: auto; + padding: 0.5em; + color: #333; + background: #f8f8f8; +} + +.hljs-comment, +.hljs-quote { + color: #998; + font-style: italic; +} + +.hljs-keyword, +.hljs-selector-tag, +.hljs-subst { + color: #333; + font-weight: bold; +} + +.hljs-number, +.hljs-literal, +.hljs-variable, +.hljs-template-variable, +.hljs-tag .hljs-attr { + color: #008080; +} + +.hljs-string, +.hljs-doctag { + color: #d14; +} + +.hljs-title, +.hljs-section, +.hljs-selector-id { + color: #900; + font-weight: bold; +} + +.hljs-subst { + font-weight: normal; +} + +.hljs-type, +.hljs-class .hljs-title { + color: #458; + font-weight: bold; +} + +.hljs-tag, +.hljs-name, +.hljs-attribute { + color: #000080; + font-weight: normal; +} + +.hljs-regexp, +.hljs-link { + color: #009926; +} + +.hljs-symbol, +.hljs-bullet { + color: #990073; +} + +.hljs-built_in, +.hljs-builtin-name { + color: #0086b3; +} + +.hljs-meta { + color: #999; + font-weight: bold; +} + +.hljs-deletion { + background: #fdd; +} + +.hljs-addition { + background: #dfd; +} + +.hljs-emphasis { + font-style: italic; +} + +.hljs-strong { + font-weight: bold; +} diff --git a/pkg/server/assets/styles/src/_home.scss b/pkg/server/assets/styles/src/_home.scss new file mode 100644 index 00000000..282589d2 --- /dev/null +++ b/pkg/server/assets/styles/src/_home.scss @@ -0,0 +1,185 @@ +@import './theme'; +@import './font'; + +.home-page { + .note-group-list { + flex-grow: 1; + + @include breakpoint(lg) { + margin-top: rem(16px); + } + + .note-group-list-empty { + padding: rem(40px) rem(16px); + text-align: center; + color: $gray; + } + } + + .note-group { + position: relative; + border-radius: 4px; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.14); + + &:not(:first-of-type) { + margin-top: rem(20px); + + @include breakpoint(md) { + margin-top: rem(24px); + } + } + + .note-group-header { + @include font-size('small'); + display: flex; + justify-content: space-between; + color: white; + padding: rem(12px) rem(16px); + background: $light; + color: $black; + border-bottom: 1px solid $border-color; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } + + .date { + font-weight: 600; + @include font-size('small'); + } + + .mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background: white; + z-index: 1; + opacity: 0.8; + } + + .header-date { + font-weight: 600; + @include font-size('regular'); + } + .header-count { + font-weight: 300; + } + + .list { + list-style: none; + padding-left: 0; + margin-bottom: 0; + } + } + + .note-list { + list-style: none; + padding-left: 0; + margin-bottom: 0; + } + + .note-item { + background: white; + position: relative; + + border-bottom: 1px solid $border-color; + + .link { + color: $black; + display: block; + padding: rem(12px) rem(16px); + border: 2px solid transparent; + + &:hover { + text-decoration: none; + background: $light-blue; + color: inherit; + } + } + + .meta { + line-height: rem(16px); + } + + .body { + overflow: hidden; + text-overflow: ellipsis; + } + + .note-header { + display: flex; + justify-content: space-between; + } + + .note-content { + margin-top: rem(12px); + line-height: 1.6rem; + overflow: hidden; + text-overflow: ellipsis; + color: $gray; + } + + .book-label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 700; + @include font-size('small'); + + width: 212px; + + @include breakpoint('md') { + width: 320px; + } + } + + .match { + display: inline-block; + background: #f7f77d; + padding: rem(4px) rem(4px); + } + } + + .toolbar { + text-align: right; + } + + .paginator { + display: inline-flex; + align-items: center; + + .paginator-info { + @include font-size('small'); + color: $gray; + } + + .paginator-link { + padding: rem(12px) rem(12px); + + &.disabled { + cursor: not-allowed; + } + } + + .paginator-link-prev { + margin-left: rem(8px); + + @include breakpoint(md) { + margin-left: rem(20px); + } + } + + .caret-next { + transform: rotate(-90deg); + } + + .caret-prev { + transform: rotate(90deg); + } + + .paginator-label { + font-weight: 600; + } + } +} diff --git a/pkg/server/assets/styles/src/_login.scss b/pkg/server/assets/styles/src/_login.scss new file mode 100644 index 00000000..673813e3 --- /dev/null +++ b/pkg/server/assets/styles/src/_login.scss @@ -0,0 +1,88 @@ +@import './theme'; +@import './font'; + +.auth-page { + background: $lighter-gray; + text-align: center; + min-height: 100vh; + padding: 50px 0; + + .auth-button { + margin-top: 8px; + } + + .heading { + color: $black; + @include font-size('2x-large'); + font-weight: 300; + margin-top: 12px; + margin-bottom: 0; + } + + .body { + max-width: 420px; + margin-left: auto; + margin-right: auto; + margin-top: 20px; + } + + .referrer-flash { + margin: 24px 0; + } + .error-flash { + margin-bottom: 24px; + } + + .footer { + margin-top: 20px; + line-height: 20px; + } + + .callout { + color: #7c7c7c; + @include font-size('small'); + } + .cta { + @include font-size('small'); + } + + .panel { + border: 1px solid $border-color; + background: $white; + border-radius: 2px; + padding: 20px; + text-align: left; + } + + .auth-button { + margin-top: 16px; + } + + .input-row { + & ~ .input-row { + margin-top: 12px; + } + } + .label { + @include font-size('small'); + font-weight: 600; + width: 100%; + margin-bottom: 0; + } + + .forgot { + @include font-size('small'); + float: right; + font-weight: 400; + } + + &.password-reset-page { + .email-input { + margin-top: rem(16px); + } + } + + .alert { + margin-bottom: 1rem; + } +} diff --git a/pkg/server/assets/styles/src/_markdown.scss b/pkg/server/assets/styles/src/_markdown.scss new file mode 100644 index 00000000..99b3e92f --- /dev/null +++ b/pkg/server/assets/styles/src/_markdown.scss @@ -0,0 +1,966 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +/* +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +.markdown-body .anchor { + float: left; + line-height: 1; + margin-left: -20px; + padding-right: 4px; +} + +.markdown-body .anchor:focus { + outline: none; +} + +.markdown-body h1:hover .anchor, +.markdown-body h2:hover .anchor, +.markdown-body h3:hover .anchor, +.markdown-body h4:hover .anchor, +.markdown-body h5:hover .anchor, +.markdown-body h6:hover .anchor { + text-decoration: none; +} + +.markdown-body { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + color: #24292e; + line-height: 1.5; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, + sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; +} + +.markdown-body .pl-c { + color: #6a737d; +} + +.markdown-body .pl-c1, +.markdown-body .pl-s .pl-v { + color: #005cc5; +} + +.markdown-body .pl-e, +.markdown-body .pl-en { + color: #6f42c1; +} + +.markdown-body .pl-s .pl-s1, +.markdown-body .pl-smi { + color: #24292e; +} + +.markdown-body .pl-ent { + color: #22863a; +} + +.markdown-body .pl-k { + color: #d73a49; +} + +.markdown-body .pl-pds, +.markdown-body .pl-s, +.markdown-body .pl-s .pl-pse .pl-s1, +.markdown-body .pl-sr, +.markdown-body .pl-sr .pl-cce, +.markdown-body .pl-sr .pl-sra, +.markdown-body .pl-sr .pl-sre { + color: #032f62; +} + +.markdown-body .pl-smw, +.markdown-body .pl-v { + color: #e36209; +} + +.markdown-body .pl-bu { + color: #b31d28; +} + +.markdown-body .pl-ii { + background-color: #b31d28; + color: #fafbfc; +} + +.markdown-body .pl-c2 { + background-color: #d73a49; + color: #fafbfc; +} + +.markdown-body .pl-c2:before { + content: '^M'; +} + +.markdown-body .pl-sr .pl-cce { + color: #22863a; + font-weight: 700; +} + +.markdown-body .pl-ml { + color: #735c0f; +} + +.markdown-body .pl-mh, +.markdown-body .pl-mh .pl-en, +.markdown-body .pl-ms { + color: #005cc5; + font-weight: 700; +} + +.markdown-body .pl-mi { + color: #24292e; + font-style: italic; +} + +.markdown-body .pl-mb { + color: #24292e; + font-weight: 700; +} + +.markdown-body .pl-md { + background-color: #ffeef0; + color: #b31d28; +} + +.markdown-body .pl-mi1 { + background-color: #f0fff4; + color: #22863a; +} + +.markdown-body .pl-mc { + background-color: #ffebda; + color: #e36209; +} + +.markdown-body .pl-mi2 { + background-color: #005cc5; + color: #f6f8fa; +} + +.markdown-body .pl-mdr { + color: #6f42c1; + font-weight: 700; +} + +.markdown-body .pl-ba { + color: #586069; +} + +.markdown-body .pl-sg { + color: #959da5; +} + +.markdown-body .pl-corl { + color: #032f62; + text-decoration: underline; +} + +.markdown-body details { + display: block; +} + +.markdown-body summary { + display: list-item; +} + +.markdown-body a { + background-color: transparent; +} + +.markdown-body a:active, +.markdown-body a:hover { + outline-width: 0; +} + +.markdown-body strong { + font-weight: inherit; + font-weight: bolder; +} + +.markdown-body h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.markdown-body img { + border-style: none; +} + +.markdown-body code, +.markdown-body kbd, +.markdown-body pre { + font-family: monospace, monospace; + font-size: 1em; +} + +.markdown-body hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +.markdown-body input { + font: inherit; + margin: 0; +} + +.markdown-body input { + overflow: visible; +} + +.markdown-body [type='checkbox'] { + box-sizing: border-box; + padding: 0; +} + +.markdown-body * { + box-sizing: border-box; +} + +.markdown-body input { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +.markdown-body a { + color: #0366d6; + text-decoration: none; +} + +.markdown-body a:hover { + text-decoration: underline; +} + +.markdown-body strong { + font-weight: 600; +} + +.markdown-body hr { + background: transparent; + border: 0; + border-bottom: 1px solid #dfe2e5; + height: 0; + margin: 15px 0; + overflow: hidden; +} + +.markdown-body hr:before { + content: ''; + display: table; +} + +.markdown-body hr:after { + clear: both; + content: ''; + display: table; +} + +.markdown-body table { + border-collapse: collapse; + border-spacing: 0; +} + +.markdown-body td, +.markdown-body th { + padding: 0; +} + +.markdown-body details summary { + cursor: pointer; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + margin-bottom: 0; + margin-top: 0; +} + +.markdown-body h1 { + font-size: 32px; +} + +.markdown-body h1, +.markdown-body h2 { + font-weight: 600; +} + +.markdown-body h2 { + font-size: 24px; +} + +.markdown-body h3 { + font-size: 20px; +} + +.markdown-body h3, +.markdown-body h4 { + font-weight: 600; +} + +.markdown-body h4 { + font-size: 16px; +} + +.markdown-body h5 { + font-size: 14px; +} + +.markdown-body h5, +.markdown-body h6 { + font-weight: 600; +} + +.markdown-body h6 { + font-size: 12px; +} + +.markdown-body p { + margin-bottom: 10px; + margin-top: 0; +} + +.markdown-body blockquote { + margin: 0; +} + +.markdown-body ol, +.markdown-body ul { + margin-bottom: 0; + margin-top: 0; + padding-left: 0; +} + +.markdown-body ol ol, +.markdown-body ul ol { + list-style-type: lower-roman; +} + +.markdown-body ol ol ol, +.markdown-body ol ul ol, +.markdown-body ul ol ol, +.markdown-body ul ul ol { + list-style-type: lower-alpha; +} + +.markdown-body dd { + margin-left: 0; +} + +.markdown-body code, +.markdown-body pre { + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, + monospace; + font-size: 12px; +} + +.markdown-body pre { + margin-bottom: 0; + margin-top: 0; +} + +.markdown-body input::-webkit-inner-spin-button, +.markdown-body input::-webkit-outer-spin-button { + -webkit-appearance: none; + appearance: none; + margin: 0; +} + +.markdown-body .border { + border: 1px solid #e1e4e8 !important; +} + +.markdown-body .border-0 { + border: 0 !important; +} + +.markdown-body .border-bottom { + border-bottom: 1px solid #e1e4e8 !important; +} + +.markdown-body .rounded-1 { + border-radius: 3px !important; +} + +.markdown-body .bg-white { + background-color: #fff !important; +} + +.markdown-body .bg-gray-light { + background-color: #fafbfc !important; +} + +.markdown-body .text-gray-light { + color: #6a737d !important; +} + +.markdown-body .mb-0 { + margin-bottom: 0 !important; +} + +.markdown-body .my-2 { + margin-bottom: 8px !important; + margin-top: 8px !important; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .py-0 { + padding-bottom: 0 !important; + padding-top: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .py-2 { + padding-bottom: 8px !important; + padding-top: 8px !important; +} + +.markdown-body .pl-3, +.markdown-body .px-3 { + padding-left: 16px !important; +} + +.markdown-body .px-3 { + padding-right: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body .f6 { + font-size: 12px !important; +} + +.markdown-body .lh-condensed { + line-height: 1.25 !important; +} + +.markdown-body .text-bold { + font-weight: 600 !important; +} + +.markdown-body:before { + content: ''; + display: table; +} + +.markdown-body:after { + clear: both; + content: ''; + display: table; +} + +.markdown-body > :first-child { + margin-top: 0 !important; +} + +.markdown-body > :last-child { + margin-bottom: 0 !important; +} + +.markdown-body a:not([href]) { + color: inherit; + text-decoration: none; +} + +.markdown-body blockquote, +.markdown-body dl, +.markdown-body ol, +.markdown-body p, +.markdown-body pre, +.markdown-body table, +.markdown-body ul { + margin-bottom: 16px; + margin-top: 0; +} + +.markdown-body hr { + background-color: #e1e4e8; + border: 0; + height: 0.25em; + margin: 24px 0; + padding: 0; +} + +.markdown-body blockquote { + border-left: 0.25em solid #dfe2e5; + color: #6a737d; + padding: 0 1em; +} + +.markdown-body blockquote > :first-child { + margin-top: 0; +} + +.markdown-body blockquote > :last-child { + margin-bottom: 0; +} + +.markdown-body kbd { + background-color: #fafbfc; + border: 1px solid #c6cbd1; + border-bottom-color: #959da5; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #959da5; + color: #444d56; + display: inline-block; + font-size: 11px; + line-height: 10px; + padding: 3px 5px; + vertical-align: middle; +} + +.markdown-body h1, +.markdown-body h2, +.markdown-body h3, +.markdown-body h4, +.markdown-body h5, +.markdown-body h6 { + font-weight: 600; + line-height: 1.25; + margin-bottom: 16px; + margin-top: 24px; +} + +.markdown-body h1 { + font-size: 2em; +} + +.markdown-body h1, +.markdown-body h2 { + padding-bottom: 0.3em; +} + +.markdown-body h2 { + border-bottom: 1px solid #eaecef; +} + +.markdown-body h2 { + font-size: 1.5em; +} + +.markdown-body h3 { + font-size: 1.25em; +} + +.markdown-body h4 { + font-size: 1em; +} + +.markdown-body h5 { + font-size: 0.875em; +} + +.markdown-body h6 { + color: #6a737d; + font-size: 0.85em; +} + +.markdown-body ol, +.markdown-body ul { + padding-left: 2em; +} + +.markdown-body ol ol, +.markdown-body ol ul, +.markdown-body ul ol, +.markdown-body ul ul { + margin-bottom: 0; + margin-top: 0; +} + +.markdown-body li { + word-wrap: break-all; +} + +.markdown-body li > p { + margin-top: 16px; +} + +.markdown-body li + li { + margin-top: 0.25em; +} + +.markdown-body dl { + padding: 0; +} + +.markdown-body dl dt { + font-size: 1em; + font-style: italic; + font-weight: 600; + margin-top: 16px; + padding: 0; +} + +.markdown-body dl dd { + margin-bottom: 16px; + padding: 0 16px; +} + +.markdown-body table { + display: block; + overflow: auto; + width: 100%; +} + +.markdown-body table th { + font-weight: 600; +} + +.markdown-body table td, +.markdown-body table th { + border: 1px solid #dfe2e5; + padding: 6px 13px; +} + +.markdown-body table tr { + background-color: #fff; + border-top: 1px solid #c6cbd1; +} + +.markdown-body table tr:nth-child(2n) { + background-color: #f6f8fa; +} + +.markdown-body img { + background-color: #fff; + box-sizing: content-box; + max-width: 100%; +} + +.markdown-body img[align='right'] { + padding-left: 20px; +} + +.markdown-body img[align='left'] { + padding-right: 20px; +} + +.markdown-body code { + background-color: rgba(27, 31, 35, 0.05); + border-radius: 3px; + font-size: 85%; + margin: 0; + padding: 0.2em 0.4em; +} + +.markdown-body pre { + word-wrap: normal; +} + +.markdown-body pre > code { + background: transparent; + border: 0; + font-size: 100%; + margin: 0; + padding: 0; + white-space: pre; + word-break: normal; + white-space: pre-wrap; +} + +.markdown-body .highlight { + margin-bottom: 16px; +} + +.markdown-body .highlight pre { + margin-bottom: 0; + word-break: normal; +} + +.markdown-body .highlight pre, +.markdown-body pre { + background-color: #f6f8fa; + border-radius: 3px; + font-size: 85%; + line-height: 1.45; + overflow: auto; + padding: 16px; +} + +.markdown-body pre code { + background-color: transparent; + border: 0; + display: inline; + line-height: inherit; + margin: 0; + max-width: auto; + overflow: visible; + padding: 0; + word-wrap: normal; +} + +.markdown-body .commit-tease-sha { + color: #444d56; + display: inline-block; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, + monospace; + font-size: 90%; +} + +.markdown-body .blob-wrapper { + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + overflow-x: auto; + overflow-y: hidden; +} + +.markdown-body .blob-wrapper-embedded { + max-height: 240px; + overflow-y: auto; +} + +.markdown-body .blob-num { + -moz-user-select: none; + -ms-user-select: none; + -webkit-user-select: none; + color: rgba(27, 31, 35, 0.3); + cursor: pointer; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, + monospace; + font-size: 12px; + line-height: 20px; + min-width: 50px; + padding-left: 10px; + padding-right: 10px; + text-align: right; + user-select: none; + vertical-align: top; + white-space: nowrap; + width: 1%; +} + +.markdown-body .blob-num:hover { + color: rgba(27, 31, 35, 0.6); +} + +.markdown-body .blob-num:before { + content: attr(data-line-number); +} + +.markdown-body .blob-code { + line-height: 20px; + padding-left: 10px; + padding-right: 10px; + position: relative; + vertical-align: top; +} + +.markdown-body .blob-code-inner { + color: #24292e; + font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, + monospace; + font-size: 12px; + overflow: visible; + white-space: pre; + word-wrap: normal; +} + +.markdown-body .pl-token.active, +.markdown-body .pl-token:hover { + background: #ffea7f; + cursor: pointer; +} + +.markdown-body kbd { + background-color: #fafbfc; + border: 1px solid #d1d5da; + border-bottom-color: #c6cbd1; + border-radius: 3px; + box-shadow: inset 0 -1px 0 #c6cbd1; + color: #444d56; + display: inline-block; + font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, + monospace; + line-height: 10px; + padding: 3px 5px; + vertical-align: middle; +} + +.markdown-body :checked + .radio-label { + border-color: #0366d6; + position: relative; + z-index: 1; +} + +.markdown-body .tab-size[data-tab-size='1'] { + -moz-tab-size: 1; + tab-size: 1; +} + +.markdown-body .tab-size[data-tab-size='2'] { + -moz-tab-size: 2; + tab-size: 2; +} + +.markdown-body .tab-size[data-tab-size='3'] { + -moz-tab-size: 3; + tab-size: 3; +} + +.markdown-body .tab-size[data-tab-size='4'] { + -moz-tab-size: 4; + tab-size: 4; +} + +.markdown-body .tab-size[data-tab-size='5'] { + -moz-tab-size: 5; + tab-size: 5; +} + +.markdown-body .tab-size[data-tab-size='6'] { + -moz-tab-size: 6; + tab-size: 6; +} + +.markdown-body .tab-size[data-tab-size='7'] { + -moz-tab-size: 7; + tab-size: 7; +} + +.markdown-body .tab-size[data-tab-size='8'] { + -moz-tab-size: 8; + tab-size: 8; +} + +.markdown-body .tab-size[data-tab-size='9'] { + -moz-tab-size: 9; + tab-size: 9; +} + +.markdown-body .tab-size[data-tab-size='10'] { + -moz-tab-size: 10; + tab-size: 10; +} + +.markdown-body .tab-size[data-tab-size='11'] { + -moz-tab-size: 11; + tab-size: 11; +} + +.markdown-body .tab-size[data-tab-size='12'] { + -moz-tab-size: 12; + tab-size: 12; +} + +.markdown-body .task-list-item { + list-style-type: none; +} + +.markdown-body .task-list-item + .task-list-item { + margin-top: 3px; +} + +.markdown-body .task-list-item input { + margin: 0 0.2em 0.25em -1.6em; + vertical-align: middle; +} + +.markdown-body hr { + border-bottom-color: #eee; +} + +.markdown-body .pl-0 { + padding-left: 0 !important; +} + +.markdown-body .pl-1 { + padding-left: 4px !important; +} + +.markdown-body .pl-2 { + padding-left: 8px !important; +} + +.markdown-body .pl-3 { + padding-left: 16px !important; +} + +.markdown-body .pl-4 { + padding-left: 24px !important; +} + +.markdown-body .pl-5 { + padding-left: 32px !important; +} + +.markdown-body .pl-6 { + padding-left: 40px !important; +} + +.markdown-body .pl-7 { + padding-left: 48px !important; +} + +.markdown-body .pl-8 { + padding-left: 64px !important; +} + +.markdown-body .pl-9 { + padding-left: 80px !important; +} + +.markdown-body .pl-10 { + padding-left: 96px !important; +} + +.markdown-body .pl-11 { + padding-left: 112px !important; +} + +.markdown-body .pl-12 { + padding-left: 128px !important; +} diff --git a/pkg/server/assets/styles/src/_marker.scss b/pkg/server/assets/styles/src/_marker.scss new file mode 100644 index 00000000..75afcc10 --- /dev/null +++ b/pkg/server/assets/styles/src/_marker.scss @@ -0,0 +1,39 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +.marker { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; +} + +.marker-first { + color: #fff; + background-color: #007bff; +} + +.marker-info { + color: #fff; + background-color: #17a2b8; +} diff --git a/pkg/server/assets/styles/src/_note.scss b/pkg/server/assets/styles/src/_note.scss new file mode 100644 index 00000000..a903a66e --- /dev/null +++ b/pkg/server/assets/styles/src/_note.scss @@ -0,0 +1,102 @@ +.note-page { + // min-height: calc(100vh - 57px); + background: $lighter-gray; + flex-grow: 1; + flex-basis: 0; + + // .inner { + // display: flex; + // justify-content: center; + // padding-top: rem(40px); + // padding-bottom: rem(40px); + // + // @include breakpoint(md) { + // padding-top: rem(52px); + // padding-bottom: rem(52px); + // } + // } + + .header { + display: flex; + align-items: center; + justify-content: space-between; + padding: rem(12px) rem(16px); + border-bottom: 1px solid $border-color; + } + .header-left, + .header-right { + display: flex; + align-items: center; + } + + .book-icon { + vertical-align: middle; + } + + .content-wrapper { + padding: rem(12px) rem(16px); + } + + .collapsed-content { + color: $light-gray; + } + + .footer { + display: flex; + justify-content: space-between; + align-items: center; + @include font-size('small'); + padding: rem(12px) rem(16px); + } + + .ts { + color: $light-gray; + } + .ts-lead { + display: none; + @include breakpoint(md) { + display: inline; + } + } + + .match { + display: inline-block; + background: #f7f77d; + } + + .book-label { + @include font-size('medium'); + font-weight: 600; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: $black; + + a { + color: inherit; + + &:hover { + color: inherit; + } + } + } + + // header + .header { + .book-label { + max-width: rem(200px); + margin-left: rem(12px); + + @include breakpoint(sm) { + max-width: rem(200px); + } + @include breakpoint(md) { + max-width: rem(420px); + } + @include breakpoint(lg) { + max-width: rem(600px); + } + } + } +} diff --git a/pkg/server/assets/styles/src/_reboot.scss b/pkg/server/assets/styles/src/_reboot.scss new file mode 100644 index 00000000..174f2989 --- /dev/null +++ b/pkg/server/assets/styles/src/_reboot.scss @@ -0,0 +1,367 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +/*! + * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) + */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} + +[tabindex='-1']:focus { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):hover, +a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg { + overflow: hidden; + vertical-align: middle; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; +} + +th { + text-align: inherit; +} + +label { + display: inline-block; + margin-bottom: 0.5rem; +} + +button { + border-radius: 0; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +select { + word-wrap: normal; +} + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; +} + +button:not(:disabled), +[type='button']:not(:disabled), +[type='reset']:not(:disabled), +[type='submit']:not(:disabled) { + cursor: pointer; +} + +button::-moz-focus-inner, +[type='button']::-moz-focus-inner, +[type='reset']::-moz-focus-inner, +[type='submit']::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type='radio'], +input[type='checkbox'] { + box-sizing: border-box; + padding: 0; +} + +input[type='date'], +input[type='time'], +input[type='datetime-local'], +input[type='month'] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type='number']::-webkit-inner-spin-button, +[type='number']::-webkit-outer-spin-button { + height: auto; +} + +[type='search'] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} +/*# sourceMappingURL=bootstrap-reboot.css.map */ diff --git a/pkg/server/assets/styles/src/_rem.scss b/pkg/server/assets/styles/src/_rem.scss new file mode 100644 index 00000000..6c9d98a9 --- /dev/null +++ b/pkg/server/assets/styles/src/_rem.scss @@ -0,0 +1,116 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +/* +MIT License + +Copyright (c) 2017 Pierre Burel + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +@use "sass:math"; +// assume 1 rem = 10 px +// achieved by body { font-size: 62.5%; ) +$rem-baseline: 10px !default; +$rem-fallback: false !default; +$rem-px-only: false !default; + +@function rem-separator($list, $separator: false) { + @if $separator == 'comma' or $separator == 'space' { + @return append($list, null, $separator); + } + + @if function-exists('list-separator') == true { + @return list-separator($list); + } + + // list-separator polyfill by Hugo Giraudel (https://sass-compatibility.github.io/#list_separator_function) + $test-list: (); + @each $item in $list { + $test-list: append($test-list, $item, space); + } + + @return if($test-list == $list, space, comma); +} + +@mixin rem-baseline($zoom: 100%) { + font-size: $zoom / 16px * $rem-baseline; +} + +@function rem-convert($to, $values...) { + $result: (); + $separator: rem-separator($values); + + @each $value in $values { + @if type-of($value) == 'number' and unit($value) == 'rem' and $to == 'px' { + $result: append($result, $value / 1rem * $rem-baseline, $separator); + } @else if + type-of($value) == + 'number' and + unit($value) == + 'px' and + $to == + 'rem' + { + $result: append( + $result, + math.div($value, $rem-baseline) * 1rem, + $separator + ); + } @else if type-of($value) == 'list' { + $value-separator: rem-separator($value); + $value: rem-convert($to, $value...); + $value: rem-separator($value, $value-separator); + $result: append($result, $value, $separator); + } @else { + $result: append($result, $value, $separator); + } + } + + @return if(length($result) == 1, nth($result, 1), $result); +} + +@function rem($values...) { + @if $rem-px-only { + @return rem-convert(px, $values...); + } @else { + @return rem-convert(rem, $values...); + } +} + +@mixin rem($properties, $values...) { + @if type-of($properties) == 'map' { + @each $property in map-keys($properties) { + @include rem($property, map-get($properties, $property)); + } + } @else { + @each $property in $properties { + @if $rem-fallback or $rem-px-only { + #{$property}: rem-convert(px, $values...); + } + @if not $rem-px-only { + #{$property}: rem-convert(rem, $values...); + } + } + } +} diff --git a/pkg/server/assets/styles/src/_responsive.scss b/pkg/server/assets/styles/src/_responsive.scss new file mode 100644 index 00000000..caf5bc12 --- /dev/null +++ b/pkg/server/assets/styles/src/_responsive.scss @@ -0,0 +1,62 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +@import './variables'; + +@mixin breakpoint($point) { + @if $point == xl { + @media (min-width: $xl-breakpoint) { + @content; + } + } @else if $point == lg { + @media (min-width: $lg-breakpoint) { + @content; + } + } @else if $point == md { + @media (min-width: $md-breakpoint) { + @content; + } + } @else if $point == sm { + @media (min-width: $sm-breakpoint) { + @content; + } + } @else if $point == smonly { + @media (min-width: $sm-breakpoint) and (max-width: $md-breakpoint - 1px) { + @content; + } + } @else if $point == smdown { + @media (max-width: $md-breakpoint - 1px) { + @content; + } + } @else if $point == mdonly { + @media (min-width: $md-breakpoint) and (max-width: $lg-breakpoint - 1px) { + @content; + } + } @else if $point == mddown { + @media (max-width: $lg-breakpoint - 1px) { + @content; + } + } +} + +// landscape is the mobile landscape mode +@mixin landscape() { + @media (max-height: 400px) { + @content; + } +} diff --git a/pkg/server/assets/styles/src/_select.scss b/pkg/server/assets/styles/src/_select.scss new file mode 100644 index 00000000..f534a64a --- /dev/null +++ b/pkg/server/assets/styles/src/_select.scss @@ -0,0 +1,463 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +/** + * React Select + * ============ + * Created by Jed Watson and Joss Mackison for KeystoneJS, http://www.keystonejs.com/ + * https://twitter.com/jedwatson https://twitter.com/jossmackison https://twitter.com/keystonejs + * MIT License: https://github.com/JedWatson/react-select +*/ +.Select { + position: relative; +} +.Select input::-webkit-contacts-auto-fill-button, +.Select input::-webkit-credentials-auto-fill-button { + display: none !important; +} +.Select input::-ms-clear { + display: none !important; +} +.Select input::-ms-reveal { + display: none !important; +} +.Select, +.Select div, +.Select input, +.Select span { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +.Select.is-disabled .Select-arrow-zone { + cursor: default; + pointer-events: none; + opacity: 0.35; +} +.Select.is-disabled > .Select-control { + background-color: #f9f9f9; +} +.Select.is-disabled > .Select-control:hover { + box-shadow: none; +} +.Select.is-open > .Select-control { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + background: #fff; +} +.Select.is-open > .Select-control .Select-arrow { + top: -2px; + border-color: transparent transparent #999; + border-width: 0 5px 5px; +} +.Select.is-searchable.is-open > .Select-control { + cursor: text; +} +.Select.is-searchable.is-focused:not(.is-open) > .Select-control { + cursor: text; +} +.Select.is-focused > .Select-control { + background: #fff; +} +.Select.is-focused > .Select-control { + border-top-color: #b6c9e9; + border-bottom-color: #b6c9e9; +} +.Select.is-focused:not(.is-open) > .Select-control { + background: #fff; +} +.Select.has-value.is-clearable.Select--single > .Select-control .Select-value { + padding-right: 42px; +} +.Select.has-value.Select--single > .Select-control .Select-value .Select-value-label, +.Select.has-value.is-pseudo-focused.Select--single + > .Select-control + .Select-value + .Select-value-label { + color: #333; +} +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label, +.Select.has-value.is-pseudo-focused.Select--single + > .Select-control + .Select-value + a.Select-value-label { + cursor: pointer; + text-decoration: none; +} +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label:hover, +.Select.has-value.is-pseudo-focused.Select--single + > .Select-control + .Select-value + a.Select-value-label:hover, +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label:focus, +.Select.has-value.is-pseudo-focused.Select--single + > .Select-control + .Select-value + a.Select-value-label:focus { + color: #007eff; + outline: none; + text-decoration: underline; +} +.Select.has-value.Select--single > .Select-control .Select-value a.Select-value-label:focus, +.Select.has-value.is-pseudo-focused.Select--single + > .Select-control + .Select-value + a.Select-value-label:focus { + background: #fff; +} +.Select.has-value.is-pseudo-focused .Select-input { + opacity: 0; +} +.Select.is-open .Select-arrow, +.Select .Select-arrow-zone:hover > .Select-arrow { + border-top-color: #666; +} +.Select.Select--rtl { + direction: rtl; + text-align: right; +} +.Select-control { + background-color: #fff; + color: #333; + cursor: default; + display: table; + border-top: 1px solid #e2e2e2; + border-bottom: 1px solid #e2e2e2; + height: 36px; + outline: none; + overflow: hidden; + position: relative; + width: 100%; +} +.Select-control:hover { + // box-shadow: inset 0px 0px 3px 2px rgba(0, 0, 0, 0.03); +} +.Select-control .Select-input:focus { + outline: none; + background: #fff; +} +.Select-placeholder, +.Select--single > .Select-control .Select-value { + bottom: 0; + font-weight: 300; + color: #c3c3c3; + left: 0; + line-height: 34px; + // padding-left: 10px; + padding-left: 16px; + padding-right: 10px; + position: absolute; + right: 0; + top: 0; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.Select-input { + height: 34px; + padding-left: 10px; + padding-right: 10px; + vertical-align: middle; +} +.Select-input > input { + width: 100%; + background: none transparent; + border: 0 none; + box-shadow: none; + cursor: default; + display: inline-block; + font-family: inherit; + font-size: inherit; + margin: 0; + outline: none; + line-height: 17px; + /* For IE 8 compatibility */ + padding: 8px 0 12px; + /* For IE 8 compatibility */ + -webkit-appearance: none; +} +.is-focused .Select-input > input { + cursor: text; +} +.has-value.is-pseudo-focused .Select-input { + opacity: 0; +} +.Select-control:not(.is-searchable) > .Select-input { + outline: none; +} +.Select-loading-zone { + cursor: pointer; + display: table-cell; + position: relative; + text-align: center; + vertical-align: middle; + width: 16px; +} +.Select-loading { + -webkit-animation: Select-animation-spin 400ms infinite linear; + -o-animation: Select-animation-spin 400ms infinite linear; + animation: Select-animation-spin 400ms infinite linear; + width: 16px; + height: 16px; + box-sizing: border-box; + border-radius: 50%; + border: 2px solid #ccc; + border-right-color: #333; + display: inline-block; + position: relative; + vertical-align: middle; +} +.Select-clear-zone { + -webkit-animation: Select-animation-fadeIn 200ms; + -o-animation: Select-animation-fadeIn 200ms; + animation: Select-animation-fadeIn 200ms; + color: #999; + cursor: pointer; + display: table-cell; + position: relative; + text-align: center; + vertical-align: middle; + width: 17px; +} +.Select-clear-zone:hover { + color: #d0021b; +} +.Select-clear { + display: inline-block; + font-size: 18px; + line-height: 1; +} +.Select--multi .Select-clear-zone { + width: 17px; +} +.Select-arrow-zone { + cursor: pointer; + display: table-cell; + position: relative; + text-align: center; + vertical-align: middle; + width: 25px; + padding-right: 5px; +} +.Select--rtl .Select-arrow-zone { + padding-right: 0; + padding-left: 5px; +} +.Select-arrow { + border-color: #999 transparent transparent; + border-style: solid; + border-width: 5px 5px 2.5px; + display: inline-block; + height: 0; + width: 0; + position: relative; +} +.Select-control > *:last-child { + padding-right: 5px; +} +.Select--multi .Select-multi-value-wrapper { + display: inline-block; +} +.Select .Select-aria-only { + position: absolute; + display: inline-block; + height: 1px; + width: 1px; + margin: -1px; + clip: rect(0, 0, 0, 0); + overflow: hidden; + float: left; +} +@-webkit-keyframes Select-animation-fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +@keyframes Select-animation-fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} +.Select-menu-outer { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + background-color: #fff; + border: 1px solid #ccc; + border-top-color: #e6e6e6; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.06); + box-sizing: border-box; + margin-top: -1px; + max-height: 200px; + position: absolute; + left: 0; + top: 100%; + width: 100%; + z-index: 1; + -webkit-overflow-scrolling: touch; +} +.Select.is-open > .Select-menu-outer { + border-top-color: #b6c9e9; +} +.Select-menu { + max-height: 150px; + overflow-y: auto; +} +.Select-option { + box-sizing: border-box; + background-color: #fff; + color: #666666; + cursor: pointer; + display: block; + padding: 8px 10px; +} +.Select-option:last-child { + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +.Select-option.is-selected { + background-color: #f5faff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.04); + color: #333; +} +.Select-option.is-focused { + background-color: #ebf5ff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.08); + color: #333; +} +.Select-option.is-disabled { + color: #cccccc; + cursor: default; +} +.Select-noresults { + box-sizing: border-box; + color: #999999; + cursor: default; + display: block; + padding: 8px 10px; +} +.Select--multi .Select-input { + vertical-align: middle; + margin-left: 10px; + padding: 0; +} +.Select--multi.Select--rtl .Select-input { + margin-left: 0; + margin-right: 10px; +} +.Select--multi.has-value .Select-input { + margin-left: 5px; +} +.Select--multi .Select-value { + background-color: #ebf5ff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.08); + border-radius: 2px; + border: 1px solid #c2e0ff; + /* Fallback color for IE 8 */ + border: 1px solid rgba(0, 126, 255, 0.24); + color: #007eff; + display: inline-block; + font-size: 0.9em; + line-height: 1.4; + margin-left: 5px; + margin-top: 5px; + vertical-align: top; +} +.Select--multi .Select-value-icon, +.Select--multi .Select-value-label { + display: inline-block; + vertical-align: middle; +} +.Select--multi .Select-value-label { + border-bottom-right-radius: 2px; + border-top-right-radius: 2px; + cursor: default; + padding: 2px 5px; +} +.Select--multi a.Select-value-label { + color: #007eff; + cursor: pointer; + text-decoration: none; +} +.Select--multi a.Select-value-label:hover { + text-decoration: underline; +} +.Select--multi .Select-value-icon { + cursor: pointer; + border-bottom-left-radius: 2px; + border-top-left-radius: 2px; + border-right: 1px solid #c2e0ff; + /* Fallback color for IE 8 */ + border-right: 1px solid rgba(0, 126, 255, 0.24); + padding: 1px 5px 3px; +} +.Select--multi .Select-value-icon:hover, +.Select--multi .Select-value-icon:focus { + background-color: #d8eafd; + /* Fallback color for IE 8 */ + background-color: rgba(0, 113, 230, 0.08); + color: #0071e6; +} +.Select--multi .Select-value-icon:active { + background-color: #c2e0ff; + /* Fallback color for IE 8 */ + background-color: rgba(0, 126, 255, 0.24); +} +.Select--multi.Select--rtl .Select-value { + margin-left: 0; + margin-right: 5px; +} +.Select--multi.Select--rtl .Select-value-icon { + border-right: none; + border-left: 1px solid #c2e0ff; + /* Fallback color for IE 8 */ + border-left: 1px solid rgba(0, 126, 255, 0.24); +} +.Select--multi.is-disabled .Select-value { + background-color: #fcfcfc; + border: 1px solid #e3e3e3; + color: #333; +} +.Select--multi.is-disabled .Select-value-icon { + cursor: not-allowed; + border-right: 1px solid #e3e3e3; +} +.Select--multi.is-disabled .Select-value-icon:hover, +.Select--multi.is-disabled .Select-value-icon:focus, +.Select--multi.is-disabled .Select-value-icon:active { + background-color: #fcfcfc; +} +@keyframes Select-animation-spin { + to { + transform: rotate(1turn); + } +} +@-webkit-keyframes Select-animation-spin { + to { + -webkit-transform: rotate(1turn); + } +} diff --git a/pkg/server/assets/styles/src/_settings.scss b/pkg/server/assets/styles/src/_settings.scss new file mode 100644 index 00000000..81c2ac43 --- /dev/null +++ b/pkg/server/assets/styles/src/_settings.scss @@ -0,0 +1,147 @@ +@import './theme'; +@import './font'; + +.settings-page { + .sidebar { + box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); + background: white; + margin-bottom: rem(20px); + margin-top: rem(20px); + + @include breakpoint(lg) { + margin-bottom: 0; + margin-top: 0; + } + } + + .sidebar-item { + display: block; + padding: rem(12px) rem(16px); + border-left: 4px solid transparent; + @include font-size('regular'); + + &:hover { + text-decoration: none; + background: $light; + } + + &.active { + font-weight: 600; + border-left-color: $first; + } + } + + .setting-section-wrapper { + .header { + @include breakpoint(lg) { + display: none; + } + } + + .setting-section { + margin-top: rem(24px); + background: white; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.14); + + &:first-child { + margin-top: 0; + } + } + + .section-heading { + @include font-size('regular'); + font-weight: 600; + padding-bottom: rem(4px); + background: $light; + padding: rem(16px) rem(20px); + } + .section-content { + margin-top: rem(20px); + } + + .actions { + margin-top: rem(18px); + text-align: right; + } + } + + .setting-row { + padding: rem(16px) rem(20px); + + &:not(:last-child) { + border-bottom: 1px solid $border-color; + } + + .setting-row-summary { + display: flex; + flex-direction: column; + // align-items: flex-start; + + @include breakpoint(md) { + flex-direction: row; + justify-content: space-between; + align-items: center; + } + } + + .setting-row-main { + padding-top: rem(24px); + } + + .setting-name { + font-weight: 400; + @include font-size('regular'); + margin-bottom: 0; + } + .setting-desc { + margin-bottom: 0; + @include font-size('small'); + color: $gray; + } + .setting-action { + display: flex; + flex-direction: column; + + @include breakpoint(md) { + flex-direction: row; + } + } + + .setting-right { + display: flex; + word-break: break-all; + justify-content: space-between; + align-items: center; + margin-top: rem(4px); + + @include breakpoint(md) { + flex-direction: row; + align-items: center; + margin-top: 0; + } + } + + .setting-edit { + color: $link; + padding: 0; + + &:hover { + color: $link-hover; + } + @include breakpoint(md) { + margin-left: rem(16px); + } + } + + .input-row { + & ~ .input-row, + .input-row { + margin-top: rem(12px); + } + } + } + + .email-verification-form { + margin-left: rem(12px); + } +} diff --git a/pkg/server/assets/styles/src/_shared.scss b/pkg/server/assets/styles/src/_shared.scss new file mode 100644 index 00000000..773706bb --- /dev/null +++ b/pkg/server/assets/styles/src/_shared.scss @@ -0,0 +1,241 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +@import './font'; +@import './responsive'; + +@keyframes holderPulse { + 0% { + opacity: 0.4; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0.4; + } +} + +// placeholder frames +.holder { + animation: holderPulse 800ms infinite; + background: #f4f4f4; + + &.holder-dark { + background: #e6e6e6; + } +} + +input[type='text']:disabled, +input[type='email']:disabled, +input[type='number']:disabled, +input[type='password']:disabled, +textarea:disabled { + background-color: $lighter-gray; + cursor: not-allowed; +} + +.list-unstyled { + list-style: none; + padding-left: 0; + margin-bottom: 0; +} + +.sr-only { + display: none; +} + +.scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +button { + img, + svg { + display: block; + } +} + +.text-input { + border: 1px solid $border-color; + padding: rem(8px) rem(12px); + position: relative; + border-radius: rem(4px); + display: block; + + &::placeholder { + color: $gray; + } + &:focus { + border-color: $light-blue; + box-shadow: inset 0 1px 2px rgba(24, 31, 35, 0.075), + 0 0 0 0.2em rgba(4, 100, 210, 0.3); + outline: none; + } +} + +.text-input-small { + padding: rem(4px) rem(12px); +} + +.text-input-medium { + padding: rem(8px) rem(12px); +} + +.text-input-stretch { + width: 100%; +} + +.label-full { + width: 100%; +} + +a { + color: $link; + + &:hover { + color: $link-hover; + } +} + +// normalize +h1, +h2, +h3, +h4, +h5, +h6 { + margin-bottom: 0; +} + +// grid +.container.mobile-fw { + @include breakpoint(mddown) { + max-width: 100%; + } +} +.container.mobile-nopadding { + @include breakpoint(mddown) { + padding-left: 0; + padding-right: 0; + + .row { + margin-left: 0; + margin-right: 0; + } + [class*='col-'] { + // Apply to all column(s) inside the row + padding-left: 0; + padding-right: 0; + } + } +} +html body { + overflow-y: scroll; +} + +.page { + padding-top: rem(20px); + padding-bottom: rem(20px); + + &.page-mobile-full { + padding-top: 0; + padding-bottom: 0; + + @include breakpoint(lg) { + padding-top: rem(32px); + padding-bottom: rem(32px); + } + } +} + +.page-header { + margin-top: rem(20px); + + &.page-header-full { + margin-bottom: rem(20px); + } + + @include breakpoint(lg) { + // padding: 0; + margin-bottom: rem(20px); + margin-top: 0; + } +} + +.form-select { + appearance: none; + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAYAAACEYr13AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACeSURBVHgBzZPBCYQwFERn2Qa2BEuwhJSyHawdrB1oB1qCV6uwhHj0qBXoBPwQRONXEXzwLoEXcCTAzfxogpPEdJyNcZCIWu8CO5+p8WOxoR9NnK3EYrEX/wOxuDmqUcSikejlXfCFfqie5ngE/ie4cVS/ibS0XB4anBhxSaKIU+xQBmLV8m6HZiW2OECEi4/JoXrO78AFHR1oTSvcxQTq7lVcue6CCAAAAABJRU5ErkJggg=='); + background-color: #fff; + background-repeat: no-repeat; + background-position: right 8px center; + background-size: 8px 10px; + border: 1px solid $border-color; + min-height: 34px; + padding: 6px 8px; + padding-right: 24px; + outline: none; + vertical-align: middle; + border-radius: 4px; + box-shadow: inset 0 1px 2px rgba(32, 36, 41, 0.08); + + &:focus { + border-color: #2188ff; + outline: none; + box-shadow: inset 0 1px 2px rgba(32, 36, 41, 0.08), + 0 0 0 2px rgba(3, 102, 214, 0.3); + } + &:disabled, + &.form-select-disabled { + background-image: url('data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAYAAACEYr13AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEKSURBVHgBzVTNDYIwFC4NB46OwAi4gY7gETgoE6gTGCcwTgAJ4efGCLCBjMAIXrmA3yOhQazQhJj4JQ0v7fte3/e1hbFfIk3TYxzHp6kc7dtCFEUW5/xBcdM0a9d1S1kel00mSWKCnIkkxDSnXADIMYYEU9O0zPf91WwB6L6NyB3atrUMw7hNFkCbFyROmXYYmypMDMNwo+t6ztSwtW27oEAXrXBuwu2rCht+WPgU7C8gPCBzYOBKhQS5FTwIKBYeQFeJoWyiKNYH5Co6OCuQr/0JdBuPVyElQCd7GRMb3B3HebsHHzexrmvyQvZwqjFZWsDzvCc62BFhSGYD3UMsfs6ToKOd+6EsxgtrtWLW4gUN3AAAAABJRU5ErkJggg=='); + background-color: $lighter-gray; + } +} + +.input-label { + // width: 100%; + width: auto; + font-weight: 600; + margin-bottom: rem(4px); + @include font-size('small'); +} + +.page-heading { + @include font-size('x-large'); +} + +.dropdown-caret { + display: inline-block; + vertical-align: middle; + border-top-width: 4px; + border-top-style: solid; + border-right: 4px solid transparent; + border-bottom: 0 solid transparent; + border-left: 4px solid transparent; + margin-left: rem(8px); +} + +.divider { + height: 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} diff --git a/pkg/server/assets/styles/src/_theme.scss b/pkg/server/assets/styles/src/_theme.scss new file mode 100644 index 00000000..a3e09996 --- /dev/null +++ b/pkg/server/assets/styles/src/_theme.scss @@ -0,0 +1,47 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +// basic colors +$black: #2a2a2a; +$white: #ffffff; +$light: #f7f9fa; +$gray: #686868; +$light-gray: #8c8c8c; +$lighter-gray: #f3f3f3; +$dark-gray: #637283; + +// primary colors +$first: #072a40; +$second: #e7e7e7; +$third: #0a4b73; + +// functional colors +$border-color: #d8d8d8; +$border-color-light: $lighter-gray; + +$link: #6f53c0; +$link-hover: darken($link, 5%); + +$danger-text: #cb2431; +$danger-background: #f8d7da; + +$blue: #0668d7; +$light-blue: #ecf4ff; +$green: #28a755; + +$active: #49abfd; diff --git a/pkg/server/api/health.go b/pkg/server/assets/styles/src/_variables.scss similarity index 76% rename from pkg/server/api/health.go rename to pkg/server/assets/styles/src/_variables.scss index 3480b796..808e20f7 100644 --- a/pkg/server/api/health.go +++ b/pkg/server/assets/styles/src/_variables.scss @@ -16,13 +16,16 @@ * along with Dnote. If not, see . */ -package api +$header-height: 60px; +$footer-height: 56px; -import ( - "net/http" -) +// breakpoints +$xl-breakpoint: 1441px; +$lg-breakpoint: 992px; +$md-breakpoint: 576px; +$sm-breakpoint: 321px; -func (a *API) checkHealth(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) +:export { + mdBreakpoint: $md-breakpoint; + smBreakpoint: $sm-breakpoint; } diff --git a/pkg/server/assets/styles/src/main.scss b/pkg/server/assets/styles/src/main.scss new file mode 100644 index 00000000..bf1af413 --- /dev/null +++ b/pkg/server/assets/styles/src/main.scss @@ -0,0 +1,144 @@ +/* Copyright (C) 2019, 2020 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 . + */ + +@import './reboot'; +@import './grid'; +@import './bootstrap'; +@import './buttons'; +@import './responsive'; +@import './select'; +@import './shared'; +@import './marker'; +@import './rem'; +@import './markdown'; +@import './hljs'; + +@import './login'; +@import './home'; +@import './note'; +@import './books'; +@import './settings'; +@import './header'; +@import './global'; + +html { + font-size: 62.5%; /* 1.0 rem = 10px */ +} + +html body { + margin: 0; + font-size: 1.6rem; +} + +img { + max-width: 100%; +} + +// input[type='email'], +// input[type='password'], +// input[type='text'] { +// &::placeholder { +// color: #aaa; +// } +// } + +.main-content { + padding-top: 24px; +} + +.no-scroll { + overflow: hidden; + + // prevent ios safari from scrolling + // but it causes page to scroll to top on modal open + // position: fixed; + // left: 0; + // right: 0; + // top: 0; + // bottom: 0; +} + +.container.mobile-nopadding { + @include breakpoint(mdonly) { + max-width: 100%; + } + + @include breakpoint(mddown) { + padding-left: 0; + padding-right: 0; + + .row { + margin-left: 0; + margin-right: 0; + } + [class*='col-'] { + // Apply to all column(s) inside the row + padding-left: 0; + padding-right: 0; + } + } +} + +// START: override bootstrap +.form-control { + font-size: 1.6rem; +} +.dropdown { + position: inherit; +} +// END: override bootstrap +// START: bootstrap related +.input-group { + input ~ button { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } +} +// END: bootstrap related + +// .twitter-follow-btn { +// position: fixed; +// bottom: 0px; +// right: 10px; +// } + +.page { + //padding: 35px 0; + //min-height: calc(100vh - 57px); + //min-height: 100vh; + // padding-top: rem(48px); +} +.page-bgdark { + background: #ececec; +} + +// Measure scrollbar width for padding body during modal show/hide +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +.input { + border-radius: rem(4px); + background-clip: padding-box; + border: 1px solid #ced4da; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} diff --git a/pkg/server/buildinfo/info.go b/pkg/server/buildinfo/info.go new file mode 100644 index 00000000..0e7d201f --- /dev/null +++ b/pkg/server/buildinfo/info.go @@ -0,0 +1,15 @@ +package buildinfo + +var ( + // Version is the server version + Version = "master" + // CSSFiles is the css files + CSSFiles = "" + // JSFiles is the js files + JSFiles = "" + // RootURL is the root url + RootURL = "/" + // Standalone reprsents whether the build is for on-premises. It is a string + // rather than a boolean, so that it can be overridden during compile time. + Standalone = "false" +) diff --git a/pkg/server/config/config.go b/pkg/server/config/config.go index c3e5eaaa..a5d2cc3b 100644 --- a/pkg/server/config/config.go +++ b/pkg/server/config/config.go @@ -25,6 +25,11 @@ import ( "os" ) +const ( + // AppEnvProduction represents an app environment for production. + AppEnvProduction string = "PRODUCTION" +) + var ( // ErrDBMissingHost is an error for an incomplete configuration missing the host ErrDBMissingHost = errors.New("DB Host is empty") @@ -92,11 +97,25 @@ func loadDBConfig() PostgresConfig { // Config is an application configuration type Config struct { + AppEnv string WebURL string OnPremise bool DisableRegistration bool Port string DB PostgresConfig + PageTemplateDir string + StaticDir string + AssetBaseURL string +} + +func getAppEnv() string { + // DEPRECATED + goEnv := os.Getenv("GO_ENV") + if goEnv != "" { + return goEnv + } + + return os.Getenv("APP_ENV") } // Load constructs and returns a new config based on the environment variables. @@ -107,11 +126,13 @@ func Load() Config { } c := Config{ + AppEnv: getAppEnv(), WebURL: os.Getenv("WebURL"), Port: port, OnPremise: readBoolEnv("OnPremise"), DisableRegistration: readBoolEnv("DisableRegistration"), DB: loadDBConfig(), + AssetBaseURL: "", } if err := validate(c); err != nil { @@ -126,6 +147,26 @@ func (c *Config) SetOnPremise(val bool) { c.OnPremise = val } +// SetPageTemplateDir sets page template dir for the config +func (c *Config) SetPageTemplateDir(d string) { + c.PageTemplateDir = d +} + +// SetStaticDir sets static dir for the confi +func (c *Config) SetStaticDir(d string) { + c.StaticDir = d +} + +// SetAssetBaseURL sets static dir for the confi +func (c *Config) SetAssetBaseURL(d string) { + c.AssetBaseURL = d +} + +// IsProd checks if the app environment is configured to be production. +func (c Config) IsProd() bool { + return c.AppEnv == AppEnvProduction +} + func validate(c Config) error { if _, err := url.ParseRequestURI(c.WebURL); err != nil { return errors.Wrapf(ErrWebURLInvalid, "provided: '%s'", c.WebURL) diff --git a/pkg/server/consts/consts.go b/pkg/server/consts/consts.go new file mode 100644 index 00000000..8b6e83ab --- /dev/null +++ b/pkg/server/consts/consts.go @@ -0,0 +1,10 @@ +package consts + +const ( + // ContentTypeForm is the content type header for form encoded data + ContentTypeForm = "application/x-www-form-urlencoded" + // ContentTypeForm is the content type header for JSON encoded data + ContentTypeJSON = "application/json" + // ContentTypeHTML is the content type header for HTML + ContentTypeHTML = "text/html" +) diff --git a/pkg/server/context/user.go b/pkg/server/context/user.go new file mode 100644 index 00000000..81e6ba8c --- /dev/null +++ b/pkg/server/context/user.go @@ -0,0 +1,64 @@ +package context + +import ( + "context" + + "github.com/dnote/dnote/pkg/server/database" +) + +const ( + userKey privateKey = "user" + accountKey privateKey = "account" + tokenKey privateKey = "token" +) + +type privateKey string + +// WithUser creates a new context with the given user +func WithUser(ctx context.Context, user *database.User) context.Context { + return context.WithValue(ctx, userKey, user) +} + +// WithAccount creates a new context with the given account +func WithAccount(ctx context.Context, account *database.Account) context.Context { + return context.WithValue(ctx, accountKey, account) +} + +// WithToken creates a new context with the given user +func WithToken(ctx context.Context, tok *database.Token) context.Context { + return context.WithValue(ctx, tokenKey, tok) +} + +// User retrieves a user from the given context. It returns a pointer to +// a user. If the context does not contain a user, it returns nil. +func User(ctx context.Context) *database.User { + if temp := ctx.Value(userKey); temp != nil { + if user, ok := temp.(*database.User); ok { + return user + } + } + + return nil +} + +// Account retrieves an account from the given context. +func Account(ctx context.Context) *database.Account { + if temp := ctx.Value(accountKey); temp != nil { + if account, ok := temp.(*database.Account); ok { + return account + } + } + + return nil +} + +// Token retrieves a token from the given context. +func Token(ctx context.Context) *database.Token { + if temp := ctx.Value(tokenKey); temp != nil { + if tok, ok := temp.(*database.Token); ok { + return tok + } + } + + return nil +} diff --git a/pkg/server/controllers/books.go b/pkg/server/controllers/books.go new file mode 100644 index 00000000..5a937718 --- /dev/null +++ b/pkg/server/controllers/books.go @@ -0,0 +1,297 @@ +package controllers + +import ( + "fmt" + "net/http" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/context" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/helpers" + "github.com/dnote/dnote/pkg/server/presenters" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +// NewBooks creates a new Books controller. +// It panics if the necessary templates are not parsed. +func NewBooks(app *app.App) *Books { + return &Books{ + app: app, + } +} + +// Books is a user controller. +type Books struct { + app *app.App +} + +func (b *Books) getBooks(r *http.Request) ([]database.Book, error) { + user := context.User(r.Context()) + if user == nil { + return []database.Book{}, app.ErrLoginRequired + } + + conn := b.app.DB.Where("user_id = ? AND NOT deleted", user.ID).Order("label ASC") + + query := r.URL.Query() + 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) + } + + var books []database.Book + if err := conn.Find(&books).Error; err != nil { + return []database.Book{}, nil + } + + return books, nil +} + +// V3Index gets books +func (b *Books) V3Index(w http.ResponseWriter, r *http.Request) { + result, err := b.getBooks(r) + if err != nil { + handleJSONError(w, err, "getting books") + return + } + + respondJSON(w, http.StatusOK, presenters.PresentBooks(result)) +} + +// V3Show gets a book +func (b *Books) V3Show(w http.ResponseWriter, r *http.Request) { + user := context.User(r.Context()) + if user == nil { + handleJSONError(w, app.ErrLoginRequired, "login required") + return + } + + vars := mux.Vars(r) + bookUUID := vars["bookUUID"] + + if !helpers.ValidateUUID(bookUUID) { + handleJSONError(w, app.ErrInvalidUUID, "login required") + return + } + + var book database.Book + conn := b.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 { + handleJSONError(w, err, "finding the book") + return + } + + respondJSON(w, http.StatusOK, presenters.PresentBook(book)) +} + +type createBookPayload struct { + Name string `schema:"name" json:"name"` +} + +func validateCreateBookPayload(p createBookPayload) error { + if p.Name == "" { + return app.ErrBookNameRequired + } + + return nil +} + +func (b *Books) create(r *http.Request) (database.Book, error) { + user := context.User(r.Context()) + if user == nil { + return database.Book{}, app.ErrLoginRequired + } + + var params createBookPayload + if err := parseRequestData(r, ¶ms); err != nil { + return database.Book{}, errors.Wrap(err, "parsing request payload") + } + + if err := validateCreateBookPayload(params); err != nil { + return database.Book{}, errors.Wrap(err, "validating payload") + } + + var bookCount int + err := b.app.DB.Model(database.Book{}). + Where("user_id = ? AND label = ?", user.ID, params.Name). + Count(&bookCount).Error + if err != nil { + return database.Book{}, errors.Wrap(err, "checking duplicate") + } + if bookCount > 0 { + return database.Book{}, app.ErrDuplicateBook + } + + book, err := b.app.CreateBook(*user, params.Name) + if err != nil { + return database.Book{}, errors.Wrap(err, "inserting a book") + } + + return book, nil +} + +// CreateBookResp is the response from create book api +type CreateBookResp struct { + Book presenters.Book `json:"book"` +} + +// V3Create creates a book +func (b *Books) V3Create(w http.ResponseWriter, r *http.Request) { + result, err := b.create(r) + if err != nil { + handleJSONError(w, err, "creating a book") + return + } + + resp := CreateBookResp{ + Book: presenters.PresentBook(result), + } + respondJSON(w, http.StatusCreated, resp) +} + +type updateBookPayload struct { + Name *string `schema:"name" json:"name"` +} + +// UpdateBookResp is the response from create book api +type UpdateBookResp struct { + Book presenters.Book `json:"book"` +} + +func (b *Books) update(r *http.Request) (database.Book, error) { + user := context.User(r.Context()) + if user == nil { + return database.Book{}, app.ErrLoginRequired + } + + vars := mux.Vars(r) + uuid := vars["bookUUID"] + + if !helpers.ValidateUUID(uuid) { + return database.Book{}, app.ErrInvalidUUID + } + + tx := b.app.DB.Begin() + + var book database.Book + if err := tx.Where("user_id = ? AND uuid = ?", user.ID, uuid).First(&book).Error; err != nil { + return database.Book{}, errors.Wrap(err, "finding book") + } + + var params updateBookPayload + if err := parseRequestData(r, ¶ms); err != nil { + return database.Book{}, errors.Wrap(err, "decoding payload") + } + + book, err := b.app.UpdateBook(tx, *user, book, params.Name) + if err != nil { + tx.Rollback() + return database.Book{}, errors.Wrap(err, "updating a book") + } + + tx.Commit() + + return book, nil +} + +// V3Update updates a book +func (b *Books) V3Update(w http.ResponseWriter, r *http.Request) { + book, err := b.update(r) + if err != nil { + handleJSONError(w, err, "updating a book") + return + } + + resp := UpdateBookResp{ + Book: presenters.PresentBook(book), + } + respondJSON(w, http.StatusOK, resp) +} + +func (b *Books) del(r *http.Request) (database.Book, error) { + user := context.User(r.Context()) + if user == nil { + return database.Book{}, app.ErrLoginRequired + } + + vars := mux.Vars(r) + uuid := vars["bookUUID"] + + if !helpers.ValidateUUID(uuid) { + return database.Book{}, app.ErrInvalidUUID + } + + tx := b.app.DB.Begin() + + var book database.Book + if err := tx.Where("user_id = ? AND uuid = ?", user.ID, uuid).First(&book).Error; err != nil { + return database.Book{}, errors.Wrap(err, "finding a book") + } + + var notes []database.Note + if err := tx.Where("book_uuid = ? AND NOT deleted", uuid).Order("usn ASC").Find(¬es).Error; err != nil { + return database.Book{}, errors.Wrap(err, "finding notes for the book") + } + + for _, note := range notes { + if _, err := b.app.DeleteNote(tx, *user, note); err != nil { + tx.Rollback() + return database.Book{}, errors.Wrap(err, "deleting a note in the book") + } + } + + book, err := b.app.DeleteBook(tx, *user, book) + if err != nil { + return database.Book{}, errors.Wrap(err, "deleting the book") + } + + tx.Commit() + + return book, nil +} + +// deleteBookResp is the response from create book api +type deleteBookResp struct { + Status int `json:"status"` + Book presenters.Book `json:"book"` +} + +// Delete updates a book +func (b *Books) V3Delete(w http.ResponseWriter, r *http.Request) { + book, err := b.del(r) + if err != nil { + handleJSONError(w, err, "creating a books") + return + } + + resp := deleteBookResp{ + Status: http.StatusOK, + Book: presenters.PresentBook(book), + } + respondJSON(w, http.StatusOK, resp) +} + +// IndexOptions is a handler for OPTIONS endpoint for notes +func (b *Books) IndexOptions(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Methods", "GET, POST") + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version") +} diff --git a/pkg/server/api/v3_books_test.go b/pkg/server/controllers/books_test.go similarity index 65% rename from pkg/server/api/v3_books_test.go rename to pkg/server/controllers/books_test.go index 4cd6d579..5c177f70 100644 --- a/pkg/server/api/v3_books_test.go +++ b/pkg/server/controllers/books_test.go @@ -16,17 +16,19 @@ * along with Dnote. If not, see . */ -package api +package controllers import ( "encoding/json" "fmt" + "io/ioutil" "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/database" "github.com/dnote/dnote/pkg/server/presenters" "github.com/dnote/dnote/pkg/server/testutils" @@ -34,18 +36,21 @@ import ( ) func TestGetBooks(t *testing.T) { - 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.SetupAccountData(user, "alice@test.com", "pass1234") anotherUser := testutils.SetupUserData() + testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234") b1 := database.Book{ UserID: user.ID, @@ -77,7 +82,9 @@ func TestGetBooks(t *testing.T) { testutils.MustExec(t, testutils.DB.Save(&b4), "preparing b4") // Execute - req := testutils.MakeReq(server.URL, "GET", "/v3/books", "") + endpoint := "/api/v3/books" + + req := testutils.MakeReq(server.URL, "GET", endpoint, "") res := testutils.HTTPAuthDo(t, req, user) // Test @@ -114,19 +121,21 @@ func TestGetBooks(t *testing.T) { } func TestGetBooksByName(t *testing.T) { - 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.SetupAccountData(user, "alice@test.com", "pass1234") anotherUser := testutils.SetupUserData() - req := testutils.MakeReq(server.URL, "GET", "/v3/books?name=js", "") + testutils.SetupAccountData(anotherUser, "bob@test.com", "pass1234") b1 := database.Book{ UserID: user.ID, @@ -145,6 +154,9 @@ func TestGetBooksByName(t *testing.T) { 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 @@ -171,6 +183,315 @@ func TestGetBooksByName(t *testing.T) { assert.DeepEqual(t, payload, expected, "payload mismatch") } +func TestGetBook(t *testing.T) { + 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.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 := fmt.Sprintf("/api/v3/books/%s", b1.UUID) + 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 TestGetBookNonOwner(t *testing.T) { + 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.SetupAccountData(user, "alice@test.com", "pass1234") + nonOwner := testutils.SetupUserData() + testutils.SetupAccountData(nonOwner, "bob@test.com", "pass1234") + + b1 := database.Book{ + UserID: user.ID, + Label: "js", + } + testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1") + + // Execute + endpoint := fmt.Sprintf("/api/v3/books/%s", b1.UUID) + req := testutils.MakeReq(server.URL, "GET", endpoint, "") + res := testutils.HTTPAuthDo(t, req, nonOwner) + + // 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), "", "payload mismatch") +} + +func TestCreateBook(t *testing.T) { + t.Run("success", func(t *testing.T) { + 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.SetupAccountData(user, "alice@test.com", "pass1234") + testutils.MustExec(t, testutils.DB.Model(&user).Update("max_usn", 101), "preparing user max_usn") + + req := testutils.MakeReq(server.URL, "POST", "/api/v3/books", `{"name": "js"}`) + + // Execute + res := testutils.HTTPAuthDo(t, req, user) + + // Test + assert.StatusCodeEquals(t, res, http.StatusCreated, "") + + 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(&bookRecord), "finding book") + testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record") + + maxUSN := 102 + + assert.Equalf(t, bookCount, 1, "book count mismatch") + assert.Equalf(t, noteCount, 0, "note count mismatch") + + assert.NotEqual(t, bookRecord.UUID, "", "book uuid should have been generated") + assert.Equal(t, bookRecord.Label, "js", "book name mismatch") + assert.Equal(t, bookRecord.UserID, user.ID, "book user_id mismatch") + assert.Equal(t, bookRecord.USN, maxUSN, "book user_id mismatch") + assert.Equal(t, userRecord.MaxUSN, maxUSN, "user max_usn 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") + }) + + t.Run("duplicate", func(t *testing.T) { + 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.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 book data") + + // Execute + req := testutils.MakeReq(server.URL, "POST", "/api/v3/books", `{"name": "js"}`) + res := testutils.HTTPAuthDo(t, req, user) + + // Test + assert.StatusCodeEquals(t, res, http.StatusConflict, "") + + var bookRecord database.Book + var bookCount, noteCount int + var userRecord database.User + 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(&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, 0, "note count mismatch") + + assert.Equal(t, bookRecord.Label, "js", "book name mismatch") + assert.Equal(t, bookRecord.UserID, user.ID, "book user_id mismatch") + assert.Equal(t, bookRecord.USN, b1.USN, "book usn mismatch") + assert.Equal(t, userRecord.MaxUSN, 101, "user max_usn mismatch") + }) +} + +func TestUpdateBook(t *testing.T) { + updatedLabel := "updated-label" + + b1UUID := "ead8790f-aff9-4bdf-8eec-f734ccd29202" + b2UUID := "0ecaac96-8d72-4e04-8925-5a21b79a16da" + + type payloadData struct { + Name *string `schema:"name" json:"name,omitempty"` + } + + testCases := []struct { + payload testutils.PayloadWrapper + bookUUID string + bookDeleted bool + bookLabel string + expectedBookLabel string + }{ + { + payload: testutils.PayloadWrapper{ + Data: payloadData{ + Name: &updatedLabel, + }, + }, + bookUUID: b1UUID, + bookDeleted: false, + bookLabel: "original-label", + expectedBookLabel: updatedLabel, + }, + // if a deleted book is updated, it should be un-deleted + { + payload: testutils.PayloadWrapper{ + Data: payloadData{ + Name: &updatedLabel, + }, + }, + bookUUID: b1UUID, + bookDeleted: true, + bookLabel: "", + expectedBookLabel: updatedLabel, + }, + } + + 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(), + 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{ + UUID: tc.bookUUID, + UserID: user.ID, + Label: tc.bookLabel, + Deleted: tc.bookDeleted, + } + 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") + + // Execute + 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 + assert.StatusCodeEquals(t, res, http.StatusOK, fmt.Sprintf("status code mismatch for test case %d", idx)) + + var bookRecord database.Book + 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("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, 0, "note count mismatch") + + assert.Equalf(t, bookRecord.UUID, tc.bookUUID, "book uuid mismatch") + assert.Equalf(t, bookRecord.Label, tc.expectedBookLabel, "book label mismatch") + assert.Equalf(t, bookRecord.USN, 102, "book usn mismatch") + assert.Equalf(t, bookRecord.Deleted, false, "book Deleted mismatch") + + assert.Equal(t, userRecord.MaxUSN, 102, fmt.Sprintf("user max_usn mismatch for test case %d", idx)) + }) + } +} + func TestDeleteBook(t *testing.T) { testCases := []struct { label string @@ -200,19 +521,22 @@ func TestDeleteBook(t *testing.T) { 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(), + 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", 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{ @@ -283,12 +607,10 @@ func TestDeleteBook(t *testing.T) { } testutils.MustExec(t, testutils.DB.Save(&n5), "preparing a note data") - endpoint := fmt.Sprintf("/v3/books/%s", b2.UUID) - req := testutils.MakeReq(server.URL, "DELETE", endpoint, "") - req.Header.Set("Version", "0.1.1") - req.Header.Set("Origin", "chrome-extension://iaolnfnipkoinabdbbakcmkkdignedce") - // Execute + endpoint := fmt.Sprintf("/api/v3/books/%s", b2.UUID) + + req := testutils.MakeReq(server.URL, "DELETE", endpoint, "") res := testutils.HTTPAuthDo(t, req, user) // Test @@ -349,200 +671,3 @@ func TestDeleteBook(t *testing.T) { }) } } - -func TestCreateBook(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") - - req := testutils.MakeReq(server.URL, "POST", "/v3/books", `{"name": "js"}`) - req.Header.Set("Version", "0.1.1") - req.Header.Set("Origin", "chrome-extension://iaolnfnipkoinabdbbakcmkkdignedce") - - // Execute - res := testutils.HTTPAuthDo(t, req, user) - - // Test - assert.StatusCodeEquals(t, res, http.StatusCreated, "") - - 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(&bookRecord), "finding book") - testutils.MustExec(t, testutils.DB.Where("id = ?", user.ID).First(&userRecord), "finding user record") - - maxUSN := 102 - - assert.Equalf(t, bookCount, 1, "book count mismatch") - assert.Equalf(t, noteCount, 0, "note count mismatch") - - assert.NotEqual(t, bookRecord.UUID, "", "book uuid should have been generated") - assert.Equal(t, bookRecord.Label, "js", "book name mismatch") - assert.Equal(t, bookRecord.UserID, user.ID, "book user_id mismatch") - assert.Equal(t, bookRecord.USN, maxUSN, "book user_id mismatch") - assert.Equal(t, userRecord.MaxUSN, maxUSN, "user max_usn mismatch") - - var got CreateBookResp - if err := json.NewDecoder(res.Body).Decode(&got); err != nil { - t.Fatal(errors.Wrap(err, "decoding got")) - } - 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") -} - -func TestCreateBookDuplicate(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 book data") - - // Execute - req := testutils.MakeReq(server.URL, "POST", "/v3/books", `{"name": "js"}`) - res := testutils.HTTPAuthDo(t, req, user) - - // Test - assert.StatusCodeEquals(t, res, http.StatusConflict, "") - - var bookRecord database.Book - var bookCount, noteCount int - var userRecord database.User - 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(&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, 0, "note count mismatch") - - assert.Equal(t, bookRecord.Label, "js", "book name mismatch") - assert.Equal(t, bookRecord.UserID, user.ID, "book user_id mismatch") - assert.Equal(t, bookRecord.USN, b1.USN, "book usn mismatch") - assert.Equal(t, userRecord.MaxUSN, 101, "user max_usn mismatch") -} - -func TestUpdateBook(t *testing.T) { - updatedLabel := "updated-label" - - b1UUID := "ead8790f-aff9-4bdf-8eec-f734ccd29202" - b2UUID := "0ecaac96-8d72-4e04-8925-5a21b79a16da" - - testCases := []struct { - payload string - bookUUID string - bookDeleted bool - bookLabel string - expectedBookLabel string - }{ - { - payload: fmt.Sprintf(`{ - "name": "%s" - }`, updatedLabel), - bookUUID: b1UUID, - bookDeleted: false, - bookLabel: "original-label", - expectedBookLabel: updatedLabel, - }, - // if a deleted book is updated, it should be un-deleted - { - payload: fmt.Sprintf(`{ - "name": "%s" - }`, updatedLabel), - bookUUID: b1UUID, - bookDeleted: true, - bookLabel: "", - expectedBookLabel: updatedLabel, - }, - } - - for idx, tc := range testCases { - func() { - - 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: tc.bookUUID, - UserID: user.ID, - Label: tc.bookLabel, - Deleted: tc.bookDeleted, - } - 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") - - // Executdb,e - endpoint := fmt.Sprintf("/v3/books/%s", tc.bookUUID) - req := testutils.MakeReq(server.URL, "PATCH", endpoint, tc.payload) - res := testutils.HTTPAuthDo(t, req, user) - - // Test - assert.StatusCodeEquals(t, res, http.StatusOK, fmt.Sprintf("status code mismatch for test case %d", idx)) - - var bookRecord database.Book - 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("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, 0, "note count mismatch") - - assert.Equalf(t, bookRecord.UUID, tc.bookUUID, "book uuid mismatch") - assert.Equalf(t, bookRecord.Label, tc.expectedBookLabel, "book label mismatch") - assert.Equalf(t, bookRecord.USN, 102, "book usn mismatch") - assert.Equalf(t, bookRecord.Deleted, false, "book Deleted mismatch") - - assert.Equal(t, userRecord.MaxUSN, 102, fmt.Sprintf("user max_usn mismatch for test case %d", idx)) - }() - } -} diff --git a/pkg/server/controllers/controllers.go b/pkg/server/controllers/controllers.go new file mode 100644 index 00000000..5786991b --- /dev/null +++ b/pkg/server/controllers/controllers.go @@ -0,0 +1,32 @@ +package controllers + +import ( + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/log" +) + +// Controllers is a group of controllers +type Controllers struct { + Users *Users + Notes *Notes + Books *Books + Sync *Sync + Static *Static + Health *Health +} + +// New returns a new group of controllers +func New(app *app.App, baseDir string) *Controllers { + log.Info(app.Config.PageTemplateDir) + + c := Controllers{} + + c.Users = NewUsers(app, baseDir) + c.Notes = NewNotes(app) + c.Books = NewBooks(app) + c.Sync = NewSync(app) + c.Static = NewStatic(app, baseDir) + c.Health = NewHealth(app) + + return &c +} diff --git a/pkg/server/controllers/health.go b/pkg/server/controllers/health.go new file mode 100644 index 00000000..0e0608d6 --- /dev/null +++ b/pkg/server/controllers/health.go @@ -0,0 +1,23 @@ +package controllers + +import ( + "net/http" + + "github.com/dnote/dnote/pkg/server/app" +) + +// NewHealth creates a new Health controller. +// It panics if the necessary templates are not parsed. +func NewHealth(app *app.App) *Health { + return &Health{} +} + +// Health is a health controller. +type Health struct { +} + +// Index handles GET / +func (n *Health) Index(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) +} diff --git a/pkg/server/api/health_test.go b/pkg/server/controllers/health_test.go similarity index 80% rename from pkg/server/api/health_test.go rename to pkg/server/controllers/health_test.go index 0deadc5e..9e5b53b8 100644 --- a/pkg/server/api/health_test.go +++ b/pkg/server/controllers/health_test.go @@ -16,29 +16,30 @@ * along with Dnote. If not, see . */ -package api +package controllers import ( "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/testutils" - "github.com/jinzhu/gorm" ) -func TestCheckHealth(t *testing.T) { - // Setup +func TestHealth(t *testing.T) { + defer testutils.ClearData(testutils.DB) + server := MustNewServer(t, &app.App{ - DB: &gorm.DB{}, - Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, }) defer server.Close() // Execute - req := testutils.MakeReq(server.URL, "GET", "/health", "") + req := testutils.MakeReq(server.URL, "GET", "/api/health", "") res := testutils.HTTPDo(t, req) // Test diff --git a/pkg/server/controllers/helpers.go b/pkg/server/controllers/helpers.go new file mode 100644 index 00000000..7a3cb9b5 --- /dev/null +++ b/pkg/server/controllers/helpers.go @@ -0,0 +1,315 @@ +package controllers + +import ( + "encoding/json" + "net/http" + "net/url" + "strings" + "time" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/consts" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/log" + "github.com/dnote/dnote/pkg/server/views" + "github.com/gorilla/schema" + "github.com/pkg/errors" +) + +func parseRequestData(r *http.Request, dst interface{}) error { + ct := r.Header.Get("Content-Type") + + if ct == consts.ContentTypeForm { + if err := parseForm(r, dst); err != nil { + return errors.Wrap(err, "parsing form") + } + + return nil + } + + // default to JSON + if err := parseJSON(r, dst); err != nil { + return errors.Wrap(err, "parsing JSON") + } + + return nil +} + +func parseForm(r *http.Request, dst interface{}) error { + if err := r.ParseForm(); err != nil { + return err + } + + return parseValues(r.PostForm, dst) +} + +func parseURLParams(r *http.Request, dst interface{}) error { + if err := r.ParseForm(); err != nil { + return err + } + return parseValues(r.Form, dst) +} + +func parseValues(values url.Values, dst interface{}) error { + dec := schema.NewDecoder() + + // Ignore CSRF token field + dec.IgnoreUnknownKeys(true) + + if err := dec.Decode(dst, values); err != nil { + return err + } + + return nil +} + +func parseJSON(r *http.Request, dst interface{}) error { + dec := json.NewDecoder(r.Body) + + if err := dec.Decode(dst); err != nil { + return err + } + + return nil +} + +// GetCredential extracts a session key from the request from the request header. Concretely, +// it first looks at the 'Cookie' and then the 'Authorization' header. If no credential is found, +// it returns an empty string. +func GetCredential(r *http.Request) (string, error) { + ret, err := getSessionKeyFromCookie(r) + if err != nil { + return "", errors.Wrap(err, "getting session key from cookie") + } + if ret != "" { + return ret, nil + } + + ret, err = getSessionKeyFromAuth(r) + if err != nil { + return "", errors.Wrap(err, "getting session key from Authorization header") + } + + return ret, nil +} + +// getSessionKeyFromCookie reads and returns a session key from the cookie sent by the +// request. If no session key is found, it returns an empty string +func getSessionKeyFromCookie(r *http.Request) (string, error) { + c, err := r.Cookie("id") + + if err == http.ErrNoCookie { + return "", nil + } else if err != nil { + return "", errors.Wrap(err, "reading cookie") + } + + return c.Value, nil +} + +// getSessionKeyFromAuth reads and returns a session key from the Authorization header +func getSessionKeyFromAuth(r *http.Request) (string, error) { + h := r.Header.Get("Authorization") + if h == "" { + return "", nil + } + + payload, err := parseAuthHeader(h) + if err != nil { + return "", errors.Wrap(err, "parsing the authorization header") + } + if payload.scheme != "Bearer" { + return "", errors.New("unsupported scheme") + } + + return payload.credential, nil +} + +func parseAuthHeader(h string) (authHeader, error) { + parts := strings.Split(h, " ") + + if len(parts) != 2 { + return authHeader{}, errors.New("Invalid authorization header") + } + + parsed := authHeader{ + scheme: parts[0], + credential: parts[1], + } + + return parsed, nil +} + +type authHeader struct { + scheme string + credential string +} + +const ( + sessionCookieName = "id" + sessionCookiePath = "/" +) + +func setSessionCookie(w http.ResponseWriter, key string, expires time.Time) { + cookie := http.Cookie{ + Name: sessionCookieName, + Value: key, + Expires: expires, + Path: sessionCookiePath, + HttpOnly: true, + } + http.SetCookie(w, &cookie) +} + +func unsetSessionCookie(w http.ResponseWriter) { + expires := time.Now().Add(time.Hour * -24 * 30) + cookie := http.Cookie{ + Name: sessionCookieName, + Value: "", + Expires: expires, + Path: sessionCookiePath, + HttpOnly: true, + } + + w.Header().Set("Cache-Control", "no-cache") + http.SetCookie(w, &cookie) +} + +// SessionResponse is a response containing a session information +type SessionResponse struct { + Key string `json:"key"` + ExpiresAt int64 `json:"expires_at"` +} + +func logError(err error, msg string) { + // log if internal error + // if _, ok := err.(views.PublicError); !ok { + // log.ErrorWrap(err, msg) + // } + log.ErrorWrap(err, msg) +} + +func getStatusCode(err error) int { + rootErr := errors.Cause(err) + + switch rootErr { + case app.ErrNotFound: + return http.StatusNotFound + case app.ErrLoginInvalid: + return http.StatusUnauthorized + case app.ErrDuplicateEmail, app.ErrEmailRequired, app.ErrPasswordTooShort: + return http.StatusBadRequest + case app.ErrLoginRequired: + return http.StatusUnauthorized + case app.ErrBookUUIDRequired: + return http.StatusBadRequest + case app.ErrEmptyUpdate: + return http.StatusBadRequest + case app.ErrInvalidUUID: + return http.StatusBadRequest + case app.ErrDuplicateBook: + return http.StatusConflict + case app.ErrInvalidToken: + return http.StatusBadRequest + case app.ErrPasswordResetTokenExpired: + return http.StatusGone + case app.ErrPasswordConfirmationMismatch: + return http.StatusBadRequest + case app.ErrInvalidPasswordChangeInput: + return http.StatusBadRequest + case app.ErrInvalidPassword: + return http.StatusUnauthorized + case app.ErrEmailTooLong: + return http.StatusBadRequest + case app.ErrEmailAlreadyVerified: + return http.StatusConflict + case app.ErrMissingToken: + return http.StatusBadRequest + case app.ErrExpiredToken: + return http.StatusGone + } + + return http.StatusInternalServerError +} + +// handleHTMLError writes the error to the log and sets the error message in the data. +func handleHTMLError(w http.ResponseWriter, r *http.Request, err error, msg string, v *views.View, d views.Data) { + statusCode := getStatusCode(err) + + logError(err, msg) + + d.SetAlert(err, v.AlertInBody) + v.Render(w, r, &d, statusCode) +} + +// handleJSONError logs the error and responds with the given status code with a generic status text +func handleJSONError(w http.ResponseWriter, err error, msg string) { + statusCode := getStatusCode(err) + + rootErr := errors.Cause(err) + + var respText string + if pErr, ok := rootErr.(views.PublicError); ok { + respText = pErr.Public() + } else { + respText = http.StatusText(statusCode) + } + + logError(err, msg) + http.Error(w, respText, statusCode) +} + +// 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 respondWithSession(w http.ResponseWriter, statusCode int, session *database.Session) { + setSessionCookie(w, session.Key, session.ExpiresAt) + + response := SessionResponse{ + Key: session.Key, + ExpiresAt: session.ExpiresAt.Unix(), + } + + w.Header().Set("Content-Type", "application/json") + + dat, err := json.Marshal(response) + if err != nil { + handleJSONError(w, err, "encoding response") + return + } + + w.WriteHeader(statusCode) + w.Write(dat) +} + +// respondJSON encodes the given payload into a JSON format and writes it to the given response writer +func respondJSON(w http.ResponseWriter, statusCode int, payload interface{}) { + w.Header().Set("Content-Type", "application/json") + + dat, err := json.Marshal(payload) + if err != nil { + handleJSONError(w, err, "encoding response") + return + } + + w.WriteHeader(statusCode) + w.Write(dat) +} + +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" +} diff --git a/pkg/server/job/remind/main_test.go b/pkg/server/controllers/main_test.go similarity index 97% rename from pkg/server/job/remind/main_test.go rename to pkg/server/controllers/main_test.go index d8b6b62a..286b94ee 100644 --- a/pkg/server/job/remind/main_test.go +++ b/pkg/server/controllers/main_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package remind +package controllers import ( "os" diff --git a/pkg/server/controllers/notes.go b/pkg/server/controllers/notes.go new file mode 100644 index 00000000..72d41817 --- /dev/null +++ b/pkg/server/controllers/notes.go @@ -0,0 +1,446 @@ +package controllers + +import ( + "math" + "net/http" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/context" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/operations" + "github.com/dnote/dnote/pkg/server/presenters" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +// NewNotes creates a new Notes controller. +// It panics if the necessary templates are not parsed. +func NewNotes(app *app.App) *Notes { + return &Notes{ + app: app, + } +} + +var notesPerPage = 30 + +// Notes is a user controller. +type Notes struct { + app *app.App +} + +// escapeSearchQuery escapes the query for full text search +func escapeSearchQuery(searchQuery string) string { + return strings.Join(strings.Fields(searchQuery), "&") +} + +func parseSearchQuery(q url.Values) string { + searchStr := q.Get("q") + + return escapeSearchQuery(searchStr) +} + +func parsePageQuery(q url.Values) (int, error) { + pageStr := q.Get("page") + if len(pageStr) == 0 { + return 1, nil + } + + p, err := strconv.Atoi(pageStr) + return p, err +} + +func parseGetNotesQuery(q url.Values) (app.GetNotesParams, error) { + yearStr := q.Get("year") + monthStr := q.Get("month") + books := q["book"] + encryptedStr := q.Get("encrypted") + pageStr := q.Get("page") + + page, err := parsePageQuery(q) + if err != nil { + return app.GetNotesParams{}, errors.Errorf("invalid page %s", pageStr) + } + if page < 1 { + return app.GetNotesParams{}, errors.Errorf("invalid page %s", pageStr) + } + + var year int + if len(yearStr) > 0 { + y, err := strconv.Atoi(yearStr) + if err != nil { + return app.GetNotesParams{}, errors.Errorf("invalid year %s", yearStr) + } + + year = y + } + + var month int + if len(monthStr) > 0 { + m, err := strconv.Atoi(monthStr) + if err != nil { + return app.GetNotesParams{}, errors.Errorf("invalid month %s", monthStr) + } + if m < 1 || m > 12 { + return app.GetNotesParams{}, errors.Errorf("invalid month %s", monthStr) + } + + month = m + } + + var encrypted bool + if strings.ToLower(encryptedStr) == "true" { + encrypted = true + } else { + encrypted = false + } + + ret := app.GetNotesParams{ + Year: year, + Month: month, + Page: page, + Search: parseSearchQuery(q), + Books: books, + Encrypted: encrypted, + PerPage: notesPerPage, + } + + return ret, nil +} + +func (n *Notes) getNotes(r *http.Request) (app.GetNotesResult, app.GetNotesParams, error) { + user := context.User(r.Context()) + if user == nil { + return app.GetNotesResult{}, app.GetNotesParams{}, app.ErrLoginRequired + } + + query := r.URL.Query() + p, err := parseGetNotesQuery(query) + if err != nil { + return app.GetNotesResult{}, app.GetNotesParams{}, errors.Wrap(err, "parsing query") + } + + res, err := n.app.GetNotes(user.ID, p) + if err != nil { + return app.GetNotesResult{}, app.GetNotesParams{}, errors.Wrap(err, "getting notes") + } + + return res, p, nil +} + +type noteGroup struct { + Year int + Month int + Data []database.Note +} + +type bucketKey struct { + year int + month time.Month +} + +func groupNotes(notes []database.Note) []noteGroup { + ret := []noteGroup{} + + buckets := map[bucketKey][]database.Note{} + + for _, note := range notes { + year := note.UpdatedAt.Year() + month := note.UpdatedAt.Month() + key := bucketKey{year, month} + + if _, ok := buckets[key]; !ok { + buckets[key] = []database.Note{} + } + + buckets[key] = append(buckets[key], note) + } + + keys := []bucketKey{} + for key := range buckets { + keys = append(keys, key) + } + + sort.Slice(keys, func(i, j int) bool { + yearI := keys[i].year + yearJ := keys[j].year + monthI := keys[i].month + monthJ := keys[j].month + + if yearI == yearJ { + return monthI < monthJ + } + + return yearI < yearJ + }) + + for _, key := range keys { + group := noteGroup{ + Year: key.year, + Month: int(key.month), + Data: buckets[key], + } + ret = append(ret, group) + } + + return ret +} + +func getMaxPage(page, total int) int { + tmp := float64(total) / float64(notesPerPage) + return int(math.Ceil(tmp)) +} + +// GetNotesResponse is a reponse by getNotesHandler +type GetNotesResponse struct { + Notes []presenters.Note `json:"notes"` + Total int `json:"total"` +} + +// V3Index is a v3 handler for getting notes +func (n *Notes) V3Index(w http.ResponseWriter, r *http.Request) { + result, _, err := n.getNotes(r) + if err != nil { + handleJSONError(w, err, "getting notes") + return + } + + respondJSON(w, http.StatusOK, GetNotesResponse{ + Notes: presenters.PresentNotes(result.Notes), + Total: result.Total, + }) +} + +func (n *Notes) getNote(r *http.Request) (database.Note, error) { + user := context.User(r.Context()) + + vars := mux.Vars(r) + noteUUID := vars["noteUUID"] + + note, ok, err := operations.GetNote(n.app.DB, noteUUID, user) + if !ok { + return database.Note{}, app.ErrNotFound + } + if err != nil { + return database.Note{}, errors.Wrap(err, "finding note") + } + + return note, nil +} + +// V3Show is api for show +func (n *Notes) V3Show(w http.ResponseWriter, r *http.Request) { + note, err := n.getNote(r) + if err != nil { + handleJSONError(w, err, "getting note") + return + } + + respondJSON(w, http.StatusOK, presenters.PresentNote(note)) +} + +type createNotePayload struct { + BookUUID string `schema:"book_uuid" json:"book_uuid"` + Content string `schema:"content" json:"content"` + AddedOn *int64 `schema:"added_on" json:"added_on"` + EditedOn *int64 `schema:"edited_on" json:"edited_on"` +} + +func validateCreateNotePayload(p createNotePayload) error { + if p.BookUUID == "" { + return app.ErrBookUUIDRequired + } + + return nil +} + +func (n *Notes) create(r *http.Request) (database.Note, error) { + user := context.User(r.Context()) + if user == nil { + return database.Note{}, app.ErrLoginRequired + } + + var params createNotePayload + if err := parseRequestData(r, ¶ms); err != nil { + return database.Note{}, errors.Wrap(err, "parsing request payload") + } + + if err := validateCreateNotePayload(params); err != nil { + return database.Note{}, err + } + + var book database.Book + if err := n.app.DB.Where("uuid = ? AND user_id = ?", params.BookUUID, user.ID).First(&book).Error; err != nil { + return database.Note{}, errors.Wrap(err, "finding book") + } + + client := getClientType(r) + note, err := n.app.CreateNote(*user, params.BookUUID, params.Content, params.AddedOn, params.EditedOn, false, client) + if err != nil { + return database.Note{}, errors.Wrap(err, "creating note") + } + + // preload associations + note.User = *user + note.Book = book + + return note, nil +} + +func (n *Notes) del(r *http.Request) (database.Note, error) { + vars := mux.Vars(r) + noteUUID := vars["noteUUID"] + + user := context.User(r.Context()) + if user == nil { + return database.Note{}, app.ErrLoginRequired + } + + var note database.Note + if err := n.app.DB.Where("uuid = ? AND user_id = ?", noteUUID, user.ID).Preload("Book").First(¬e).Error; err != nil { + return database.Note{}, errors.Wrap(err, "finding note") + } + + tx := n.app.DB.Begin() + + note, err := n.app.DeleteNote(tx, *user, note) + if err != nil { + tx.Rollback() + return database.Note{}, errors.Wrap(err, "deleting note") + } + + tx.Commit() + + return note, nil +} + +// CreateNoteResp is a response for creating a note +type CreateNoteResp struct { + Result presenters.Note `json:"result"` +} + +// V3Create creates note +func (n *Notes) V3Create(w http.ResponseWriter, r *http.Request) { + note, err := n.create(r) + if err != nil { + handleJSONError(w, err, "creating note") + return + } + + respondJSON(w, http.StatusCreated, CreateNoteResp{ + Result: presenters.PresentNote(note), + }) +} + +type DeleteNoteResp struct { + Status int `json:"status"` + Result presenters.Note `json:"result"` +} + +// V3Delete deletes note +func (n *Notes) V3Delete(w http.ResponseWriter, r *http.Request) { + note, err := n.del(r) + if err != nil { + handleJSONError(w, err, "deleting note") + return + } + + respondJSON(w, http.StatusOK, DeleteNoteResp{ + Status: http.StatusNoContent, + Result: presenters.PresentNote(note), + }) +} + +type updateNotePayload struct { + BookUUID *string `schema:"book_uuid" json:"book_uuid"` + Content *string `schema:"content" json:"content"` + Public *bool `schema:"public" json:"public"` +} + +func validateUpdateNotePayload(p updateNotePayload) error { + if p.BookUUID == nil && p.Content == nil && p.Public == nil { + return app.ErrEmptyUpdate + } + + return nil +} + +func (n *Notes) update(r *http.Request) (database.Note, error) { + vars := mux.Vars(r) + noteUUID := vars["noteUUID"] + + user := context.User(r.Context()) + if user == nil { + return database.Note{}, app.ErrLoginRequired + } + + var params updateNotePayload + err := parseRequestData(r, ¶ms) + if err != nil { + return database.Note{}, errors.Wrap(err, "decoding params") + } + + if err := validateUpdateNotePayload(params); err != nil { + return database.Note{}, err + } + + var note database.Note + if err := n.app.DB.Where("uuid = ? AND user_id = ?", noteUUID, user.ID).First(¬e).Error; err != nil { + return database.Note{}, errors.Wrap(err, "finding note") + } + + tx := n.app.DB.Begin() + + note, err = n.app.UpdateNote(tx, *user, note, &app.UpdateNoteParams{ + BookUUID: params.BookUUID, + Content: params.Content, + Public: params.Public, + }) + if err != nil { + tx.Rollback() + return database.Note{}, errors.Wrap(err, "updating note") + } + + var book database.Book + if err := tx.Where("uuid = ? AND user_id = ?", note.BookUUID, user.ID).First(&book).Error; err != nil { + tx.Rollback() + return database.Note{}, errors.Wrapf(err, "finding book %s to preload", note.BookUUID) + } + + tx.Commit() + + // preload associations + note.User = *user + note.Book = book + + return note, nil +} + +type updateNoteResp struct { + Status int `json:"status"` + Result presenters.Note `json:"result"` +} + +// V3Update updates a note +func (n *Notes) V3Update(w http.ResponseWriter, r *http.Request) { + note, err := n.update(r) + if err != nil { + handleJSONError(w, err, "updating note") + return + } + + respondJSON(w, http.StatusOK, updateNoteResp{ + Status: http.StatusOK, + Result: presenters.PresentNote(note), + }) +} + +// IndexOptions is a handler for OPTIONS endpoint for notes +func (n *Notes) IndexOptions(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/controllers/notes_test.go b/pkg/server/controllers/notes_test.go new file mode 100644 index 00000000..c6fa1ffe --- /dev/null +++ b/pkg/server/controllers/notes_test.go @@ -0,0 +1,770 @@ +/* Copyright (C) 2019, 2020 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 controllers + +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/config" + "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(), + 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) { + 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") + + 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 deletedNote") + + getURL := func(noteUUID string) string { + return fmt.Sprintf("/api/v3/notes/%s", noteUUID) + } + + t.Run("owner accessing private note", func(t *testing.T) { + // Execute + url := getURL(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("owner accessing public note", func(t *testing.T) { + // Execute + url := getURL(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 := getURL(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 := getURL(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 := getURL(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 := getURL(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 := getURL("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 := getURL(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") + }) +} + +func TestCreateNote(t *testing.T) { + 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.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) { + 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 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(), + 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", 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("/api/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") + }) + } +} + +func TestUpdateNote(t *testing.T) { + updatedBody := "some updated content" + + b1UUID := "37868a8e-a844-4265-9a4f-0be598084733" + b2UUID := "8f3bd424-6aa5-4ed5-910d-e5b38ab09f8c" + + type payloadData struct { + Content *string `schema:"content" json:"content,omitempty"` + BookUUID *string `schema:"book_uuid" json:"book_uuid,omitempty"` + Public *bool `schema:"public" json:"public,omitempty"` + } + + testCases := []struct { + payload testutils.PayloadWrapper + noteUUID string + noteBookUUID string + noteBody string + notePublic bool + noteDeleted bool + expectedNoteBody string + expectedNoteBookName string + expectedNoteBookUUID string + expectedNotePublic bool + }{ + { + payload: testutils.PayloadWrapper{ + Data: payloadData{ + Content: &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: testutils.PayloadWrapper{ + Data: payloadData{ + BookUUID: &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: testutils.PayloadWrapper{ + Data: payloadData{ + BookUUID: &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: testutils.PayloadWrapper{ + Data: payloadData{ + BookUUID: &b2UUID, + Content: &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: testutils.PayloadWrapper{ + Data: payloadData{ + BookUUID: &b1UUID, + Content: &updatedBody, + }, + }, + noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053", + noteBookUUID: b1UUID, + notePublic: false, + noteBody: "", + noteDeleted: true, + expectedNoteBookUUID: b1UUID, + expectedNoteBody: updatedBody, + expectedNoteBookName: "js", + expectedNotePublic: false, + }, + { + payload: testutils.PayloadWrapper{ + Data: payloadData{ + Public: &testutils.TrueVal, + }, + }, + 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: testutils.PayloadWrapper{ + Data: payloadData{ + Public: &testutils.FalseVal, + }, + }, + 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: testutils.PayloadWrapper{ + Data: payloadData{ + Content: &updatedBody, + Public: &testutils.FalseVal, + }, + }, + noteUUID: "ab50aa32-b232-40d8-b10f-10a7f9134053", + noteBookUUID: b1UUID, + notePublic: true, + noteBody: "original content", + noteDeleted: false, + expectedNoteBookUUID: b1UUID, + expectedNoteBody: updatedBody, + expectedNoteBookName: "css", + expectedNotePublic: false, + }, + { + payload: testutils.PayloadWrapper{ + Data: payloadData{ + BookUUID: &b2UUID, + Content: &updatedBody, + Public: &testutils.TrueVal, + }, + }, + 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(), + 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{ + 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 + var req *http.Request + + 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) + + // 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") + }) + } +} diff --git a/pkg/server/controllers/routes.go b/pkg/server/controllers/routes.go new file mode 100644 index 00000000..af76bf76 --- /dev/null +++ b/pkg/server/controllers/routes.go @@ -0,0 +1,124 @@ +package controllers + +import ( + "net/http" + + "github.com/dnote/dnote/pkg/server/app" + mw "github.com/dnote/dnote/pkg/server/middleware" + "github.com/gorilla/mux" + "github.com/pkg/errors" +) + +// Route represents a single route +type Route struct { + Method string + Pattern string + Handler http.HandlerFunc + RateLimit bool +} + +// RouteConfig is the configuration for routes +type RouteConfig struct { + Controllers *Controllers + WebRoutes []Route + APIRoutes []Route +} + +// NewWebRoutes returns a new web routes +func NewWebRoutes(a *app.App, c *Controllers) []Route { + redirectGuest := &mw.AuthParams{RedirectGuestsToLogin: true} + + ret := []Route{ + {"GET", "/", mw.Auth(a, c.Users.Settings, redirectGuest), true}, + {"GET", "/about", mw.Auth(a, c.Users.About, 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", "/password-reset", c.Users.PasswordResetView.ServeHTTP, true}, + {"PATCH", "/password-reset", c.Users.PasswordReset, true}, + {"GET", "/password-reset/{token}", c.Users.PasswordResetConfirm, true}, + {"POST", "/reset-token", c.Users.CreateResetToken, true}, + {"POST", "/verification-token", mw.Auth(a, c.Users.CreateEmailVerificationToken, redirectGuest), true}, + {"GET", "/verify-email/{token}", mw.Auth(a, c.Users.VerifyEmail, redirectGuest), true}, + {"PATCH", "/account/profile", mw.Auth(a, c.Users.ProfileUpdate, nil), true}, + {"PATCH", "/account/password", mw.Auth(a, c.Users.PasswordUpdate, nil), true}, + } + + if !a.Config.DisableRegistration { + ret = append(ret, Route{"GET", "/join", c.Users.New, true}) + ret = append(ret, Route{"POST", "/join", c.Users.Create, true}) + } + + return ret +} + +// NewAPIRoutes returns a new api routes +func NewAPIRoutes(a *app.App, c *Controllers) []Route { + + proOnly := mw.AuthParams{ProOnly: true} + + return []Route{ + // internal + {"GET", "/health", c.Health.Index, true}, + + // v3 + {"GET", "/v3/sync/fragment", mw.Cors(mw.Auth(a, c.Sync.GetSyncFragment, &proOnly)), false}, + {"GET", "/v3/sync/state", mw.Cors(mw.Auth(a, c.Sync.GetSyncState, &proOnly)), false}, + {"POST", "/v3/signin", mw.Cors(c.Users.V3Login), true}, + {"POST", "/v3/signout", mw.Cors(c.Users.V3Logout), true}, + {"OPTIONS", "/v3/signout", mw.Cors(c.Users.logoutOptions), true}, + {"GET", "/v3/notes", mw.Cors(mw.Auth(a, c.Notes.V3Index, nil)), true}, + {"GET", "/v3/notes/{noteUUID}", c.Notes.V3Show, true}, + {"POST", "/v3/notes", mw.Cors(mw.Auth(a, c.Notes.V3Create, nil)), true}, + {"DELETE", "/v3/notes/{noteUUID}", mw.Cors(mw.Auth(a, c.Notes.V3Delete, nil)), true}, + {"PATCH", "/v3/notes/{noteUUID}", mw.Cors(mw.Auth(a, c.Notes.V3Update, nil)), true}, + {"OPTIONS", "/v3/notes", mw.Cors(c.Notes.IndexOptions), true}, + {"GET", "/v3/books", mw.Cors(mw.Auth(a, c.Books.V3Index, nil)), true}, + {"GET", "/v3/books/{bookUUID}", mw.Cors(mw.Auth(a, c.Books.V3Show, nil)), true}, + {"POST", "/v3/books", mw.Cors(mw.Auth(a, c.Books.V3Create, nil)), true}, + {"PATCH", "/v3/books/{bookUUID}", mw.Cors(mw.Auth(a, c.Books.V3Update, nil)), true}, + {"DELETE", "/v3/books/{bookUUID}", mw.Cors(mw.Auth(a, c.Books.V3Delete, nil)), true}, + {"OPTIONS", "/v3/books", mw.Cors(c.Books.IndexOptions), true}, + } +} + +func registerRoutes(router *mux.Router, wrapper mw.Middleware, app *app.App, routes []Route) { + for _, route := range routes { + wrappedHandler := wrapper(route.Handler, app, route.RateLimit) + + router. + Handle(route.Pattern, wrappedHandler). + Methods(route.Method) + } +} + +// NewRouter creates and returns a new router +func NewRouter(app *app.App, rc RouteConfig) (http.Handler, error) { + if err := app.Validate(); err != nil { + return nil, errors.Wrap(err, "validating the app parameters") + } + + router := mux.NewRouter().StrictSlash(true) + + webRouter := router.PathPrefix("/").Subrouter() + apiRouter := router.PathPrefix("/api").Subrouter() + registerRoutes(webRouter, mw.WebMw, app, rc.WebRoutes) + registerRoutes(apiRouter, mw.APIMw, app, rc.APIRoutes) + + router.PathPrefix("/api/v1").Handler(mw.ApplyLimit(mw.NotSupported, true)) + router.PathPrefix("/api/v2").Handler(mw.ApplyLimit(mw.NotSupported, true)) + + // static + staticHandler := http.StripPrefix("/static/", http.FileServer(http.Dir(app.Config.StaticDir))) + router.PathPrefix("/static/").Handler(staticHandler) + + router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("User-agent: *\nAllow: /")) + }) + + // catch-all + router.PathPrefix("/").HandlerFunc(rc.Controllers.Static.NotFound) + + return mw.Global(router), nil +} diff --git a/pkg/server/controllers/routes_test.go b/pkg/server/controllers/routes_test.go new file mode 100644 index 00000000..5f09b0d1 --- /dev/null +++ b/pkg/server/controllers/routes_test.go @@ -0,0 +1,77 @@ +/* Copyright (C) 2019, 2020 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 controllers + +import ( + "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/testutils" +) + +func TestNotSupportedVersions(t *testing.T) { + testCases := []struct { + path string + }{ + // v1 + { + path: "/api/v1", + }, + { + path: "/api/v1/foo", + }, + { + path: "/api/v1/bar/baz", + }, + // v2 + { + path: "/api/v2", + }, + { + path: "/api/v2/foo", + }, + { + path: "/api/v2/bar/baz", + }, + } + + // setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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") + }) + } +} diff --git a/pkg/server/controllers/static.go b/pkg/server/controllers/static.go new file mode 100644 index 00000000..6641c6fc --- /dev/null +++ b/pkg/server/controllers/static.go @@ -0,0 +1,35 @@ +package controllers + +import ( + "net/http" + "strings" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/views" +) + +// NewStatic creates a new Static controller. +func NewStatic(app *app.App, baseDir string) *Static { + return &Static{ + NotFoundView: views.NewView(baseDir, app, views.Config{Title: "Not Found", Layout: "base"}, "static/not_found"), + } +} + +// Static is a static controller +type Static struct { + NotFoundView *views.View +} + +// NotFound is a catch-all handler for requests with no matching handler +func (s *Static) NotFound(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + + accept := r.Header.Get("Accept") + + if strings.Contains(accept, "text/html") { + s.NotFoundView.Render(w, r, nil, http.StatusOK) + } else { + statusText := http.StatusText(http.StatusNotFound) + w.Write([]byte(statusText)) + } +} diff --git a/pkg/server/api/v3_sync.go b/pkg/server/controllers/sync.go similarity index 83% rename from pkg/server/api/v3_sync.go rename to pkg/server/controllers/sync.go index f30f4273..76e7f742 100644 --- a/pkg/server/api/v3_sync.go +++ b/pkg/server/controllers/sync.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package api +package controllers import ( "fmt" @@ -26,13 +26,27 @@ import ( "strconv" "time" + "github.com/dnote/dnote/pkg/server/context" "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/middleware" "github.com/pkg/errors" + + "github.com/dnote/dnote/pkg/server/app" ) +// NewSync creates a new Sync controller +func NewSync(app *app.App) *Sync { + return &Sync{ + app: app, + } +} + +// Sync is a sync controller. +type Sync struct { + app *app.App +} + // fullSyncBefore is the system-wide timestamp that represents the point in time // before which clients must perform a full-sync rather than incremental sync. const fullSyncBefore = 0 @@ -121,13 +135,13 @@ func (e *queryParamError) Error() string { return fmt.Sprintf("invalid query param %s=%s. %s", e.key, e.value, e.message) } -func (a *API) newFragment(userID, userMaxUSN, afterUSN, limit int) (SyncFragment, error) { +func (s *Sync) newFragment(userID, userMaxUSN, afterUSN, limit int) (SyncFragment, error) { var notes []database.Note - if err := a.App.DB.Where("user_id = ? AND usn > ? AND usn <= ?", userID, afterUSN, userMaxUSN).Order("usn ASC").Limit(limit).Find(¬es).Error; err != nil { + if err := s.app.DB.Where("user_id = ? AND usn > ? AND usn <= ?", userID, afterUSN, userMaxUSN).Order("usn ASC").Limit(limit).Find(¬es).Error; err != nil { return SyncFragment{}, nil } var books []database.Book - if err := a.App.DB.Where("user_id = ? AND usn > ? AND usn <= ?", userID, afterUSN, userMaxUSN).Order("usn ASC").Limit(limit).Find(&books).Error; err != nil { + if err := s.app.DB.Where("user_id = ? AND usn > ? AND usn <= ?", userID, afterUSN, userMaxUSN).Order("usn ASC").Limit(limit).Find(&books).Error; err != nil { return SyncFragment{}, nil } @@ -192,7 +206,7 @@ func (a *API) newFragment(userID, userMaxUSN, afterUSN, limit int) (SyncFragment ret := SyncFragment{ FragMaxUSN: fragMaxUSN, UserMaxUSN: userMaxUSN, - CurrentTime: a.App.Clock.Now().Unix(), + CurrentTime: s.app.Clock.Now().Unix(), Notes: fragNotes, Books: fragBooks, ExpungedNotes: fragExpungedNotes, @@ -248,29 +262,29 @@ type GetSyncFragmentResp struct { } // GetSyncFragment responds with a sync fragment -func (a *API) GetSyncFragment(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) +func (s *Sync) GetSyncFragment(w http.ResponseWriter, r *http.Request) { + user := context.User(r.Context()) + if user == nil { + middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } afterUSN, limit, err := parseGetSyncFragmentQuery(r.URL.Query()) if err != nil { - handlers.DoError(w, "parsing query params", err, http.StatusInternalServerError) + middleware.DoError(w, "parsing query params", err, http.StatusInternalServerError) return } - fragment, err := a.newFragment(user.ID, user.MaxUSN, afterUSN, limit) + fragment, err := s.newFragment(user.ID, user.MaxUSN, afterUSN, limit) if err != nil { - handlers.DoError(w, "getting fragment", err, http.StatusInternalServerError) + middleware.DoError(w, "getting fragment", err, http.StatusInternalServerError) return } response := GetSyncFragmentResp{ Fragment: fragment, } - handlers.RespondJSON(w, http.StatusOK, response) + respondJSON(w, http.StatusOK, response) } // GetSyncStateResp represents a response from GetSyncFragment handler @@ -281,10 +295,10 @@ type GetSyncStateResp struct { } // GetSyncState responds with a sync fragment -func (a *API) GetSyncState(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) +func (s *Sync) GetSyncState(w http.ResponseWriter, r *http.Request) { + user := context.User(r.Context()) + if user == nil { + middleware.DoError(w, "No authenticated user found", nil, http.StatusInternalServerError) return } @@ -292,7 +306,7 @@ func (a *API) GetSyncState(w http.ResponseWriter, r *http.Request) { FullSyncBefore: fullSyncBefore, MaxUSN: user.MaxUSN, // TODO: exposing server time means we probably shouldn't seed random generator with time? - CurrentTime: a.App.Clock.Now().Unix(), + CurrentTime: s.app.Clock.Now().Unix(), } log.WithFields(log.Fields{ @@ -300,5 +314,5 @@ func (a *API) GetSyncState(w http.ResponseWriter, r *http.Request) { "resp": response, }).Info("getting sync state") - handlers.RespondJSON(w, http.StatusOK, response) + respondJSON(w, http.StatusOK, response) } diff --git a/pkg/server/api/v3_sync_test.go b/pkg/server/controllers/sync_test.go similarity index 99% rename from pkg/server/api/v3_sync_test.go rename to pkg/server/controllers/sync_test.go index 8f2fb6e6..e8e48da1 100644 --- a/pkg/server/api/v3_sync_test.go +++ b/pkg/server/controllers/sync_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package api +package controllers import ( "fmt" diff --git a/pkg/server/api/testutils.go b/pkg/server/controllers/testutils.go similarity index 67% rename from pkg/server/api/testutils.go rename to pkg/server/controllers/testutils.go index fe17e25c..aaf4f5c7 100644 --- a/pkg/server/api/testutils.go +++ b/pkg/server/controllers/testutils.go @@ -1,4 +1,4 @@ -/* Copyright (C) 2019, 2020, 2021 Monomax Software Pty Ltd +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd * * This file is part of Dnote. * @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package api +package controllers import ( "net/http/httptest" @@ -29,20 +29,29 @@ import ( // 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) + server, err := NewServer(appParams) if err != nil { - t.Fatal(errors.Wrap(err, "initializing server")) + t.Fatal(errors.Wrap(err, "initializing router")) } - server := httptest.NewServer(r) - return server } -// NewTestAPI returns a new API for test -func NewTestAPI(appParams *app.App) API { +func NewServer(appParams *app.App) (*httptest.Server, error) { a := app.NewTest(appParams) - return API{App: &a} + ctl := New(&a, a.Config.PageTemplateDir) + rc := RouteConfig{ + WebRoutes: NewWebRoutes(&a, ctl), + APIRoutes: NewAPIRoutes(&a, ctl), + Controllers: ctl, + } + r, err := NewRouter(&a, rc) + if err != nil { + return nil, errors.Wrap(err, "initializing router") + } + + server := httptest.NewServer(r) + + return server, nil } diff --git a/pkg/server/controllers/users.go b/pkg/server/controllers/users.go new file mode 100644 index 00000000..ba8cdca1 --- /dev/null +++ b/pkg/server/controllers/users.go @@ -0,0 +1,718 @@ +package controllers + +import ( + "net/http" + "net/url" + "time" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/buildinfo" + "github.com/dnote/dnote/pkg/server/context" + "github.com/dnote/dnote/pkg/server/database" + "github.com/dnote/dnote/pkg/server/helpers" + "github.com/dnote/dnote/pkg/server/log" + "github.com/dnote/dnote/pkg/server/mailer" + "github.com/dnote/dnote/pkg/server/token" + "github.com/dnote/dnote/pkg/server/views" + "github.com/gorilla/mux" + "github.com/pkg/errors" + "golang.org/x/crypto/bcrypt" +) + +var commonHelpers = map[string]interface{}{ + "getPathWithReferrer": func(base string, referrer string) string { + if referrer == "" { + return base + } + + query := url.Values{} + query.Set("referrer", referrer) + + return helpers.GetPath(base, &query) + }, +} + +// NewUsers creates a new Users controller. +// It panics if the necessary templates are not parsed. +func NewUsers(app *app.App, baseDir string) *Users { + return &Users{ + NewView: views.NewView(baseDir, app, + views.Config{Title: "Join", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true}, + "users/new", + ), + LoginView: views.NewView(baseDir, app, + views.Config{Title: "Sign In", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true}, + "users/login", + ), + PasswordResetView: views.NewView(baseDir, app, + views.Config{Title: "Reset Password", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true}, + "users/password_reset", + ), + PasswordResetConfirmView: views.NewView(baseDir, app, + views.Config{Title: "Reset Password", Layout: "base", HelperFuncs: commonHelpers, AlertInBody: true}, + "users/password_reset_confirm", + ), + SettingView: views.NewView(baseDir, app, + views.Config{Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"}, + "users/settings", + ), + AboutView: views.NewView(baseDir, app, + views.Config{Title: "About", Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"}, + "users/settings_about", + ), + EmailVerificationView: views.NewView(baseDir, app, + views.Config{Layout: "base", HelperFuncs: commonHelpers, HeaderTemplate: "navbar"}, + "users/email_verification", + ), + app: app, + } +} + +// Users is a user controller. +type Users struct { + NewView *views.View + LoginView *views.View + SettingView *views.View + AboutView *views.View + PasswordResetView *views.View + PasswordResetConfirmView *views.View + EmailVerificationView *views.View + app *app.App +} + +// New renders user registration page +func (u *Users) New(w http.ResponseWriter, r *http.Request) { + vd := getDataWithReferrer(r) + u.NewView.Render(w, r, &vd, http.StatusOK) +} + +// RegistrationForm is the form data for registering +type RegistrationForm struct { + Email string `schema:"email"` + Password string `schema:"password"` + PasswordConfirmation string `schema:"password_confirmation"` +} + +// Create handles register +func (u *Users) Create(w http.ResponseWriter, r *http.Request) { + vd := getDataWithReferrer(r) + + var form RegistrationForm + if err := parseForm(r, &form); err != nil { + handleHTMLError(w, r, err, "parsing form", u.NewView, vd) + return + } + + vd.Yield["Email"] = form.Email + + user, err := u.app.CreateUser(form.Email, form.Password, form.PasswordConfirmation) + if err != nil { + handleHTMLError(w, r, err, "creating user", u.NewView, vd) + return + } + + session, err := u.app.SignIn(&user) + if err != nil { + handleHTMLError(w, r, err, "signing in a user", u.LoginView, vd) + return + } + + if err := u.app.SendWelcomeEmail(form.Email); err != nil { + log.ErrorWrap(err, "sending welcome email") + } + + setSessionCookie(w, session.Key, session.ExpiresAt) + + dest := getPathOrReferrer("/", r) + http.Redirect(w, r, dest, http.StatusFound) +} + +// LoginForm is the form data for log in +type LoginForm struct { + Email string `schema:"email" json:"email"` + Password string `schema:"password" json:"password"` +} + +func (u *Users) login(form LoginForm) (*database.Session, error) { + if form.Email == "" { + return nil, app.ErrEmailRequired + } + if form.Password == "" { + return nil, app.ErrPasswordRequired + } + + user, err := u.app.Authenticate(form.Email, form.Password) + if err != nil { + // If the user is not found, treat it as invalid login + if err == app.ErrNotFound { + return nil, app.ErrLoginInvalid + } + + return nil, err + } + + s, err := u.app.SignIn(user) + if err != nil { + return nil, err + } + + return s, nil +} + +func getPathOrReferrer(path string, r *http.Request) string { + q := r.URL.Query() + referrer := q.Get("referrer") + + if referrer == "" { + return path + } + + return referrer +} + +func getDataWithReferrer(r *http.Request) views.Data { + vd := views.Data{} + + vd.Yield = map[string]interface{}{ + "Referrer": r.URL.Query().Get("referrer"), + } + + return vd +} + +// NewLogin renders user login page +func (u *Users) NewLogin(w http.ResponseWriter, r *http.Request) { + vd := getDataWithReferrer(r) + u.LoginView.Render(w, r, &vd, http.StatusOK) +} + +// Login handles login +func (u *Users) Login(w http.ResponseWriter, r *http.Request) { + vd := getDataWithReferrer(r) + + var form LoginForm + if err := parseRequestData(r, &form); err != nil { + handleHTMLError(w, r, err, "parsing payload", u.LoginView, vd) + return + } + + session, err := u.login(form) + if err != nil { + vd.Yield["Email"] = form.Email + handleHTMLError(w, r, err, "logging in user", u.LoginView, vd) + return + } + + setSessionCookie(w, session.Key, session.ExpiresAt) + + dest := getPathOrReferrer("/", r) + http.Redirect(w, r, dest, http.StatusFound) +} + +// V3Login handles login +func (u *Users) V3Login(w http.ResponseWriter, r *http.Request) { + var form LoginForm + if err := parseRequestData(r, &form); err != nil { + handleJSONError(w, err, "parsing payload") + return + } + + session, err := u.login(form) + if err != nil { + handleJSONError(w, err, "logging in user") + return + } + + respondWithSession(w, http.StatusOK, session) +} + +func (u *Users) logout(r *http.Request) (bool, error) { + key, err := GetCredential(r) + if err != nil { + return false, errors.Wrap(err, "getting credentials") + } + + if key == "" { + return false, nil + } + + if err = u.app.DeleteSession(key); err != nil { + return false, errors.Wrap(err, "deleting session") + } + + return true, nil +} + +// Logout handles logout +func (u *Users) Logout(w http.ResponseWriter, r *http.Request) { + var vd views.Data + + ok, err := u.logout(r) + if err != nil { + handleHTMLError(w, r, err, "logging out", u.LoginView, vd) + return + } + + if ok { + unsetSessionCookie(w) + } + + http.Redirect(w, r, "/login", http.StatusFound) +} + +// V3Logout handles logout via API +func (u *Users) V3Logout(w http.ResponseWriter, r *http.Request) { + ok, err := u.logout(r) + if err != nil { + handleJSONError(w, err, "logging out") + return + } + + if ok { + unsetSessionCookie(w) + } + + w.WriteHeader(http.StatusNoContent) +} + +type createResetTokenPayload struct { + Email string `schema:"email" json:"email"` +} + +func (u *Users) CreateResetToken(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + var form createResetTokenPayload + if err := parseForm(r, &form); err != nil { + handleHTMLError(w, r, err, "parsing form", u.PasswordResetView, vd) + return + } + + if form.Email == "" { + handleHTMLError(w, r, app.ErrEmailRequired, "email is not provided", u.PasswordResetView, vd) + return + } + + var account database.Account + conn := u.app.DB.Where("email = ?", form.Email).First(&account) + if conn.RecordNotFound() { + return + } + if err := conn.Error; err != nil { + handleHTMLError(w, r, err, "finding account", u.PasswordResetView, vd) + return + } + + resetToken, err := token.Create(u.app.DB, account.UserID, database.TokenTypeResetPassword) + if err != nil { + handleHTMLError(w, r, err, "generating token", u.PasswordResetView, vd) + return + } + + if err := u.app.SendPasswordResetEmail(account.Email.String, resetToken.Value); err != nil { + handleHTMLError(w, r, err, "sending password reset email", u.PasswordResetView, vd) + return + } + + alert := views.Alert{ + Level: views.AlertLvlSuccess, + Message: "Check your email for a link to reset your password.", + } + views.RedirectAlert(w, r, "/password-reset", http.StatusFound, alert) +} + +// PasswordResetConfirm renders password reset view +func (u *Users) PasswordResetConfirm(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + vars := mux.Vars(r) + token := vars["token"] + + vd.Yield = map[string]interface{}{ + "Token": token, + } + + u.PasswordResetConfirmView.Render(w, r, &vd, http.StatusOK) +} + +type resetPasswordPayload struct { + Password string `schema:"password" json:"password"` + PasswordConfirmation string `schema:"password_confirmation" json:"password_confirmation"` + Token string `schema:"token" json:"token"` +} + +// PasswordReset renders password reset view +func (u *Users) PasswordReset(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + var params resetPasswordPayload + if err := parseForm(r, ¶ms); err != nil { + handleHTMLError(w, r, err, "parsing params", u.NewView, vd) + return + } + + vd.Yield = map[string]interface{}{ + "Token": params.Token, + } + + if params.Password != params.PasswordConfirmation { + handleHTMLError(w, r, app.ErrPasswordConfirmationMismatch, "password mismatch", u.PasswordResetConfirmView, vd) + return + } + + var token database.Token + conn := u.app.DB.Where("value = ? AND type =? AND used_at IS NULL", params.Token, database.TokenTypeResetPassword).First(&token) + if conn.RecordNotFound() { + handleHTMLError(w, r, app.ErrInvalidToken, "invalid token", u.PasswordResetConfirmView, vd) + return + } + if err := conn.Error; err != nil { + handleHTMLError(w, r, err, "finding token", u.PasswordResetConfirmView, vd) + return + } + + if token.UsedAt != nil { + handleHTMLError(w, r, app.ErrInvalidToken, "invalid token", u.PasswordResetConfirmView, vd) + return + } + + // Expire after 10 minutes + if time.Since(token.CreatedAt).Minutes() > 10 { + handleHTMLError(w, r, app.ErrPasswordResetTokenExpired, "expired token", u.PasswordResetConfirmView, vd) + return + } + + tx := u.app.DB.Begin() + + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(params.Password), bcrypt.DefaultCost) + if err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "hashing password", u.PasswordResetConfirmView, vd) + return + } + + var account database.Account + if err := u.app.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "finding user", u.PasswordResetConfirmView, vd) + return + } + + if err := tx.Model(&account).Update("password", string(hashedPassword)).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "updating password", u.PasswordResetConfirmView, vd) + return + } + if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "updating password reset token", u.PasswordResetConfirmView, vd) + return + } + + if err := u.app.DeleteUserSessions(tx, account.UserID); err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "deleting user sessions", u.PasswordResetConfirmView, vd) + return + } + + tx.Commit() + + var user database.User + if err := u.app.DB.Where("id = ?", account.UserID).First(&user).Error; err != nil { + handleHTMLError(w, r, err, "finding user", u.PasswordResetConfirmView, vd) + return + } + + alert := views.Alert{ + Level: views.AlertLvlSuccess, + Message: "Password reset successful", + } + views.RedirectAlert(w, r, "/login", http.StatusFound, alert) + + if err := u.app.SendPasswordResetAlertEmail(account.Email.String); err != nil { + log.ErrorWrap(err, "sending password reset email") + } +} + +func (u *Users) logoutOptions(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Access-Control-Allow-Methods", "POST") + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Version") +} + +func (u *Users) Settings(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + u.SettingView.Render(w, r, &vd, http.StatusOK) +} + +func (u *Users) About(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + vd.Yield = map[string]interface{}{ + "Version": buildinfo.Version, + } + + u.AboutView.Render(w, r, &vd, http.StatusOK) +} + +type updatePasswordForm struct { + OldPassword string `schema:"old_password"` + NewPassword string `schema:"new_password"` + NewPasswordConfirmation string `schema:"new_password_confirmation"` +} + +func (u *Users) PasswordUpdate(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + user := context.User(r.Context()) + if user == nil { + handleHTMLError(w, r, app.ErrLoginRequired, "No authenticated user found", u.SettingView, vd) + return + } + + var form updatePasswordForm + if err := parseRequestData(r, &form); err != nil { + handleHTMLError(w, r, err, "parsing payload", u.LoginView, vd) + return + } + + if form.OldPassword == "" || form.NewPassword == "" { + handleHTMLError(w, r, app.ErrInvalidPasswordChangeInput, "invalid params", u.SettingView, vd) + return + } + if form.NewPassword != form.NewPasswordConfirmation { + handleHTMLError(w, r, app.ErrPasswordConfirmationMismatch, "passwords do not match", u.SettingView, vd) + return + } + + var account database.Account + if err := u.app.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { + handleHTMLError(w, r, err, "getting account", u.SettingView, vd) + return + } + + password := []byte(form.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") + handleHTMLError(w, r, app.ErrInvalidPassword, "invalid password", u.SettingView, vd) + return + } + + if err := validatePassword(form.NewPassword); err != nil { + handleHTMLError(w, r, err, "invalid password", u.SettingView, vd) + return + } + + hashedNewPassword, err := bcrypt.GenerateFromPassword([]byte(form.NewPassword), bcrypt.DefaultCost) + if err != nil { + handleHTMLError(w, r, err, "hashing password", u.SettingView, vd) + return + } + + if err := u.app.DB.Model(&account).Update("password", string(hashedNewPassword)).Error; err != nil { + handleHTMLError(w, r, err, "updating password", u.SettingView, vd) + return + } + + alert := views.Alert{ + Level: views.AlertLvlSuccess, + Message: "Password change successful", + } + views.RedirectAlert(w, r, "/", http.StatusFound, alert) +} + +func validatePassword(password string) error { + if len(password) < 8 { + return app.ErrPasswordTooShort + } + + return nil +} + +type updateProfileForm struct { + Email string `schema:"email"` + Password string `schema:"password"` +} + +func (u *Users) ProfileUpdate(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + user := context.User(r.Context()) + if user == nil { + handleHTMLError(w, r, app.ErrLoginRequired, "No authenticated user found", u.SettingView, vd) + return + } + + var account database.Account + if err := u.app.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { + handleHTMLError(w, r, err, "getting account", u.SettingView, vd) + return + } + + var form updateProfileForm + if err := parseRequestData(r, &form); err != nil { + handleHTMLError(w, r, err, "parsing payload", u.SettingView, vd) + return + } + + password := []byte(form.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") + handleHTMLError(w, r, app.ErrInvalidPassword, "Wrong password", u.SettingView, vd) + return + } + + // Validate + if len(form.Email) > 60 { + handleHTMLError(w, r, app.ErrEmailTooLong, "Email is too long", u.SettingView, vd) + return + } + + tx := u.app.DB.Begin() + if err := tx.Save(&user).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "saving user", u.SettingView, vd) + return + } + + // check if email was changed + if form.Email != account.Email.String { + account.EmailVerified = false + } + account.Email.String = form.Email + + if err := tx.Save(&account).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "saving account", u.SettingView, vd) + return + } + + tx.Commit() + + alert := views.Alert{ + Level: views.AlertLvlSuccess, + Message: "Email change successful", + } + views.RedirectAlert(w, r, "/", http.StatusFound, alert) +} + +func (u *Users) VerifyEmail(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + vars := mux.Vars(r) + tokenValue := vars["token"] + + if tokenValue == "" { + handleHTMLError(w, r, app.ErrMissingToken, "Missing email verification token", u.EmailVerificationView, vd) + return + } + + var token database.Token + if err := u.app.DB. + Where("value = ? AND type = ?", tokenValue, database.TokenTypeEmailVerification). + First(&token).Error; err != nil { + handleHTMLError(w, r, app.ErrInvalidToken, "Finding token", u.EmailVerificationView, vd) + return + } + + if token.UsedAt != nil { + handleHTMLError(w, r, app.ErrInvalidToken, "Token has already been used.", u.EmailVerificationView, vd) + return + } + + // Expire after ttl + if time.Since(token.CreatedAt).Minutes() > 30 { + handleHTMLError(w, r, app.ErrExpiredToken, "Token has expired.", u.EmailVerificationView, vd) + return + } + + var account database.Account + if err := u.app.DB.Where("user_id = ?", token.UserID).First(&account).Error; err != nil { + handleHTMLError(w, r, err, "finding account", u.EmailVerificationView, vd) + return + } + if account.EmailVerified { + handleHTMLError(w, r, app.ErrEmailAlreadyVerified, "Already verified", u.EmailVerificationView, vd) + return + } + + tx := u.app.DB.Begin() + account.EmailVerified = true + if err := tx.Save(&account).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "updating email_verified", u.EmailVerificationView, vd) + return + } + if err := tx.Model(&token).Update("used_at", time.Now()).Error; err != nil { + tx.Rollback() + handleHTMLError(w, r, err, "updating reset token", u.EmailVerificationView, vd) + return + } + tx.Commit() + + var user database.User + if err := u.app.DB.Where("id = ?", token.UserID).First(&user).Error; err != nil { + handleHTMLError(w, r, err, "finding user", u.EmailVerificationView, vd) + return + } + + session, err := u.app.SignIn(&user) + if err != nil { + handleHTMLError(w, r, err, "Creating session", u.EmailVerificationView, vd) + } + + setSessionCookie(w, session.Key, session.ExpiresAt) + http.Redirect(w, r, "/", http.StatusFound) +} + +func (u *Users) CreateEmailVerificationToken(w http.ResponseWriter, r *http.Request) { + vd := views.Data{} + + user := context.User(r.Context()) + if user == nil { + handleHTMLError(w, r, app.ErrLoginRequired, "No authenticated user found", u.SettingView, vd) + return + } + + var account database.Account + err := u.app.DB.Where("user_id = ?", user.ID).First(&account).Error + if err != nil { + handleHTMLError(w, r, err, "finding account", u.SettingView, vd) + return + } + + if account.EmailVerified { + handleHTMLError(w, r, app.ErrEmailAlreadyVerified, "email is already verified.", u.SettingView, vd) + return + } + if account.Email.String == "" { + handleHTMLError(w, r, app.ErrEmailRequired, "email is empty.", u.SettingView, vd) + return + } + + tok, err := token.Create(u.app.DB, account.UserID, database.TokenTypeEmailVerification) + if err != nil { + handleHTMLError(w, r, err, "saving token", u.SettingView, vd) + return + } + + if err := u.app.SendVerificationEmail(account.Email.String, tok.Value); err != nil { + if errors.Cause(err) == mailer.ErrSMTPNotConfigured { + handleHTMLError(w, r, app.ErrInvalidSMTPConfig, "SMTP config is not configured correctly.", u.SettingView, vd) + } else { + handleHTMLError(w, r, err, "sending verification email", u.SettingView, vd) + } + + return + } + + alert := views.Alert{ + Level: views.AlertLvlSuccess, + Message: "Please check your email for the verification", + } + views.RedirectAlert(w, r, "/", http.StatusFound, alert) +} diff --git a/pkg/server/controllers/users_test.go b/pkg/server/controllers/users_test.go new file mode 100644 index 00000000..110e61b1 --- /dev/null +++ b/pkg/server/controllers/users_test.go @@ -0,0 +1,1388 @@ +/* Copyright (C) 2019, 2020 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 controllers + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "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 assertResponseSessionCookie(t *testing.T, res *http.Response) { + 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") + + 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 TestJoin(t *testing.T) { + testCases := []struct { + email string + password string + passwordConfirmation string + onPremise bool + expectedPro bool + }{ + { + email: "alice@example.com", + password: "pass1234", + passwordConfirmation: "pass1234", + onPremise: false, + expectedPro: false, + }, + { + email: "bob@example.com", + password: "Y9EwmjH@Jq6y5a64MSACUoM4w7SAhzvY", + passwordConfirmation: "Y9EwmjH@Jq6y5a64MSACUoM4w7SAhzvY", + onPremise: false, + expectedPro: false, + }, + { + email: "chuck@example.com", + password: "e*H@kJi^vXbWEcD9T5^Am!Y@7#Po2@PC", + passwordConfirmation: "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", + passwordConfirmation: "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) + + // Setup + emailBackend := testutils.MockEmailbackendImplementation{} + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + EmailBackend: &emailBackend, + Config: config.Config{ + OnPremise: tc.onPremise, + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + dat := url.Values{} + dat.Set("email", tc.email) + dat.Set("password", tc.password) + dat.Set("password_confirmation", tc.passwordConfirmation) + req := testutils.MakeFormReq(server.URL, "POST", "/join", dat) + + // Execute + res := testutils.HTTPDo(t, req) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "") + + 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 + assertResponseSessionCookie(t, res) + }) + } +} + +func TestJoinError(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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + dat := url.Values{} + dat.Set("password", "SLMZFM5RmSjA5vfXnG5lPOnrpZSbtmV76cnAcrlr2yU") + req := testutils.MakeFormReq(server.URL, "POST", "/join", 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + dat := url.Values{} + dat.Set("email", "alice@example.com") + req := testutils.MakeFormReq(server.URL, "POST", "/join", 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("password confirmation mismatch", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + dat := url.Values{} + dat.Set("email", "alice@example.com") + dat.Set("password", "pass1234") + dat.Set("password_confirmation", "1234pass") + req := testutils.MakeFormReq(server.URL, "POST", "/join", 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 TestJoinDuplicateEmail(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + testutils.SetupAccountData(u, "alice@example.com", "somepassword") + + dat := url.Values{} + dat.Set("email", "alice@example.com") + dat.Set("password", "foobarbaz") + dat.Set("password_confirmation", "foobarbaz") + req := testutils.MakeFormReq(server.URL, "POST", "/join", 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 TestJoinDisabled(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + DisableRegistration: true, + }, + }) + defer server.Close() + + dat := url.Values{} + dat.Set("email", "alice@example.com") + dat.Set("password", "foobarbaz") + req := testutils.MakeFormReq(server.URL, "POST", "/join", dat) + + // Execute + res := testutils.HTTPDo(t, req) + + // Test + assert.StatusCodeEquals(t, res, http.StatusNotFound, "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 TestLogin(t *testing.T) { + testutils.RunForWebAndAPI(t, "success", func(t *testing.T, target testutils.EndpointType) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + + u := testutils.SetupUserData() + testutils.SetupAccountData(u, "alice@example.com", "pass1234") + defer server.Close() + + // Execute + var req *http.Request + if target == testutils.EndpointWeb { + dat := url.Values{} + dat.Set("email", "alice@example.com") + dat.Set("password", "pass1234") + req = testutils.MakeFormReq(server.URL, "POST", "/login", dat) + } else { + dat := `{"email": "alice@example.com", "password": "pass1234"}` + req = testutils.MakeReq(server.URL, "POST", "/api/v3/signin", dat) + } + + res := testutils.HTTPDo(t, req) + + // Test + if target == testutils.EndpointWeb { + assert.StatusCodeEquals(t, res, http.StatusFound, "") + } else { + 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") + + if target == testutils.EndpointWeb { + assertResponseSessionCookie(t, res) + } else { + // 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") + + assertResponseSessionCookie(t, res) + } + }) + + testutils.RunForWebAndAPI(t, "wrong password", func(t *testing.T, target testutils.EndpointType) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + + u := testutils.SetupUserData() + testutils.SetupAccountData(u, "alice@example.com", "pass1234") + defer server.Close() + + var req *http.Request + if target == testutils.EndpointWeb { + dat := url.Values{} + dat.Set("email", "alice@example.com") + dat.Set("password", "wrongpassword1234") + req = testutils.MakeFormReq(server.URL, "POST", "/login", dat) + } else { + dat := `{"email": "alice@example.com", "password": "wrongpassword1234"}` + req = testutils.MakeReq(server.URL, "POST", "/api/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") + }) + + testutils.RunForWebAndAPI(t, "wrong email", func(t *testing.T, target testutils.EndpointType) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + testutils.SetupAccountData(u, "alice@example.com", "pass1234") + + var req *http.Request + if target == testutils.EndpointWeb { + dat := url.Values{} + dat.Set("email", "bob@example.com") + dat.Set("password", "foobarbaz") + req = testutils.MakeFormReq(server.URL, "POST", "/login", dat) + } else { + dat := `{"email": "bob@example.com", "password": "foobarbaz"}` + req = testutils.MakeReq(server.URL, "POST", "/api/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") + }) + + testutils.RunForWebAndAPI(t, "nonexistent email", func(t *testing.T, target testutils.EndpointType) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + var req *http.Request + if target == testutils.EndpointWeb { + dat := url.Values{} + dat.Set("email", "nonexistent@example.com") + dat.Set("password", "pass1234") + req = testutils.MakeFormReq(server.URL, "POST", "/login", dat) + } else { + dat := `{"email": "nonexistent@example.com", "password": "pass1234"}` + req = testutils.MakeReq(server.URL, "POST", "/api/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 TestLogout(t *testing.T) { + setupLogoutTest := func(t *testing.T) (*httptest.Server, *database.Session, *database.Session) { + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + + aliceUser := testutils.SetupUserData() + testutils.SetupAccountData(aliceUser, "alice@example.com", "pass1234") + anotherUser := testutils.SetupUserData() + + session1ExpiresAt := time.Now().Add(time.Hour * 24) + session1 := database.Session{ + Key: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=", + UserID: aliceUser.ID, + ExpiresAt: session1ExpiresAt, + } + 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") + + return server, &session1, &session2 + } + + testutils.RunForWebAndAPI(t, "authenticated", func(t *testing.T, target testutils.EndpointType) { + defer testutils.ClearData(testutils.DB) + + server, session1, _ := setupLogoutTest(t) + defer server.Close() + + // Execute + var req *http.Request + if target == testutils.EndpointWeb { + dat := url.Values{} + req = testutils.MakeFormReq(server.URL, "POST", "/logout", dat) + req.AddCookie(&http.Cookie{Name: "id", Value: "A9xgggqzTHETy++GDi1NpDNe0iyqosPm9bitdeNGkJU=", Expires: session1.ExpiresAt, Path: "/", HttpOnly: true}) + } else { + req = testutils.MakeReq(server.URL, "POST", "/api/v3/signout", "") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", session1.Key)) + } + + res := testutils.HTTPDo(t, req) + + // Test + if target == testutils.EndpointWeb { + assert.StatusCodeEquals(t, res, http.StatusFound, "Status mismatch") + } else { + 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") + + if target == testutils.EndpointWeb { + 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") + } + } + }) + + testutils.RunForWebAndAPI(t, "unauthenticated", func(t *testing.T, target testutils.EndpointType) { + defer testutils.ClearData(testutils.DB) + + server, _, _ := setupLogoutTest(t) + defer server.Close() + + // Execute + var req *http.Request + if target == testutils.EndpointWeb { + dat := url.Values{} + req = testutils.MakeFormReq(server.URL, "POST", "/logout", dat) + } else { + req = testutils.MakeReq(server.URL, "POST", "/api/v3/signout", "") + } + + res := testutils.HTTPDo(t, req) + + // Test + if target == testutils.EndpointWeb { + assert.StatusCodeEquals(t, res, http.StatusFound, "Status mismatch") + } else { + 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") + }) +} + +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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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") + + 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 + dat := url.Values{} + dat.Set("token", "MivFxYiSMMA4An9dP24DNQ==") + dat.Set("password", "newpassword") + dat.Set("password_confirmation", "newpassword") + req := testutils.MakeFormReq(server.URL, "PATCH", "/password-reset", dat) + + res := testutils.HTTPDo(t, req) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "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, 0, "should have deleted a 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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 := url.Values{} + dat.Set("token", "-ApMnyvpg59uOU5b-Kf5uQ==") + dat.Set("password", "oldpassword") + dat.Set("password_confirmation", "oldpassword") + req := testutils.MakeFormReq(server.URL, "PATCH", "/password-reset", 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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 := url.Values{} + dat.Set("token", "MivFxYiSMMA4An9dP24DNQ==") + dat.Set("password", "oldpassword") + dat.Set("password_confirmation", "oldpassword") + req := testutils.MakeFormReq(server.URL, "PATCH", "/password-reset", 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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 := url.Values{} + dat.Set("token", "MivFxYiSMMA4An9dP24DNQ==") + dat.Set("password", "oldpassword") + dat.Set("password_confirmation", "oldpassword") + req := testutils.MakeFormReq(server.URL, "PATCH", "/password-reset", 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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 := url.Values{} + dat.Set("token", "MivFxYiSMMA4An9dP24DNQ==") + dat.Set("password", "oldpassword") + dat.Set("password_confirmation", "oldpassword") + req := testutils.MakeFormReq(server.URL, "PATCH", "/password-reset", 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") + }) +} + +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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + testutils.SetupAccountData(u, "alice@example.com", "somepassword") + + // Execute + dat := url.Values{} + dat.Set("email", "alice@example.com") + req := testutils.MakeFormReq(server.URL, "POST", "/reset-token", dat) + + res := testutils.HTTPDo(t, req) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + testutils.SetupAccountData(u, "alice@example.com", "somepassword") + + // Execute + dat := url.Values{} + dat.Set("email", "bob@example.com") + req := testutils.MakeFormReq(server.URL, "POST", "/reset-token", dat) + + 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 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + user := testutils.SetupUserData() + testutils.SetupAccountData(user, "alice@example.com", "oldpassword") + + // Execute + dat := url.Values{} + dat.Set("old_password", "oldpassword") + dat.Set("new_password", "newpassword") + dat.Set("new_password_confirmation", "newpassword") + req := testutils.MakeFormReq(server.URL, "PATCH", "/account/password", dat) + + res := testutils.HTTPAuthDo(t, req, user) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword") + + // Execute + dat := url.Values{} + dat.Set("old_password", "randompassword") + dat.Set("new_password", "newpassword") + dat.Set("new_password_confirmation", "newpassword") + req := testutils.MakeFormReq(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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword") + + // Execute + dat := url.Values{} + dat.Set("old_password", "oldpassword") + dat.Set("new_password", "a") + dat.Set("new_password_confirmation", "a") + req := testutils.MakeFormReq(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") + }) + + t.Run("password confirmation mismatch", func(t *testing.T) { + defer testutils.ClearData(testutils.DB) + + // Setup + server := MustNewServer(t, &app.App{ + Clock: clock.NewMock(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + defer server.Close() + + u := testutils.SetupUserData() + a := testutils.SetupAccountData(u, "alice@example.com", "oldpassword") + + // Execute + dat := url.Values{} + dat.Set("old_password", "oldpassword") + dat.Set("new_password", "newpassword1") + dat.Set("new_password_confirmation", "newpassword2") + req := testutils.MakeFormReq(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 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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 := url.Values{} + dat.Set("email", "alice-new@example.com") + dat.Set("password", "pass1234") + req := testutils.MakeFormReq(server.URL, "PATCH", "/account/profile", dat) + + res := testutils.HTTPAuthDo(t, req, u) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "Status code mismatch") + + 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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 := url.Values{} + dat.Set("email", "alice-new@example.com") + dat.Set("password", "wrongpassword") + req := testutils.MakeFormReq(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 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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") + + // Execute + req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "") + res := testutils.HTTPAuthDo(t, req, user) + + // Test + assert.StatusCodeEquals(t, res, http.StatusFound, "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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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") + + // Execute + req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "") + 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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") + + // Execute + req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "") + 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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") + + // Execute + req := testutils.MakeReq(server.URL, "GET", fmt.Sprintf("/verify-email/%s", "someTokenValue"), "") + 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 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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + 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.StatusFound, "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(), + Config: config.Config{ + PageTemplateDir: "../views", + }, + }) + 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.StatusConflict, "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") + }) +} diff --git a/pkg/server/database/errors.go b/pkg/server/database/errors.go new file mode 100644 index 00000000..e142a83d --- /dev/null +++ b/pkg/server/database/errors.go @@ -0,0 +1,12 @@ +package database + +import ( + "github.com/pkg/errors" +) + +type modelError string + +var ( + // ErrNotFound an error that indicates that the given resource is not found + ErrNotFound error = errors.New("not found") +) diff --git a/pkg/server/handlers/main_test.go b/pkg/server/helpers/url.go similarity index 70% rename from pkg/server/handlers/main_test.go rename to pkg/server/helpers/url.go index 550c919e..e136b230 100644 --- a/pkg/server/handlers/main_test.go +++ b/pkg/server/helpers/url.go @@ -1,4 +1,4 @@ -/* Copyright (C) 2019, 2020, 2021 Monomax Software Pty Ltd +/* Copyright (C) 2019, 2020 Monomax Software Pty Ltd * * This file is part of Dnote. * @@ -16,20 +16,19 @@ * along with Dnote. If not, see . */ -package handlers +package helpers import ( - "os" - "testing" - - "github.com/dnote/dnote/pkg/server/testutils" + "fmt" + "net/url" ) -func TestMain(m *testing.M) { - testutils.InitTestDB() +// GetPath returns a path optionally suffixed by query string +func GetPath(path string, query *url.Values) string { + if query == nil { + return path + } - code := m.Run() - testutils.ClearData(testutils.DB) - - os.Exit(code) + q := query.Encode() + return fmt.Sprintf("%s?%s", path, q) } diff --git a/pkg/server/helpers/url_test.go b/pkg/server/helpers/url_test.go new file mode 100644 index 00000000..14752e93 --- /dev/null +++ b/pkg/server/helpers/url_test.go @@ -0,0 +1,29 @@ +package helpers + +import ( + "net/url" + "testing" + + "github.com/dnote/dnote/pkg/assert" +) + +func TestGetPath(t *testing.T) { + t.Run("without query", func(t *testing.T) { + // execute + got := GetPath("/some-path", nil) + + // test + assert.Equal(t, got, "/some-path", "got mismatch") + }) + + t.Run("with query", func(t *testing.T) { + // execute + q := url.Values{} + q.Set("foo", "bar") + q.Set("baz", "/quz") + got := GetPath("/some-path", &q) + + // test + assert.Equal(t, got, "/some-path?baz=%2Fquz&foo=bar", "got mismatch") + }) +} diff --git a/pkg/server/helpers/helpers.go b/pkg/server/helpers/uuid.go similarity index 100% rename from pkg/server/helpers/helpers.go rename to pkg/server/helpers/uuid.go diff --git a/pkg/server/job/job.go b/pkg/server/job/job.go index 45990a49..6f069ad6 100644 --- a/pkg/server/job/job.go +++ b/pkg/server/job/job.go @@ -23,8 +23,6 @@ import ( "github.com/dnote/dnote/pkg/clock" "github.com/dnote/dnote/pkg/server/config" - "github.com/dnote/dnote/pkg/server/job/remind" - "github.com/dnote/dnote/pkg/server/log" "github.com/dnote/dnote/pkg/server/mailer" "github.com/jinzhu/gorm" "github.com/pkg/errors" @@ -102,7 +100,6 @@ func scheduleJob(c *cron.Cron, spec string, cmd func()) { func (r *Runner) schedule(ch chan error) { // Schedule jobs cr := cron.New() - scheduleJob(cr, "0 8 * * *", func() { r.RemindNoRecentNotes() }) cr.Start() ch <- nil @@ -128,26 +125,3 @@ func (r *Runner) Do() error { return nil } - -// RemindNoRecentNotes remind users if no notes have been added recently -func (r *Runner) RemindNoRecentNotes() { - c := remind.Context{ - DB: r.DB, - Clock: r.Clock, - EmailTmpl: r.EmailTmpl, - EmailBackend: r.EmailBackend, - Config: r.Config, - } - - result, err := remind.DoInactive(c) - m := log.WithFields(log.Fields{ - "success_count": result.SuccessCount, - "failed_user_ids": result.FailedUserIDs, - }) - - if err == nil { - m.Info("successfully processed no recent note reminder job") - } else { - m.ErrorWrap(err, "error processing no recent note reminder job") - } -} diff --git a/pkg/server/job/remind/inactive.go b/pkg/server/job/remind/inactive.go deleted file mode 100644 index d0e9de01..00000000 --- a/pkg/server/job/remind/inactive.go +++ /dev/null @@ -1,210 +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 remind - -import ( - "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/log" - "github.com/dnote/dnote/pkg/server/mailer" - "github.com/jinzhu/gorm" - "github.com/pkg/errors" -) - -// Context holds data that repetition job needs in order to perform -type Context struct { - DB *gorm.DB - Clock clock.Clock - EmailTmpl mailer.Templates - EmailBackend mailer.Backend - Config config.Config -} - -type inactiveUserInfo struct { - userID int - email string - sampleNoteUUID string -} - -func (c *Context) sampleUserNote(userID int) (database.Note, error) { - var ret database.Note - // FIXME: ordering by random() requires a sequential scan on the whole table and does not scale - if err := c.DB.Where("user_id = ?", userID).Order("random() DESC").First(&ret).Error; err != nil { - return ret, errors.Wrap(err, "getting a random note") - } - - return ret, nil -} - -func (c *Context) getInactiveUserInfo() ([]inactiveUserInfo, error) { - ret := []inactiveUserInfo{} - - threshold := c.Clock.Now().AddDate(0, 0, -14).Unix() - - rows, err := c.DB.Raw(` -SELECT - notes.user_id AS user_id, - accounts.email, - SUM( - CASE - WHEN notes.created_at > to_timestamp(?) THEN 1 - ELSE 0 - END - ) AS recent_note_count, - COUNT(*) AS total_note_count -FROM notes -INNER JOIN accounts ON accounts.user_id = notes.user_id -WHERE accounts.email IS NOT NULL AND accounts.email_verified IS TRUE -GROUP BY notes.user_id, accounts.email`, threshold).Rows() - if err != nil { - return ret, errors.Wrap(err, "executing note count SQL query") - } - defer rows.Close() - for rows.Next() { - var userID, recentNoteCount, totalNoteCount int - var email string - if err := rows.Scan(&userID, &email, &recentNoteCount, &totalNoteCount); err != nil { - return nil, errors.Wrap(err, "scanning a row") - } - - if recentNoteCount == 0 && totalNoteCount > 0 { - note, err := c.sampleUserNote(userID) - if err != nil { - return nil, errors.Wrap(err, "sampling user note") - } - - ret = append(ret, inactiveUserInfo{ - userID: userID, - email: email, - sampleNoteUUID: note.UUID, - }) - } - } - - return ret, nil -} - -func (c *Context) canNotify(info inactiveUserInfo) (bool, error) { - var pref database.EmailPreference - if err := c.DB.Where("user_id = ?", info.userID).First(&pref).Error; err != nil { - return false, errors.Wrap(err, "getting email preference") - } - - if !pref.InactiveReminder { - return false, nil - } - - var notif database.Notification - conn := c.DB.Where("user_id = ? AND type = ?", info.userID, mailer.EmailTypeInactiveReminder).Order("created_at DESC").First(¬if) - - if conn.RecordNotFound() { - return true, nil - } else if err := conn.Error; err != nil { - return false, errors.Wrap(err, "checking cooldown") - } - - t := c.Clock.Now().AddDate(0, 0, -14) - if notif.CreatedAt.Before(t) { - return true, nil - } - - return false, nil -} - -func (c *Context) process(info inactiveUserInfo) error { - ok, err := c.canNotify(info) - if err != nil { - return errors.Wrap(err, "checking if user can be notified") - } - if !ok { - return nil - } - - sender, err := app.GetSenderEmail(c.Config, "noreply@getdnote.com") - if err != nil { - return errors.Wrap(err, "getting sender email") - } - - tok, err := mailer.GetToken(c.DB, info.userID, database.TokenTypeEmailPreference) - if err != nil { - return errors.Wrap(err, "getting email token") - } - - tmplData := mailer.InactiveReminderTmplData{ - WebURL: c.Config.WebURL, - SampleNoteUUID: info.sampleNoteUUID, - Token: tok.Value, - } - body, err := c.EmailTmpl.Execute(mailer.EmailTypeInactiveReminder, mailer.EmailKindText, tmplData) - if err != nil { - return errors.Wrap(err, "executing inactive email template") - } - - if err := c.EmailBackend.Queue("Your Dnote stopped growing", sender, []string{info.email}, mailer.EmailKindText, body); err != nil { - return errors.Wrap(err, "queueing email") - } - - if err := c.DB.Create(&database.Notification{ - Type: mailer.EmailTypeInactiveReminder, - UserID: info.userID, - }).Error; err != nil { - return errors.Wrap(err, "creating notification") - } - - return nil -} - -// Result holds the result of the job -type Result struct { - SuccessCount int - FailedUserIDs []int -} - -// DoInactive sends reminder for users with no recent notes -func DoInactive(c Context) (Result, error) { - log.Info("performing reminder for no recent notes") - - result := Result{} - items, err := c.getInactiveUserInfo() - if err != nil { - return result, errors.Wrap(err, "getting inactive user information") - } - - log.WithFields(log.Fields{ - "user_count": len(items), - }).Info("counted inactive users") - - for _, item := range items { - err := c.process(item) - - if err == nil { - result.SuccessCount = result.SuccessCount + 1 - } else { - log.WithFields(log.Fields{ - "user_id": item.userID, - }).ErrorWrap(err, "Could not process no recent notes reminder") - - result.FailedUserIDs = append(result.FailedUserIDs, item.userID) - } - } - - return result, nil -} diff --git a/pkg/server/job/remind/inactive_test.go b/pkg/server/job/remind/inactive_test.go deleted file mode 100644 index d79095c5..00000000 --- a/pkg/server/job/remind/inactive_test.go +++ /dev/null @@ -1,194 +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 remind - -import ( - "os" - "sort" - "testing" - "time" - - "github.com/dnote/dnote/pkg/assert" - "github.com/dnote/dnote/pkg/clock" - "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/mailer" - "github.com/dnote/dnote/pkg/server/testutils" - "github.com/pkg/errors" -) - -func getTestContext(c clock.Clock, be *testutils.MockEmailbackendImplementation) Context { - emailTmplDir := os.Getenv("DNOTE_TEST_EMAIL_TEMPLATE_DIR") - - con := Context{ - DB: testutils.DB, - Clock: c, - EmailTmpl: mailer.NewTemplates(&emailTmplDir), - EmailBackend: be, - } - - return con -} - -func TestDoInactive(t *testing.T) { - defer testutils.ClearData(testutils.DB) - - t1 := time.Now() - - // u1 is an active user - u1 := testutils.SetupUserData() - a1 := testutils.SetupAccountData(u1, "alice@example.com", "pass1234") - testutils.MustExec(t, testutils.DB.Model(&a1).Update("email_verified", true), "setting email verified") - testutils.MustExec(t, testutils.DB.Save(&database.EmailPreference{UserID: u1.ID, InactiveReminder: true}), "preparing email preference") - - b1 := database.Book{ - UserID: u1.ID, - Label: "js", - } - testutils.MustExec(t, testutils.DB.Save(&b1), "preparing b1") - n1 := database.Note{ - BookUUID: b1.UUID, - UserID: u1.ID, - } - testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1") - - // u2 is an inactive user - u2 := testutils.SetupUserData() - a2 := testutils.SetupAccountData(u2, "bob@example.com", "pass1234") - testutils.MustExec(t, testutils.DB.Model(&a2).Update("email_verified", true), "setting email verified") - testutils.MustExec(t, testutils.DB.Save(&database.EmailPreference{UserID: u2.ID, InactiveReminder: true}), "preparing email preference") - - b2 := database.Book{ - UserID: u2.ID, - Label: "css", - } - testutils.MustExec(t, testutils.DB.Save(&b2), "preparing b2") - n2 := database.Note{ - UserID: u2.ID, - BookUUID: b2.UUID, - } - testutils.MustExec(t, testutils.DB.Save(&n2), "preparing n2") - testutils.MustExec(t, testutils.DB.Model(&n2).Update("created_at", t1.AddDate(0, 0, -15)), "preparing n2") - - // u3 is an inactive user with inactive alert email preference disabled - u3 := testutils.SetupUserData() - a3 := testutils.SetupAccountData(u3, "alice@example.com", "pass1234") - testutils.MustExec(t, testutils.DB.Model(&a3).Update("email_verified", true), "setting email verified") - emailPref3 := database.EmailPreference{UserID: u3.ID} - testutils.MustExec(t, testutils.DB.Save(&emailPref3), "preparing email preference") - testutils.MustExec(t, testutils.DB.Model(&emailPref3).Update(map[string]interface{}{"inactive_reminder": false}), "updating email preference") - - b3 := database.Book{ - UserID: u3.ID, - Label: "js", - } - testutils.MustExec(t, testutils.DB.Save(&b3), "preparing b3") - n3 := database.Note{ - BookUUID: b3.UUID, - UserID: u3.ID, - } - testutils.MustExec(t, testutils.DB.Save(&n3), "preparing n3") - testutils.MustExec(t, testutils.DB.Model(&n3).Update("created_at", t1.AddDate(0, 0, -15)), "preparing n3") - - c := clock.NewMock() - c.SetNow(t1) - be := &testutils.MockEmailbackendImplementation{} - - con := getTestContext(c, be) - if _, err := DoInactive(con); err != nil { - t.Fatal(errors.Wrap(err, "performing")) - } - - assert.Equalf(t, len(be.Emails), 1, "email queue count mismatch") - assert.DeepEqual(t, be.Emails[0].To, []string{a2.Email.String}, "email address mismatch") -} - -func TestDoInactive_Cooldown(t *testing.T) { - defer testutils.ClearData(testutils.DB) - - // setup sets up an inactive user - setup := func(t *testing.T, now time.Time, email string) database.User { - u := testutils.SetupUserData() - a := testutils.SetupAccountData(u, email, "pass1234") - testutils.MustExec(t, testutils.DB.Model(&a).Update("email_verified", true), "setting email verified") - testutils.MustExec(t, testutils.DB.Save(&database.EmailPreference{UserID: u.ID, InactiveReminder: true}), "preparing email preference") - - b := database.Book{ - UserID: u.ID, - Label: "css", - } - testutils.MustExec(t, testutils.DB.Save(&b), "preparing book") - n := database.Note{ - UserID: u.ID, - BookUUID: b.UUID, - } - testutils.MustExec(t, testutils.DB.Save(&n), "preparing note") - testutils.MustExec(t, testutils.DB.Model(&n).Update("created_at", now.AddDate(0, 0, -15)), "preparing note") - - return u - } - - // Set up - now := time.Now() - - setup(t, now, "alice@example.com") - - bob := setup(t, now, "bob@example.com") - bobNotif := database.Notification{Type: mailer.EmailTypeInactiveReminder, UserID: bob.ID} - testutils.MustExec(t, testutils.DB.Create(&bobNotif), "preparing inactive notification for bob") - testutils.MustExec(t, testutils.DB.Model(&bobNotif).Update("created_at", now.AddDate(0, 0, -7)), "preparing created_at for inactive notification for bob") - - chuck := setup(t, now, "chuck@example.com") - chuckNotif := database.Notification{Type: mailer.EmailTypeInactiveReminder, UserID: chuck.ID} - testutils.MustExec(t, testutils.DB.Create(&chuckNotif), "preparing inactive notification for chuck") - testutils.MustExec(t, testutils.DB.Model(&chuckNotif).Update("created_at", now.AddDate(0, 0, -15)), "preparing created_at for inactive notification for chuck") - - dan := setup(t, now, "dan@example.com") - danNotif1 := database.Notification{Type: mailer.EmailTypeInactiveReminder, UserID: dan.ID} - testutils.MustExec(t, testutils.DB.Create(&danNotif1), "preparing inactive notification 1 for dan") - testutils.MustExec(t, testutils.DB.Model(&danNotif1).Update("created_at", now.AddDate(0, 0, -10)), "preparing created_at for inactive notification for dan") - danNotif2 := database.Notification{Type: mailer.EmailTypeInactiveReminder, UserID: dan.ID} - testutils.MustExec(t, testutils.DB.Create(&danNotif2), "preparing inactive notification 2 for dan") - testutils.MustExec(t, testutils.DB.Model(&danNotif2).Update("created_at", now.AddDate(0, 0, -15)), "preparing created_at for inactive notification for dan") - - c := clock.NewMock() - c.SetNow(now) - be := &testutils.MockEmailbackendImplementation{} - - // Execute - con := getTestContext(c, be) - if _, err := DoInactive(con); err != nil { - t.Fatal(errors.Wrap(err, "performing")) - } - - // Test - assert.Equalf(t, len(be.Emails), 2, "email queue count mismatch") - - var recipients []string - for _, email := range be.Emails { - recipients = append(recipients, email.To[0]) - } - sort.SliceStable(recipients, func(i, j int) bool { - r1 := recipients[i] - r2 := recipients[j] - - return r1 < r2 - }) - - assert.DeepEqual(t, recipients, []string{"alice@example.com", "chuck@example.com"}, "email address mismatch") -} diff --git a/pkg/server/mailer/templates/src/reset_password.txt b/pkg/server/mailer/templates/src/reset_password.txt index a66c0ba8..3bc34850 100644 --- a/pkg/server/mailer/templates/src/reset_password.txt +++ b/pkg/server/mailer/templates/src/reset_password.txt @@ -6,4 +6,4 @@ Please click on the following link, or paste this into your browser to complete You can reply to this message, if you have questions. -- Sung (Maker of Dnote) +- Dnote team diff --git a/pkg/server/mailer/templates/src/reset_password_alert.txt b/pkg/server/mailer/templates/src/reset_password_alert.txt index f1d7389b..3aa9bdd6 100644 --- a/pkg/server/mailer/templates/src/reset_password_alert.txt +++ b/pkg/server/mailer/templates/src/reset_password_alert.txt @@ -6,4 +6,4 @@ If you did not initiate this password change, please notify us by replying, and Thanks. -- Sung (Maker of Dnote) +- Dnote team diff --git a/pkg/server/mailer/templates/src/subscription_confirmation.txt b/pkg/server/mailer/templates/src/subscription_confirmation.txt index 03e98a66..e212211f 100644 --- a/pkg/server/mailer/templates/src/subscription_confirmation.txt +++ b/pkg/server/mailer/templates/src/subscription_confirmation.txt @@ -3,10 +3,10 @@ Hi, thanks for signing up for Dnote Pro. Now you can take your notes with you wherever you go! * Synchronize data among an unlimited number of machines. -* Access notes anywhere via the web interface. +* Manage notes via REST API. Your account is "{{ .AccountEmail }}". Log in at {{ .WebURL }}/login Thank you for using Dnote. Your support makes it possible to develop it for developers around the world. -- Sung (Maker of Dnote) +- Dnote team diff --git a/pkg/server/mailer/templates/src/verify_email.txt b/pkg/server/mailer/templates/src/verify_email.txt index 8e40bf89..a85ab705 100644 --- a/pkg/server/mailer/templates/src/verify_email.txt +++ b/pkg/server/mailer/templates/src/verify_email.txt @@ -1,9 +1,9 @@ Hi. -Welcome to Dnote! To verify your email so that you can automate spaced reptition, visit the following link: +Welcome to Dnote! To verify your email, visit the following link: {{ .WebURL }}/verify-email/{{ .Token }} -Thanks for using my software. +Thanks for using Dnote. -- Sung (Maker of Dnote) +- Dnote team diff --git a/pkg/server/mailer/templates/src/welcome.txt b/pkg/server/mailer/templates/src/welcome.txt index daba304a..7a33207a 100644 --- a/pkg/server/mailer/templates/src/welcome.txt +++ b/pkg/server/mailer/templates/src/welcome.txt @@ -13,4 +13,4 @@ Dnote is open source and you can see the source code at https://github.com/dnote Feel free to reply anytime. Thanks for using Dnote. -- Sung (Maker of Dnote) +- Dnote team diff --git a/pkg/server/main.go b/pkg/server/main.go index f1721009..cfd33f15 100644 --- a/pkg/server/main.go +++ b/pkg/server/main.go @@ -21,73 +21,26 @@ package main import ( "flag" "fmt" + "io/ioutil" "log" "net/http" "github.com/dnote/dnote/pkg/clock" - "github.com/dnote/dnote/pkg/server/api" "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/buildinfo" "github.com/dnote/dnote/pkg/server/config" + "github.com/dnote/dnote/pkg/server/controllers" "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/job" "github.com/dnote/dnote/pkg/server/mailer" - "github.com/dnote/dnote/pkg/server/web" + "github.com/dnote/dnote/pkg/server/views" "github.com/jinzhu/gorm" - "github.com/gobuffalo/packr/v2" "github.com/pkg/errors" ) -var versionTag = "master" -var port = flag.String("port", "3000", "port to connect to") -var rootBox *packr.Box - -func init() { - rootBox = packr.New("root", "../../web/public") -} - -func mustFind(box *packr.Box, path string) []byte { - b, err := rootBox.Find(path) - if err != nil { - panic(errors.Wrapf(err, "getting file content for %s", path)) - } - - return b -} - -func initWebContext(db *gorm.DB) web.Context { - staticBox := packr.New("static", "../../web/public/static") - - return web.Context{ - DB: db, - IndexHTML: mustFind(rootBox, "index.html"), - RobotsTxt: mustFind(rootBox, "robots.txt"), - ServiceWorkerJs: mustFind(rootBox, "service-worker.js"), - StaticFileSystem: staticBox, - } -} - -func initServer(a app.App) (*http.ServeMux, error) { - apiRouter, err := api.NewRouter(&api.API{App: &a}) - if err != nil { - return nil, errors.Wrap(err, "initializing router") - } - - webCtx := initWebContext(a.DB) - webHandlers, err := web.Init(webCtx) - if err != nil { - return nil, errors.Wrap(err, "initializing web handlers") - } - - mux := http.NewServeMux() - mux.Handle("/api/", http.StripPrefix("/api", apiRouter)) - mux.Handle("/static/", webHandlers.GetStatic) - mux.HandleFunc("/service-worker.js", webHandlers.GetServiceWorker) - mux.HandleFunc("/robots.txt", webHandlers.GetRobots) - mux.HandleFunc("/", webHandlers.GetRoot) - - return mux, nil -} +var pageDir = flag.String("pageDir", "views", "the path to a directory containing page templates") +var staticDir = flag.String("staticDir", "./static/", "the path to the static directory ") func initDB(c config.Config) *gorm.DB { db, err := gorm.Open("postgres", c.DB.GetConnectionStr()) @@ -99,15 +52,28 @@ func initDB(c config.Config) *gorm.DB { return db } -func initApp(c config.Config) app.App { - db := initDB(c) +func mustReadFile(path string) []byte { + ret, err := ioutil.ReadFile(path) + if err != nil { + panic(errors.Wrap(err, "reading file")) + } + + return ret +} + +func initApp(cfg config.Config) app.App { + db := initDB(cfg) + + files := map[string][]byte{} + files[views.ServerErrorPageFileKey] = mustReadFile(fmt.Sprintf("%s/500.html", cfg.StaticDir)) return app.App{ DB: db, Clock: clock.New(), EmailTemplates: mailer.NewTemplates(nil), EmailBackend: &mailer.SimpleBackendImplementation{}, - Config: c, + Config: cfg, + Files: files, } } @@ -124,34 +90,43 @@ func runJob(a app.App) error { } func startCmd() { - c := config.Load() + cfg := config.Load() + cfg.SetPageTemplateDir(*pageDir) + cfg.SetStaticDir(*staticDir) + cfg.SetAssetBaseURL("/static") - app := initApp(c) + app := initApp(cfg) defer app.DB.Close() if err := database.Migrate(app.DB); err != nil { panic(errors.Wrap(err, "running migrations")) } - if err := runJob(app); err != nil { panic(errors.Wrap(err, "running job")) } - srv, err := initServer(app) - if err != nil { - panic(errors.Wrap(err, "initializing server")) + ctl := controllers.New(&app, *pageDir) + rc := controllers.RouteConfig{ + WebRoutes: controllers.NewWebRoutes(&app, ctl), + APIRoutes: controllers.NewAPIRoutes(&app, ctl), + Controllers: ctl, } - log.Printf("Dnote version %s is running on port %s", versionTag, *port) - log.Fatalln(http.ListenAndServe(":"+*port, srv)) + r, err := controllers.NewRouter(&app, rc) + if err != nil { + panic(errors.Wrap(err, "initializing router")) + } + + log.Printf("Dnote version %s is running on port %s", buildinfo.Version, cfg.Port) + log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%s", cfg.Port), r)) } func versionCmd() { - fmt.Printf("dnote-server-%s\n", versionTag) + fmt.Printf("dnote-server-%s\n", buildinfo.Version) } func rootCmd() { - fmt.Printf(`Dnote Server - A simple personal knowledge base + fmt.Printf(`Dnote server - a simple personal knowledge base Usage: dnote-server [command] diff --git a/pkg/server/handlers/auth.go b/pkg/server/middleware/auth.go similarity index 76% rename from pkg/server/handlers/auth.go rename to pkg/server/middleware/auth.go index 83946dce..79d4d780 100644 --- a/pkg/server/handlers/auth.go +++ b/pkg/server/middleware/auth.go @@ -16,15 +16,16 @@ * along with Dnote. If not, see . */ -package handlers +package middleware import ( - "context" "net/http" + "net/url" "strings" "time" "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/context" "github.com/dnote/dnote/pkg/server/database" "github.com/dnote/dnote/pkg/server/helpers" "github.com/dnote/dnote/pkg/server/log" @@ -82,11 +83,18 @@ type AuthParams struct { // Auth is an authentication middleware func Auth(a *app.App, next http.HandlerFunc, p *AuthParams) http.HandlerFunc { + next = WithAccount(a, next) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, ok, err := AuthWithSession(a.DB, r, p) + user, ok, err := AuthWithSession(a.DB, r) if !ok { if p != nil && p.RedirectGuestsToLogin { - http.Redirect(w, r, "/login", http.StatusFound) + + q := url.Values{} + q.Set("referrer", r.URL.Path) + path := helpers.GetPath("/login", &q) + + http.Redirect(w, r, path, http.StatusFound) return } @@ -105,7 +113,24 @@ func Auth(a *app.App, next http.HandlerFunc, p *AuthParams) http.HandlerFunc { } } - ctx := context.WithValue(r.Context(), helpers.KeyUser, user) + ctx := context.WithUser(r.Context(), &user) + next.ServeHTTP(w, r.WithContext(ctx)) + }) + +} + +func WithAccount(a *app.App, next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + user := context.User(r.Context()) + + var account database.Account + if err := a.DB.Where("user_id = ?", user.ID).First(&account).Error; err != nil { + DoError(w, "finding account", err, http.StatusInternalServerError) + return + } + + ctx := context.WithAccount(r.Context(), &account) + next.ServeHTTP(w, r.WithContext(ctx)) }) } @@ -122,10 +147,10 @@ func TokenAuth(a *app.App, next http.HandlerFunc, tokenType string, p *AuthParam ctx := r.Context() if ok { - ctx = context.WithValue(ctx, helpers.KeyToken, token) + ctx = context.WithToken(ctx, &token) } else { // If token-based auth fails, fall back to session-based auth - user, ok, err = AuthWithSession(a.DB, r, p) + user, ok, err = AuthWithSession(a.DB, r) if err != nil { DoError(w, "authenticating with session", err, http.StatusInternalServerError) return @@ -144,13 +169,13 @@ func TokenAuth(a *app.App, next http.HandlerFunc, tokenType string, p *AuthParam } } - ctx = context.WithValue(ctx, helpers.KeyUser, user) + ctx = context.WithUser(ctx, &user) next.ServeHTTP(w, r.WithContext(ctx)) }) } // AuthWithSession performs user authentication with session -func AuthWithSession(db *gorm.DB, r *http.Request, p *AuthParams) (database.User, bool, error) { +func AuthWithSession(db *gorm.DB, r *http.Request) (database.User, bool, error) { var user database.User sessionKey, err := GetCredential(r) @@ -184,3 +209,19 @@ func AuthWithSession(db *gorm.DB, r *http.Request, p *AuthParams) (database.User return user, true, nil } + +func GuestOnly(a *app.App, next http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, ok, err := AuthWithSession(a.DB, r) + if err != nil { + // log the error and continue + log.ErrorWrap(err, "authenticating with session") + } + + if ok { + http.Redirect(w, r, "/", http.StatusFound) + } else { + next.ServeHTTP(w, r) + } + }) +} diff --git a/pkg/server/handlers/helpers.go b/pkg/server/middleware/helpers.go similarity index 91% rename from pkg/server/handlers/helpers.go rename to pkg/server/middleware/helpers.go index 8c0d51a7..f958d573 100644 --- a/pkg/server/handlers/helpers.go +++ b/pkg/server/middleware/helpers.go @@ -16,10 +16,9 @@ * along with Dnote. If not, see . */ -package handlers +package middleware import ( - "encoding/json" "net/http" "strings" "time" @@ -90,16 +89,6 @@ func DoError(w http.ResponseWriter, msg string, err error, statusCode int) { http.Error(w, statusText, statusCode) } -// RespondJSON encodes the given payload into a JSON format and writes it to the given response writer -func RespondJSON(w http.ResponseWriter, statusCode int, payload interface{}) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(statusCode) - - if err := json.NewEncoder(w).Encode(payload); err != nil { - DoError(w, "encoding response", err, http.StatusInternalServerError) - } -} - // NotSupported is the handler for the route that is no longer supported func NotSupported(w http.ResponseWriter, r *http.Request) { http.Error(w, "API version is not supported. Please upgrade your client.", http.StatusGone) diff --git a/pkg/server/handlers/helpers_test.go b/pkg/server/middleware/helpers_test.go similarity index 98% rename from pkg/server/handlers/helpers_test.go rename to pkg/server/middleware/helpers_test.go index f82e6ea0..1eb9fe7a 100644 --- a/pkg/server/handlers/helpers_test.go +++ b/pkg/server/middleware/helpers_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package middleware import ( "fmt" @@ -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, @@ -405,13 +407,15 @@ func TestAuthMiddleware_RedirectGuestsToLogin(t *testing.T) { // test assert.Equal(t, res.StatusCode, http.StatusFound, "status code mismatch") - assert.Equal(t, res.Header.Get("Location"), "/login", "location header mismatch") + assert.Equal(t, res.Header.Get("Location"), "/login?referrer=%2F", "location header mismatch") }) t.Run("logged in user", func(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/handlers/limit.go b/pkg/server/middleware/limit.go similarity index 92% rename from pkg/server/handlers/limit.go rename to pkg/server/middleware/limit.go index 9859e7e2..bdc5de40 100644 --- a/pkg/server/handlers/limit.go +++ b/pkg/server/middleware/limit.go @@ -16,10 +16,11 @@ * along with Dnote. If not, see . */ -package handlers +package middleware import ( "net/http" + "os" "strings" "sync" "time" @@ -122,3 +123,14 @@ func Limit(next http.Handler) http.HandlerFunc { next.ServeHTTP(w, r) }) } + +// ApplyLimit applies rate limit conditionally +func ApplyLimit(h http.HandlerFunc, rateLimit bool) http.Handler { + ret := h + + if rateLimit && os.Getenv("GO_ENV") != "TEST" { + ret = Limit(ret) + } + + return ret +} diff --git a/pkg/server/handlers/logging.go b/pkg/server/middleware/logging.go similarity index 97% rename from pkg/server/handlers/logging.go rename to pkg/server/middleware/logging.go index 98e36597..85bfbf32 100644 --- a/pkg/server/handlers/logging.go +++ b/pkg/server/middleware/logging.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package handlers +package middleware import ( "fmt" @@ -39,7 +39,7 @@ func (w *logResponseWriter) WriteHeader(code int) { w.ResponseWriter.WriteHeader(code) } -// Logging is a logging middleware +// logging is a logging middleware func Logging(inner http.Handler) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() diff --git a/pkg/server/api/main_test.go b/pkg/server/middleware/main_test.go similarity index 98% rename from pkg/server/api/main_test.go rename to pkg/server/middleware/main_test.go index cca6ae29..08a3acf1 100644 --- a/pkg/server/api/main_test.go +++ b/pkg/server/middleware/main_test.go @@ -16,7 +16,7 @@ * along with Dnote. If not, see . */ -package api +package middleware import ( "os" diff --git a/pkg/server/middleware/middleware.go b/pkg/server/middleware/middleware.go new file mode 100644 index 00000000..44f6377a --- /dev/null +++ b/pkg/server/middleware/middleware.go @@ -0,0 +1,76 @@ +package middleware + +import ( + "net/http" + "net/url" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/gorilla/schema" +) + +// Middleware is a middleware for request handlers +type Middleware func(h http.Handler, app *app.App, rateLimit bool) http.Handler + +type payload struct { + Method string `schema:"_method"` +} + +func parseValues(values url.Values, dst interface{}) error { + dec := schema.NewDecoder() + + // Ignore CSRF token field + dec.IgnoreUnknownKeys(true) + + if err := dec.Decode(dst, values); err != nil { + return err + } + + return nil +} + +// methodOverrideKey is the form key for overriding the method +var methodOverrideKey = "_method" + +// methodOverride overrides the request's method to simulate form actions that +// are not natively supported by web browsers +func methodOverride(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodPost { + method := r.PostFormValue(methodOverrideKey) + + if method == http.MethodPut || method == http.MethodPatch || method == http.MethodDelete { + r.Method = method + } + } + + next.ServeHTTP(w, r) + }) +} + +// WebMw is the middleware for the web +func WebMw(h http.Handler, app *app.App, rateLimit bool) http.Handler { + ret := h + + ret = ApplyLimit(ret.ServeHTTP, rateLimit) + + return ret +} + +// APIMw is the middleware for the API +func APIMw(h http.Handler, app *app.App, rateLimit bool) http.Handler { + ret := h + + ret = ApplyLimit(ret.ServeHTTP, rateLimit) + + return ret +} + +// Global is the middleware for all routes +func Global(h http.Handler) http.Handler { + ret := h + + ret = Logging(ret) + ret = methodOverride(ret) + + return ret +} diff --git a/pkg/server/net/writer.go b/pkg/server/net/writer.go new file mode 100644 index 00000000..912d44d4 --- /dev/null +++ b/pkg/server/net/writer.go @@ -0,0 +1,31 @@ +package net + +import ( + "github.com/dnote/dnote/pkg/server/log" + "net/http" +) + +// LifecycleWriter wraps http.ResponseWriter to track state of the http response. +// The optional interfaces of http.ResponseWriter are lost because of the wrapping, and +// such interfaces should be implemented if needed. (i.e. http.Pusher, http.Flusher, etc.) +type LifecycleWriter struct { + http.ResponseWriter + StatusCode int +} + +// WriteHeader wraps the WriteHeader call and marks the response state as done. +func (w *LifecycleWriter) WriteHeader(code int) { + w.StatusCode = code + w.ResponseWriter.WriteHeader(code) +} + +// IsHeaderWritten returns true if a response has been written. +func IsHeaderWritten(w http.ResponseWriter) bool { + if lw, ok := w.(*LifecycleWriter); ok { + return lw.StatusCode != 0 + } + + // the response writer must have been wrapped in the middleware chain. + log.Error("unable to log because writer is not a LifecycleWriter") + return false +} diff --git a/pkg/server/operations/notes.go b/pkg/server/operations/notes.go index 22bb8468..1106ab5e 100644 --- a/pkg/server/operations/notes.go +++ b/pkg/server/operations/notes.go @@ -27,7 +27,7 @@ import ( ) // GetNote retrieves a note for the given user -func GetNote(db *gorm.DB, uuid string, user database.User) (database.Note, bool, error) { +func GetNote(db *gorm.DB, uuid string, user *database.User) (database.Note, bool, error) { zeroNote := database.Note{} if !helpers.ValidateUUID(uuid) { return zeroNote, false, nil @@ -45,7 +45,7 @@ func GetNote(db *gorm.DB, uuid string, user database.User) (database.Note, bool, return zeroNote, false, errors.Wrap(err, "finding note") } - if ok := permissions.ViewNote(&user, note); !ok { + if ok := permissions.ViewNote(user, note); !ok { return zeroNote, false, nil } diff --git a/pkg/server/operations/notes_test.go b/pkg/server/operations/notes_test.go index 0ea93878..ceb0728f 100644 --- a/pkg/server/operations/notes_test.go +++ b/pkg/server/operations/notes_test.go @@ -107,7 +107,7 @@ func TestGetNote(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - note, ok, err := GetNote(testutils.DB, tc.note.UUID, tc.user) + note, ok, err := GetNote(testutils.DB, tc.note.UUID, &tc.user) if err != nil { t.Fatal(errors.Wrap(err, "executing")) } @@ -141,7 +141,7 @@ func TestGetNote_nonexistent(t *testing.T) { testutils.MustExec(t, testutils.DB.Save(&n1), "preparing n1") nonexistentUUID := "4fd19336-671e-4ff3-8f22-662b80e22edd" - note, ok, err := GetNote(testutils.DB, nonexistentUUID, user) + note, ok, err := GetNote(testutils.DB, nonexistentUUID, &user) if err != nil { t.Fatal(errors.Wrap(err, "executing")) } diff --git a/pkg/server/static/main.css b/pkg/server/static/main.css new file mode 100644 index 00000000..131ab48d --- /dev/null +++ b/pkg/server/static/main.css @@ -0,0 +1,12 @@ +/*! + * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) + */*,*::before,*::after{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0)}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0 !important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[title],abbr[data-original-title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul,dl{margin-top:0;margin-bottom:1rem}ol ol,ul ul,ol ul,ul ol{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}a{color:#007bff;text-decoration:none;background-color:rgba(0,0,0,0)}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):hover,a:not([href]):not([tabindex]):focus{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre,code,kbd,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}input,button,select,optgroup,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button:not(:disabled),[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled){cursor:pointer}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{padding:0;border-style:none}input[type=radio],input[type=checkbox]{box-sizing:border-box;padding:0}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none !important}/*! + * Bootstrap Grid v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */html{box-sizing:border-box;-ms-overflow-style:scrollbar}*,*::before,*::after{box-sizing:inherit}.container-wide{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media(min-width: 576px){.container-wide{max-width:540px}}@media(min-width: 768px){.container-wide{max-width:720px}}@media(min-width: 992px){.container-wide{max-width:960px}}@media(min-width: 1200px){.container-wide{max-width:1040px}}@media(min-width: 1440px){.container-wide{max-width:1280px}}@media(min-width: 1800px){.container-wide{max-width:1660px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media(min-width: 576px){.container{max-width:540px}}@media(min-width: 768px){.container{max-width:720px}}@media(min-width: 992px){.container{max-width:960px}}@media(min-width: 1200px){.container{max-width:1280px}}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col-1,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-10,.col-11,.col-12,.col,.col-auto,.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm,.col-sm-auto,.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12,.col-md,.col-md-auto,.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg,.col-lg-auto,.col-xl-1,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media(min-width: 576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media(min-width: 768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media(min-width: 992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media(min-width: 1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}.alert{position:relative;padding:1.75rem 1.25rem;border:1px solid rgba(0,0,0,0)}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}.alert-slim{padding:.75rem 1.25rem}:export{mdBreakpoint:576px;smBreakpoint:321px}.button{position:relative;display:inline-block;text-align:center;white-space:nowrap;vertical-align:middle;user-select:none;border-image:initial;transition-property:color,box-shadow;transition-duration:.2s;transition-timing-function:ease-in-out;text-decoration:none;cursor:pointer}.button:not(.button-no-ui){border-width:1px;border-style:solid;border-color:rgba(0,0,0,0)}.button:not(:disabled):hover{text-decoration:none}.button:disabled{cursor:not-allowed;opacity:.6}.button:focus{outline:2px dotted #9c9c9c}button:disabled{cursor:not-allowed;opacity:.6}.button-small{font-size:14px;font-size:1.4rem;padding:.4rem 1.2rem}@media(min-width: 576px){.button-small{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.button-small{font-size:14px;font-size:1.4rem}}.button-normal{padding:.8rem 1.6rem}.button-large{font-size:18px;font-size:1.8rem;padding:.8rem 2.4rem}@media(min-width: 576px){.button-large{font-size:18px;font-size:1.8rem}}@media(min-width: 992px){.button-large{font-size:18px;font-size:1.8rem}}@media(min-width: 576px){.button-large{padding:1.2rem 3.6rem}}@media(min-width: 992px){.button-large{padding:1.2rem 4.8rem}}.button-xlarge{font-size:19.2px;font-size:1.92rem;padding:1.6rem 2.4rem}@media(min-width: 576px){.button-xlarge{font-size:21.6px;font-size:2.16rem}}@media(min-width: 992px){.button-xlarge{font-size:24px;font-size:2.4rem}}@media(min-width: 576px){.button-xlarge{padding:1.2rem 3.6rem}}@media(min-width: 992px){.button-xlarge{padding:1.6rem 4.8rem}}.button-first{color:#fff;background-color:#333745}.button-first:not(:disabled):hover{color:#fff;background-color:#282b36;box-shadow:0px 0px 4px 2px #cacaca}.button-first-outline{background:rgba(0,0,0,0);color:#333745}.button-first-outline:not(.button-no-ui){border-color:#333745;border-width:2px}.button-first-outline:not(:disabled):hover{color:#333745;box-shadow:0px 0px 4px 2px #cacaca}.button-second{color:#2a2a2a;background-color:#e7e7e7}.button-second:not(:disabled):hover{color:#2a2a2a;background-color:#dadada;box-shadow:0px 0px 4px 2px #cacaca}.button-second-outline{background:rgba(0,0,0,0);color:#2a2a2a}.button-second-outline:not(.button-no-ui){border-color:#e7e7e7;border-width:2px}.button-second-outline:not(:disabled):hover{color:#2a2a2a;box-shadow:0px 0px 4px 2px #cacaca}.button-third{color:#fff;background-color:#0a4b73}.button-third:not(:disabled):hover{color:#fff;background-color:#083c5c;box-shadow:0px 0px 4px 2px #cacaca}.button-third-outline{background:rgba(0,0,0,0);color:#0a4b73}.button-third-outline:not(.button-no-ui){border-color:#0a4b73;border-width:2px}.button-third-outline:not(:disabled):hover{color:#0a4b73;box-shadow:0px 0px 4px 2px #cacaca}.button-danger{background:rgba(0,0,0,0);color:#cb2431;font-weight:600}.button-danger:not(.button-no-ui){border-color:#cb2431;border-width:2px}.button-danger:not(:disabled):hover{color:#cb2431;box-shadow:0px 0px 4px 2px #cacaca}.button-stretch{width:100%}.button~.button{margin-left:1.2rem}.button-no-ui{border:none;background:none;text-align:left;cursor:pointer}.button-no-padding{padding:0}.button-link{color:#6f53c0}.button-link:hover{color:#6143b7;text-decoration:underline}:export{mdBreakpoint:576px;smBreakpoint:321px}.Select{position:relative}.Select input::-webkit-contacts-auto-fill-button,.Select input::-webkit-credentials-auto-fill-button{display:none !important}.Select input::-ms-clear{display:none !important}.Select input::-ms-reveal{display:none !important}.Select,.Select div,.Select input,.Select span{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.Select.is-disabled .Select-arrow-zone{cursor:default;pointer-events:none;opacity:.35}.Select.is-disabled>.Select-control{background-color:#f9f9f9}.Select.is-disabled>.Select-control:hover{box-shadow:none}.Select.is-open>.Select-control{border-bottom-right-radius:0;border-bottom-left-radius:0;background:#fff}.Select.is-open>.Select-control .Select-arrow{top:-2px;border-color:rgba(0,0,0,0) rgba(0,0,0,0) #999;border-width:0 5px 5px}.Select.is-searchable.is-open>.Select-control{cursor:text}.Select.is-searchable.is-focused:not(.is-open)>.Select-control{cursor:text}.Select.is-focused>.Select-control{background:#fff}.Select.is-focused>.Select-control{border-top-color:#b6c9e9;border-bottom-color:#b6c9e9}.Select.is-focused:not(.is-open)>.Select-control{background:#fff}.Select.has-value.is-clearable.Select--single>.Select-control .Select-value{padding-right:42px}.Select.has-value.Select--single>.Select-control .Select-value .Select-value-label,.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value .Select-value-label{color:#333}.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label,.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label{cursor:pointer;text-decoration:none}.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label:hover,.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label:hover,.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label:focus,.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label:focus{color:#007eff;outline:none;text-decoration:underline}.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label:focus,.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label:focus{background:#fff}.Select.has-value.is-pseudo-focused .Select-input{opacity:0}.Select.is-open .Select-arrow,.Select .Select-arrow-zone:hover>.Select-arrow{border-top-color:#666}.Select.Select--rtl{direction:rtl;text-align:right}.Select-control{background-color:#fff;color:#333;cursor:default;display:table;border-top:1px solid #e2e2e2;border-bottom:1px solid #e2e2e2;height:36px;outline:none;overflow:hidden;position:relative;width:100%}.Select-control .Select-input:focus{outline:none;background:#fff}.Select-placeholder,.Select--single>.Select-control .Select-value{bottom:0;font-weight:300;color:#c3c3c3;left:0;line-height:34px;padding-left:16px;padding-right:10px;position:absolute;right:0;top:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.Select-input{height:34px;padding-left:10px;padding-right:10px;vertical-align:middle}.Select-input>input{width:100%;background:none rgba(0,0,0,0);border:0 none;box-shadow:none;cursor:default;display:inline-block;font-family:inherit;font-size:inherit;margin:0;outline:none;line-height:17px;padding:8px 0 12px;-webkit-appearance:none}.is-focused .Select-input>input{cursor:text}.has-value.is-pseudo-focused .Select-input{opacity:0}.Select-control:not(.is-searchable)>.Select-input{outline:none}.Select-loading-zone{cursor:pointer;display:table-cell;position:relative;text-align:center;vertical-align:middle;width:16px}.Select-loading{-webkit-animation:Select-animation-spin 400ms infinite linear;-o-animation:Select-animation-spin 400ms infinite linear;animation:Select-animation-spin 400ms infinite linear;width:16px;height:16px;box-sizing:border-box;border-radius:50%;border:2px solid #ccc;border-right-color:#333;display:inline-block;position:relative;vertical-align:middle}.Select-clear-zone{-webkit-animation:Select-animation-fadeIn 200ms;-o-animation:Select-animation-fadeIn 200ms;animation:Select-animation-fadeIn 200ms;color:#999;cursor:pointer;display:table-cell;position:relative;text-align:center;vertical-align:middle;width:17px}.Select-clear-zone:hover{color:#d0021b}.Select-clear{display:inline-block;font-size:18px;line-height:1}.Select--multi .Select-clear-zone{width:17px}.Select-arrow-zone{cursor:pointer;display:table-cell;position:relative;text-align:center;vertical-align:middle;width:25px;padding-right:5px}.Select--rtl .Select-arrow-zone{padding-right:0;padding-left:5px}.Select-arrow{border-color:#999 rgba(0,0,0,0) rgba(0,0,0,0);border-style:solid;border-width:5px 5px 2.5px;display:inline-block;height:0;width:0;position:relative}.Select-control>*:last-child{padding-right:5px}.Select--multi .Select-multi-value-wrapper{display:inline-block}.Select .Select-aria-only{position:absolute;display:inline-block;height:1px;width:1px;margin:-1px;clip:rect(0, 0, 0, 0);overflow:hidden;float:left}@-webkit-keyframes Select-animation-fadeIn{from{opacity:0}to{opacity:1}}@keyframes Select-animation-fadeIn{from{opacity:0}to{opacity:1}}.Select-menu-outer{border-bottom-right-radius:4px;border-bottom-left-radius:4px;background-color:#fff;border:1px solid #ccc;border-top-color:#e6e6e6;box-shadow:0 1px 0 rgba(0,0,0,.06);box-sizing:border-box;margin-top:-1px;max-height:200px;position:absolute;left:0;top:100%;width:100%;z-index:1;-webkit-overflow-scrolling:touch}.Select.is-open>.Select-menu-outer{border-top-color:#b6c9e9}.Select-menu{max-height:150px;overflow-y:auto}.Select-option{box-sizing:border-box;background-color:#fff;color:#666;cursor:pointer;display:block;padding:8px 10px}.Select-option:last-child{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.Select-option.is-selected{background-color:#f5faff;background-color:rgba(0,126,255,.04);color:#333}.Select-option.is-focused{background-color:#ebf5ff;background-color:rgba(0,126,255,.08);color:#333}.Select-option.is-disabled{color:#ccc;cursor:default}.Select-noresults{box-sizing:border-box;color:#999;cursor:default;display:block;padding:8px 10px}.Select--multi .Select-input{vertical-align:middle;margin-left:10px;padding:0}.Select--multi.Select--rtl .Select-input{margin-left:0;margin-right:10px}.Select--multi.has-value .Select-input{margin-left:5px}.Select--multi .Select-value{background-color:#ebf5ff;background-color:rgba(0,126,255,.08);border-radius:2px;border:1px solid #c2e0ff;border:1px solid rgba(0,126,255,.24);color:#007eff;display:inline-block;font-size:.9em;line-height:1.4;margin-left:5px;margin-top:5px;vertical-align:top}.Select--multi .Select-value-icon,.Select--multi .Select-value-label{display:inline-block;vertical-align:middle}.Select--multi .Select-value-label{border-bottom-right-radius:2px;border-top-right-radius:2px;cursor:default;padding:2px 5px}.Select--multi a.Select-value-label{color:#007eff;cursor:pointer;text-decoration:none}.Select--multi a.Select-value-label:hover{text-decoration:underline}.Select--multi .Select-value-icon{cursor:pointer;border-bottom-left-radius:2px;border-top-left-radius:2px;border-right:1px solid #c2e0ff;border-right:1px solid rgba(0,126,255,.24);padding:1px 5px 3px}.Select--multi .Select-value-icon:hover,.Select--multi .Select-value-icon:focus{background-color:#d8eafd;background-color:rgba(0,113,230,.08);color:#0071e6}.Select--multi .Select-value-icon:active{background-color:#c2e0ff;background-color:rgba(0,126,255,.24)}.Select--multi.Select--rtl .Select-value{margin-left:0;margin-right:5px}.Select--multi.Select--rtl .Select-value-icon{border-right:none;border-left:1px solid #c2e0ff;border-left:1px solid rgba(0,126,255,.24)}.Select--multi.is-disabled .Select-value{background-color:#fcfcfc;border:1px solid #e3e3e3;color:#333}.Select--multi.is-disabled .Select-value-icon{cursor:not-allowed;border-right:1px solid #e3e3e3}.Select--multi.is-disabled .Select-value-icon:hover,.Select--multi.is-disabled .Select-value-icon:focus,.Select--multi.is-disabled .Select-value-icon:active{background-color:#fcfcfc}@keyframes Select-animation-spin{to{transform:rotate(1turn)}}@-webkit-keyframes Select-animation-spin{to{-webkit-transform:rotate(1turn)}}:export{mdBreakpoint:576px;smBreakpoint:321px}:export{mdBreakpoint:576px;smBreakpoint:321px}@keyframes holderPulse{0%{opacity:.4}50%{opacity:1}100%{opacity:.4}}.holder{animation:holderPulse 800ms infinite;background:#f4f4f4}.holder.holder-dark{background:#e6e6e6}input[type=text]:disabled,input[type=email]:disabled,input[type=number]:disabled,input[type=password]:disabled,textarea:disabled{background-color:#f3f3f3;cursor:not-allowed}.list-unstyled{list-style:none;padding-left:0;margin-bottom:0}.sr-only{display:none}.scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}button img,button svg{display:block}.text-input{border:1px solid #d8d8d8;padding:.8rem 1.2rem;position:relative;border-radius:.4rem;display:block}.text-input::placeholder{color:#686868}.text-input:focus{border-color:#ecf4ff;box-shadow:inset 0 1px 2px rgba(24,31,35,.075),0 0 0 .2em rgba(4,100,210,.3);outline:none}.text-input-small{padding:.4rem 1.2rem}.text-input-medium{padding:.8rem 1.2rem}.text-input-stretch{width:100%}.label-full{width:100%}a{color:#6f53c0}a:hover{color:#6143b7}h1,h2,h3,h4,h5,h6{margin-bottom:0}@media(max-width: 991px){.container.mobile-fw{max-width:100%}}@media(max-width: 991px){.container.mobile-nopadding{padding-left:0;padding-right:0}.container.mobile-nopadding .row{margin-left:0;margin-right:0}.container.mobile-nopadding [class*=col-]{padding-left:0;padding-right:0}}html body{overflow-y:scroll}.page{padding-top:2rem;padding-bottom:2rem}.page.page-mobile-full{padding-top:0;padding-bottom:0}@media(min-width: 992px){.page.page-mobile-full{padding-top:3.2rem;padding-bottom:3.2rem}}.page-header{margin-top:2rem}.page-header.page-header-full{margin-bottom:2rem}@media(min-width: 992px){.page-header{margin-bottom:2rem;margin-top:0}}.form-select{appearance:none;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAYAAACEYr13AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACeSURBVHgBzZPBCYQwFERn2Qa2BEuwhJSyHawdrB1oB1qCV6uwhHj0qBXoBPwQRONXEXzwLoEXcCTAzfxogpPEdJyNcZCIWu8CO5+p8WOxoR9NnK3EYrEX/wOxuDmqUcSikejlXfCFfqie5ngE/ie4cVS/ibS0XB4anBhxSaKIU+xQBmLV8m6HZiW2OECEi4/JoXrO78AFHR1oTSvcxQTq7lVcue6CCAAAAABJRU5ErkJggg==");background-color:#fff;background-repeat:no-repeat;background-position:right 8px center;background-size:8px 10px;border:1px solid #d8d8d8;min-height:34px;padding:6px 8px;padding-right:24px;outline:none;vertical-align:middle;border-radius:4px;box-shadow:inset 0 1px 2px rgba(32,36,41,.08)}.form-select:focus{border-color:#2188ff;outline:none;box-shadow:inset 0 1px 2px rgba(32,36,41,.08),0 0 0 2px rgba(3,102,214,.3)}.form-select:disabled,.form-select.form-select-disabled{background-image:url("data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAYAAACEYr13AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAEKSURBVHgBzVTNDYIwFC4NB46OwAi4gY7gETgoE6gTGCcwTgAJ4efGCLCBjMAIXrmA3yOhQazQhJj4JQ0v7fte3/e1hbFfIk3TYxzHp6kc7dtCFEUW5/xBcdM0a9d1S1kel00mSWKCnIkkxDSnXADIMYYEU9O0zPf91WwB6L6NyB3atrUMw7hNFkCbFyROmXYYmypMDMNwo+t6ztSwtW27oEAXrXBuwu2rCht+WPgU7C8gPCBzYOBKhQS5FTwIKBYeQFeJoWyiKNYH5Co6OCuQr/0JdBuPVyElQCd7GRMb3B3HebsHHzexrmvyQvZwqjFZWsDzvCc62BFhSGYD3UMsfs6ToKOd+6EsxgtrtWLW4gUN3AAAAABJRU5ErkJggg==");background-color:#f3f3f3}.input-label{width:auto;font-weight:600;margin-bottom:.4rem;font-size:14px;font-size:1.4rem}@media(min-width: 576px){.input-label{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.input-label{font-size:14px;font-size:1.4rem}}.page-heading{font-size:19.2px;font-size:1.92rem}@media(min-width: 576px){.page-heading{font-size:21.6px;font-size:2.16rem}}@media(min-width: 992px){.page-heading{font-size:24px;font-size:2.4rem}}.dropdown-caret{display:inline-block;vertical-align:middle;border-top-width:4px;border-top-style:solid;border-right:4px solid rgba(0,0,0,0);border-bottom:0 solid rgba(0,0,0,0);border-left:4px solid rgba(0,0,0,0);margin-left:.8rem}.divider{height:0;overflow:hidden;border-top:1px solid #e9ecef}.marker{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.marker-first{color:#fff;background-color:#007bff}.marker-info{color:#fff;background-color:#17a2b8}.markdown-body .anchor{float:left;line-height:1;margin-left:-20px;padding-right:4px}.markdown-body .anchor:focus{outline:none}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;color:#24292e;line-height:1.5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:16px;line-height:1.5;word-wrap:break-word}.markdown-body .pl-c{color:#6a737d}.markdown-body .pl-c1,.markdown-body .pl-s .pl-v{color:#005cc5}.markdown-body .pl-e,.markdown-body .pl-en{color:#6f42c1}.markdown-body .pl-s .pl-s1,.markdown-body .pl-smi{color:#24292e}.markdown-body .pl-ent{color:#22863a}.markdown-body .pl-k{color:#d73a49}.markdown-body .pl-pds,.markdown-body .pl-s,.markdown-body .pl-s .pl-pse .pl-s1,.markdown-body .pl-sr,.markdown-body .pl-sr .pl-cce,.markdown-body .pl-sr .pl-sra,.markdown-body .pl-sr .pl-sre{color:#032f62}.markdown-body .pl-smw,.markdown-body .pl-v{color:#e36209}.markdown-body .pl-bu{color:#b31d28}.markdown-body .pl-ii{background-color:#b31d28;color:#fafbfc}.markdown-body .pl-c2{background-color:#d73a49;color:#fafbfc}.markdown-body .pl-c2:before{content:"^M"}.markdown-body .pl-sr .pl-cce{color:#22863a;font-weight:700}.markdown-body .pl-ml{color:#735c0f}.markdown-body .pl-mh,.markdown-body .pl-mh .pl-en,.markdown-body .pl-ms{color:#005cc5;font-weight:700}.markdown-body .pl-mi{color:#24292e;font-style:italic}.markdown-body .pl-mb{color:#24292e;font-weight:700}.markdown-body .pl-md{background-color:#ffeef0;color:#b31d28}.markdown-body .pl-mi1{background-color:#f0fff4;color:#22863a}.markdown-body .pl-mc{background-color:#ffebda;color:#e36209}.markdown-body .pl-mi2{background-color:#005cc5;color:#f6f8fa}.markdown-body .pl-mdr{color:#6f42c1;font-weight:700}.markdown-body .pl-ba{color:#586069}.markdown-body .pl-sg{color:#959da5}.markdown-body .pl-corl{color:#032f62;text-decoration:underline}.markdown-body details{display:block}.markdown-body summary{display:list-item}.markdown-body a{background-color:rgba(0,0,0,0)}.markdown-body a:active,.markdown-body a:hover{outline-width:0}.markdown-body strong{font-weight:inherit;font-weight:bolder}.markdown-body h1{font-size:2em;margin:.67em 0}.markdown-body img{border-style:none}.markdown-body code,.markdown-body kbd,.markdown-body pre{font-family:monospace,monospace;font-size:1em}.markdown-body hr{box-sizing:content-box;height:0;overflow:visible}.markdown-body input{font:inherit;margin:0}.markdown-body input{overflow:visible}.markdown-body [type=checkbox]{box-sizing:border-box;padding:0}.markdown-body *{box-sizing:border-box}.markdown-body input{font-family:inherit;font-size:inherit;line-height:inherit}.markdown-body a{color:#0366d6;text-decoration:none}.markdown-body a:hover{text-decoration:underline}.markdown-body strong{font-weight:600}.markdown-body hr{background:rgba(0,0,0,0);border:0;border-bottom:1px solid #dfe2e5;height:0;margin:15px 0;overflow:hidden}.markdown-body hr:before{content:"";display:table}.markdown-body hr:after{clear:both;content:"";display:table}.markdown-body table{border-collapse:collapse;border-spacing:0}.markdown-body td,.markdown-body th{padding:0}.markdown-body details summary{cursor:pointer}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-bottom:0;margin-top:0}.markdown-body h1{font-size:32px}.markdown-body h1,.markdown-body h2{font-weight:600}.markdown-body h2{font-size:24px}.markdown-body h3{font-size:20px}.markdown-body h3,.markdown-body h4{font-weight:600}.markdown-body h4{font-size:16px}.markdown-body h5{font-size:14px}.markdown-body h5,.markdown-body h6{font-weight:600}.markdown-body h6{font-size:12px}.markdown-body p{margin-bottom:10px;margin-top:0}.markdown-body blockquote{margin:0}.markdown-body ol,.markdown-body ul{margin-bottom:0;margin-top:0;padding-left:0}.markdown-body ol ol,.markdown-body ul ol{list-style-type:lower-roman}.markdown-body ol ol ol,.markdown-body ol ul ol,.markdown-body ul ol ol,.markdown-body ul ul ol{list-style-type:lower-alpha}.markdown-body dd{margin-left:0}.markdown-body code,.markdown-body pre{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px}.markdown-body pre{margin-bottom:0;margin-top:0}.markdown-body input::-webkit-inner-spin-button,.markdown-body input::-webkit-outer-spin-button{-webkit-appearance:none;appearance:none;margin:0}.markdown-body .border{border:1px solid #e1e4e8 !important}.markdown-body .border-0{border:0 !important}.markdown-body .border-bottom{border-bottom:1px solid #e1e4e8 !important}.markdown-body .rounded-1{border-radius:3px !important}.markdown-body .bg-white{background-color:#fff !important}.markdown-body .bg-gray-light{background-color:#fafbfc !important}.markdown-body .text-gray-light{color:#6a737d !important}.markdown-body .mb-0{margin-bottom:0 !important}.markdown-body .my-2{margin-bottom:8px !important;margin-top:8px !important}.markdown-body .pl-0{padding-left:0 !important}.markdown-body .py-0{padding-bottom:0 !important;padding-top:0 !important}.markdown-body .pl-1{padding-left:4px !important}.markdown-body .pl-2{padding-left:8px !important}.markdown-body .py-2{padding-bottom:8px !important;padding-top:8px !important}.markdown-body .pl-3,.markdown-body .px-3{padding-left:16px !important}.markdown-body .px-3{padding-right:16px !important}.markdown-body .pl-4{padding-left:24px !important}.markdown-body .pl-5{padding-left:32px !important}.markdown-body .pl-6{padding-left:40px !important}.markdown-body .f6{font-size:12px !important}.markdown-body .lh-condensed{line-height:1.25 !important}.markdown-body .text-bold{font-weight:600 !important}.markdown-body:before{content:"";display:table}.markdown-body:after{clear:both;content:"";display:table}.markdown-body>:first-child{margin-top:0 !important}.markdown-body>:last-child{margin-bottom:0 !important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body blockquote,.markdown-body dl,.markdown-body ol,.markdown-body p,.markdown-body pre,.markdown-body table,.markdown-body ul{margin-bottom:16px;margin-top:0}.markdown-body hr{background-color:#e1e4e8;border:0;height:.25em;margin:24px 0;padding:0}.markdown-body blockquote{border-left:.25em solid #dfe2e5;color:#6a737d;padding:0 1em}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body kbd{background-color:#fafbfc;border:1px solid #c6cbd1;border-bottom-color:#959da5;border-radius:3px;box-shadow:inset 0 -1px 0 #959da5;color:#444d56;display:inline-block;font-size:11px;line-height:10px;padding:3px 5px;vertical-align:middle}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{font-weight:600;line-height:1.25;margin-bottom:16px;margin-top:24px}.markdown-body h1{font-size:2em}.markdown-body h1,.markdown-body h2{padding-bottom:.3em}.markdown-body h2{border-bottom:1px solid #eaecef}.markdown-body h2{font-size:1.5em}.markdown-body h3{font-size:1.25em}.markdown-body h4{font-size:1em}.markdown-body h5{font-size:.875em}.markdown-body h6{color:#6a737d;font-size:.85em}.markdown-body ol,.markdown-body ul{padding-left:2em}.markdown-body ol ol,.markdown-body ol ul,.markdown-body ul ol,.markdown-body ul ul{margin-bottom:0;margin-top:0}.markdown-body li{word-wrap:break-all}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:.25em}.markdown-body dl{padding:0}.markdown-body dl dt{font-size:1em;font-style:italic;font-weight:600;margin-top:16px;padding:0}.markdown-body dl dd{margin-bottom:16px;padding:0 16px}.markdown-body table{display:block;overflow:auto;width:100%}.markdown-body table th{font-weight:600}.markdown-body table td,.markdown-body table th{border:1px solid #dfe2e5;padding:6px 13px}.markdown-body table tr{background-color:#fff;border-top:1px solid #c6cbd1}.markdown-body table tr:nth-child(2n){background-color:#f6f8fa}.markdown-body img{background-color:#fff;box-sizing:content-box;max-width:100%}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body code{background-color:rgba(27,31,35,.05);border-radius:3px;font-size:85%;margin:0;padding:.2em .4em}.markdown-body pre{word-wrap:normal}.markdown-body pre>code{background:rgba(0,0,0,0);border:0;font-size:100%;margin:0;padding:0;white-space:pre;word-break:normal;white-space:pre-wrap}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{background-color:#f6f8fa;border-radius:3px;font-size:85%;line-height:1.45;overflow:auto;padding:16px}.markdown-body pre code{background-color:rgba(0,0,0,0);border:0;display:inline;line-height:inherit;margin:0;max-width:auto;overflow:visible;padding:0;word-wrap:normal}.markdown-body .commit-tease-sha{color:#444d56;display:inline-block;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:90%}.markdown-body .blob-wrapper{border-bottom-left-radius:3px;border-bottom-right-radius:3px;overflow-x:auto;overflow-y:hidden}.markdown-body .blob-wrapper-embedded{max-height:240px;overflow-y:auto}.markdown-body .blob-num{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;color:rgba(27,31,35,.3);cursor:pointer;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;line-height:20px;min-width:50px;padding-left:10px;padding-right:10px;text-align:right;user-select:none;vertical-align:top;white-space:nowrap;width:1%}.markdown-body .blob-num:hover{color:rgba(27,31,35,.6)}.markdown-body .blob-num:before{content:attr(data-line-number)}.markdown-body .blob-code{line-height:20px;padding-left:10px;padding-right:10px;position:relative;vertical-align:top}.markdown-body .blob-code-inner{color:#24292e;font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-size:12px;overflow:visible;white-space:pre;word-wrap:normal}.markdown-body .pl-token.active,.markdown-body .pl-token:hover{background:#ffea7f;cursor:pointer}.markdown-body kbd{background-color:#fafbfc;border:1px solid #d1d5da;border-bottom-color:#c6cbd1;border-radius:3px;box-shadow:inset 0 -1px 0 #c6cbd1;color:#444d56;display:inline-block;font:11px SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;line-height:10px;padding:3px 5px;vertical-align:middle}.markdown-body :checked+.radio-label{border-color:#0366d6;position:relative;z-index:1}.markdown-body .tab-size[data-tab-size="1"]{-moz-tab-size:1;tab-size:1}.markdown-body .tab-size[data-tab-size="2"]{-moz-tab-size:2;tab-size:2}.markdown-body .tab-size[data-tab-size="3"]{-moz-tab-size:3;tab-size:3}.markdown-body .tab-size[data-tab-size="4"]{-moz-tab-size:4;tab-size:4}.markdown-body .tab-size[data-tab-size="5"]{-moz-tab-size:5;tab-size:5}.markdown-body .tab-size[data-tab-size="6"]{-moz-tab-size:6;tab-size:6}.markdown-body .tab-size[data-tab-size="7"]{-moz-tab-size:7;tab-size:7}.markdown-body .tab-size[data-tab-size="8"]{-moz-tab-size:8;tab-size:8}.markdown-body .tab-size[data-tab-size="9"]{-moz-tab-size:9;tab-size:9}.markdown-body .tab-size[data-tab-size="10"]{-moz-tab-size:10;tab-size:10}.markdown-body .tab-size[data-tab-size="11"]{-moz-tab-size:11;tab-size:11}.markdown-body .tab-size[data-tab-size="12"]{-moz-tab-size:12;tab-size:12}.markdown-body .task-list-item{list-style-type:none}.markdown-body .task-list-item+.task-list-item{margin-top:3px}.markdown-body .task-list-item input{margin:0 .2em .25em -1.6em;vertical-align:middle}.markdown-body hr{border-bottom-color:#eee}.markdown-body .pl-0{padding-left:0 !important}.markdown-body .pl-1{padding-left:4px !important}.markdown-body .pl-2{padding-left:8px !important}.markdown-body .pl-3{padding-left:16px !important}.markdown-body .pl-4{padding-left:24px !important}.markdown-body .pl-5{padding-left:32px !important}.markdown-body .pl-6{padding-left:40px !important}.markdown-body .pl-7{padding-left:48px !important}.markdown-body .pl-8{padding-left:64px !important}.markdown-body .pl-9{padding-left:80px !important}.markdown-body .pl-10{padding-left:96px !important}.markdown-body .pl-11{padding-left:112px !important}.markdown-body .pl-12{padding-left:128px !important}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:bold}.hljs-number,.hljs-literal,.hljs-variable,.hljs-template-variable,.hljs-tag .hljs-attr{color:teal}.hljs-string,.hljs-doctag{color:#d14}.hljs-title,.hljs-section,.hljs-selector-id{color:#900;font-weight:bold}.hljs-subst{font-weight:normal}.hljs-type,.hljs-class .hljs-title{color:#458;font-weight:bold}.hljs-tag,.hljs-name,.hljs-attribute{color:navy;font-weight:normal}.hljs-regexp,.hljs-link{color:#009926}.hljs-symbol,.hljs-bullet{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:bold}:export{mdBreakpoint:576px;smBreakpoint:321px}.auth-page{background:#f3f3f3;text-align:center;min-height:100vh;padding:50px 0}.auth-page .auth-button{margin-top:8px}.auth-page .heading{color:#2a2a2a;font-size:25.6px;font-size:2.56rem;font-weight:300;margin-top:12px;margin-bottom:0}@media(min-width: 576px){.auth-page .heading{font-size:28.8px;font-size:2.88rem}}@media(min-width: 992px){.auth-page .heading{font-size:32px;font-size:3.2rem}}.auth-page .body{max-width:420px;margin-left:auto;margin-right:auto;margin-top:20px}.auth-page .referrer-flash{margin:24px 0}.auth-page .error-flash{margin-bottom:24px}.auth-page .footer{margin-top:20px;line-height:20px}.auth-page .callout{color:#7c7c7c;font-size:14px;font-size:1.4rem}@media(min-width: 576px){.auth-page .callout{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.auth-page .callout{font-size:14px;font-size:1.4rem}}.auth-page .cta{font-size:14px;font-size:1.4rem}@media(min-width: 576px){.auth-page .cta{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.auth-page .cta{font-size:14px;font-size:1.4rem}}.auth-page .panel{border:1px solid #d8d8d8;background:#fff;border-radius:2px;padding:20px;text-align:left}.auth-page .auth-button{margin-top:16px}.auth-page .input-row~.input-row{margin-top:12px}.auth-page .label{font-size:14px;font-size:1.4rem;font-weight:600;width:100%;margin-bottom:0}@media(min-width: 576px){.auth-page .label{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.auth-page .label{font-size:14px;font-size:1.4rem}}.auth-page .forgot{font-size:14px;font-size:1.4rem;float:right;font-weight:400}@media(min-width: 576px){.auth-page .forgot{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.auth-page .forgot{font-size:14px;font-size:1.4rem}}.auth-page.password-reset-page .email-input{margin-top:1.6rem}.auth-page .alert{margin-bottom:1rem}:export{mdBreakpoint:576px;smBreakpoint:321px}.home-page .note-group-list{flex-grow:1}@media(min-width: 992px){.home-page .note-group-list{margin-top:1.6rem}}.home-page .note-group-list .note-group-list-empty{padding:4rem 1.6rem;text-align:center;color:#686868}.home-page .note-group{position:relative;border-radius:4px;box-shadow:0 0 8px rgba(0,0,0,.14)}.home-page .note-group:not(:first-of-type){margin-top:2rem}@media(min-width: 576px){.home-page .note-group:not(:first-of-type){margin-top:2.4rem}}.home-page .note-group .note-group-header{font-size:14px;font-size:1.4rem;display:flex;justify-content:space-between;color:#fff;padding:1.2rem 1.6rem;background:#f7f9fa;color:#2a2a2a;border-bottom:1px solid #d8d8d8;border-top-left-radius:4px;border-top-right-radius:4px}@media(min-width: 576px){.home-page .note-group .note-group-header{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.home-page .note-group .note-group-header{font-size:14px;font-size:1.4rem}}.home-page .note-group .date{font-weight:600;font-size:14px;font-size:1.4rem}@media(min-width: 576px){.home-page .note-group .date{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.home-page .note-group .date{font-size:14px;font-size:1.4rem}}.home-page .note-group .mask{position:absolute;top:0;bottom:0;left:0;right:0;background:#fff;z-index:1;opacity:.8}.home-page .note-group .header-date{font-weight:600;font-size:14.4px;font-size:1.44rem}@media(min-width: 576px){.home-page .note-group .header-date{font-size:14.4px;font-size:1.44rem}}@media(min-width: 992px){.home-page .note-group .header-date{font-size:16px;font-size:1.6rem}}.home-page .note-group .header-count{font-weight:300}.home-page .note-group .list{list-style:none;padding-left:0;margin-bottom:0}.home-page .note-list{list-style:none;padding-left:0;margin-bottom:0}.home-page .note-item{background:#fff;position:relative;border-bottom:1px solid #d8d8d8}.home-page .note-item .link{color:#2a2a2a;display:block;padding:1.2rem 1.6rem;border:2px solid rgba(0,0,0,0)}.home-page .note-item .link:hover{text-decoration:none;background:#ecf4ff;color:inherit}.home-page .note-item .meta{line-height:1.6rem}.home-page .note-item .body{overflow:hidden;text-overflow:ellipsis}.home-page .note-item .note-header{display:flex;justify-content:space-between}.home-page .note-item .note-content{margin-top:1.2rem;line-height:1.6rem;overflow:hidden;text-overflow:ellipsis;color:#686868}.home-page .note-item .book-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:700;font-size:14px;font-size:1.4rem;width:212px}@media(min-width: 576px){.home-page .note-item .book-label{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.home-page .note-item .book-label{font-size:14px;font-size:1.4rem}}@media(min-width: 576px){.home-page .note-item .book-label{width:320px}}.home-page .note-item .match{display:inline-block;background:#f7f77d;padding:.4rem .4rem}.home-page .toolbar{text-align:right}.home-page .paginator{display:inline-flex;align-items:center}.home-page .paginator .paginator-info{font-size:14px;font-size:1.4rem;color:#686868}@media(min-width: 576px){.home-page .paginator .paginator-info{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.home-page .paginator .paginator-info{font-size:14px;font-size:1.4rem}}.home-page .paginator .paginator-link{padding:1.2rem 1.2rem}.home-page .paginator .paginator-link.disabled{cursor:not-allowed}.home-page .paginator .paginator-link-prev{margin-left:.8rem}@media(min-width: 576px){.home-page .paginator .paginator-link-prev{margin-left:2rem}}.home-page .paginator .caret-next{transform:rotate(-90deg)}.home-page .paginator .caret-prev{transform:rotate(90deg)}.home-page .paginator .paginator-label{font-weight:600}.note-page{background:#f3f3f3;flex-grow:1;flex-basis:0}.note-page .header{display:flex;align-items:center;justify-content:space-between;padding:1.2rem 1.6rem;border-bottom:1px solid #d8d8d8}.note-page .header-left,.note-page .header-right{display:flex;align-items:center}.note-page .book-icon{vertical-align:middle}.note-page .content-wrapper{padding:1.2rem 1.6rem}.note-page .collapsed-content{color:#8c8c8c}.note-page .footer{display:flex;justify-content:space-between;align-items:center;font-size:14px;font-size:1.4rem;padding:1.2rem 1.6rem}@media(min-width: 576px){.note-page .footer{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.note-page .footer{font-size:14px;font-size:1.4rem}}.note-page .ts{color:#8c8c8c}.note-page .ts-lead{display:none}@media(min-width: 576px){.note-page .ts-lead{display:inline}}.note-page .match{display:inline-block;background:#f7f77d}.note-page .book-label{font-size:18px;font-size:1.8rem;font-weight:600;display:inline-block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#2a2a2a}@media(min-width: 576px){.note-page .book-label{font-size:18px;font-size:1.8rem}}@media(min-width: 992px){.note-page .book-label{font-size:18px;font-size:1.8rem}}.note-page .book-label a{color:inherit}.note-page .book-label a:hover{color:inherit}.note-page .header .book-label{max-width:20rem;margin-left:1.2rem}@media(min-width: 321px){.note-page .header .book-label{max-width:20rem}}@media(min-width: 576px){.note-page .header .book-label{max-width:42rem}}@media(min-width: 992px){.note-page .header .book-label{max-width:60rem}}.books-page .books-content{padding:1.6rem 2.4rem;margin-top:1.6rem}.books-page .books-content h1{border-bottom:1px solid #f3f3f3;margin-bottom:1.2rem}:export{mdBreakpoint:576px;smBreakpoint:321px}.settings-page .sidebar{box-shadow:0 1px 5px rgba(0,0,0,.2);background:#fff;margin-bottom:2rem;margin-top:2rem}@media(min-width: 992px){.settings-page .sidebar{margin-bottom:0;margin-top:0}}.settings-page .sidebar-item{display:block;padding:1.2rem 1.6rem;border-left:4px solid rgba(0,0,0,0);font-size:14.4px;font-size:1.44rem}@media(min-width: 576px){.settings-page .sidebar-item{font-size:14.4px;font-size:1.44rem}}@media(min-width: 992px){.settings-page .sidebar-item{font-size:16px;font-size:1.6rem}}.settings-page .sidebar-item:hover{text-decoration:none;background:#f7f9fa}.settings-page .sidebar-item.active{font-weight:600;border-left-color:#072a40}@media(min-width: 992px){.settings-page .setting-section-wrapper .header{display:none}}.settings-page .setting-section-wrapper .setting-section{margin-top:2.4rem;background:#fff;box-shadow:0 0 8px rgba(0,0,0,.14)}.settings-page .setting-section-wrapper .setting-section:first-child{margin-top:0}.settings-page .setting-section-wrapper .section-heading{font-size:14.4px;font-size:1.44rem;font-weight:600;padding-bottom:.4rem;background:#f7f9fa;padding:1.6rem 2rem}@media(min-width: 576px){.settings-page .setting-section-wrapper .section-heading{font-size:14.4px;font-size:1.44rem}}@media(min-width: 992px){.settings-page .setting-section-wrapper .section-heading{font-size:16px;font-size:1.6rem}}.settings-page .setting-section-wrapper .section-content{margin-top:2rem}.settings-page .setting-section-wrapper .actions{margin-top:1.8rem;text-align:right}.settings-page .setting-row{padding:1.6rem 2rem}.settings-page .setting-row:not(:last-child){border-bottom:1px solid #d8d8d8}.settings-page .setting-row .setting-row-summary{display:flex;flex-direction:column}@media(min-width: 576px){.settings-page .setting-row .setting-row-summary{flex-direction:row;justify-content:space-between;align-items:center}}.settings-page .setting-row .setting-row-main{padding-top:2.4rem}.settings-page .setting-row .setting-name{font-weight:400;font-size:14.4px;font-size:1.44rem;margin-bottom:0}@media(min-width: 576px){.settings-page .setting-row .setting-name{font-size:14.4px;font-size:1.44rem}}@media(min-width: 992px){.settings-page .setting-row .setting-name{font-size:16px;font-size:1.6rem}}.settings-page .setting-row .setting-desc{margin-bottom:0;font-size:14px;font-size:1.4rem;color:#686868}@media(min-width: 576px){.settings-page .setting-row .setting-desc{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.settings-page .setting-row .setting-desc{font-size:14px;font-size:1.4rem}}.settings-page .setting-row .setting-action{display:flex;flex-direction:column}@media(min-width: 576px){.settings-page .setting-row .setting-action{flex-direction:row}}.settings-page .setting-row .setting-right{display:flex;word-break:break-all;justify-content:space-between;align-items:center;margin-top:.4rem}@media(min-width: 576px){.settings-page .setting-row .setting-right{flex-direction:row;align-items:center;margin-top:0}}.settings-page .setting-row .setting-edit{color:#6f53c0;padding:0}.settings-page .setting-row .setting-edit:hover{color:#6143b7}@media(min-width: 576px){.settings-page .setting-row .setting-edit{margin-left:1.6rem}}.settings-page .setting-row .input-row~.input-row,.settings-page .setting-row .input-row .input-row{margin-top:1.2rem}.settings-page .email-verification-form{margin-left:1.2rem}:export{mdBreakpoint:576px;smBreakpoint:321px}.header-wrapper{padding:0;z-index:2;position:relative;display:flex;box-shadow:0 1px 5px rgba(0,0,0,.2);background:#072a40;align-items:stretch;justify-content:space-between;flex:1;flex-direction:column;position:sticky;top:0;z-index:4;height:60px}.header-wrapper .container{height:100%}@media(min-width: 576px){.header-wrapper{flex-direction:row}}.header-wrapper .header-content{display:flex;justify-content:space-between;height:100%}.header-wrapper .left{display:flex}.header-wrapper .right{display:flex}.header-wrapper .search-wrapper{align-items:center;display:flex;margin-left:3.2rem}.header-wrapper .search-input{width:35.6rem;border:0;padding:4px 12px;border-radius:.4rem;font-size:14px;font-size:1.4rem}@media(min-width: 576px){.header-wrapper .search-input{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.header-wrapper .search-input{font-size:14px;font-size:1.4rem}}.header-wrapper .brand{display:flex;align-items:center}.header-wrapper .brand:hover{text-decoration:none}.header-wrapper .main-nav{margin-left:3.2rem;display:flex}.header-wrapper .main-nav .list{display:flex}.header-wrapper .main-nav .item{display:flex;align-items:stretch}.header-wrapper .main-nav .nav-link{font-size:14px;font-size:1.4rem;display:flex;font-weight:600;align-items:center;padding:0 1.6rem;color:#fff}@media(min-width: 576px){.header-wrapper .main-nav .nav-link{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.header-wrapper .main-nav .nav-link{font-size:14px;font-size:1.4rem}}.header-wrapper .main-nav .nav-link:hover{color:#fff;text-decoration:none;background:#0c486e}.header-wrapper .main-nav .nav-item{font-size:14px;font-size:1.4rem;font-weight:600}@media(min-width: 576px){.header-wrapper .main-nav .nav-item{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.header-wrapper .main-nav .nav-item{font-size:14px;font-size:1.4rem}}.header-wrapper .dropdown-trigger{color:#fff;padding:16px;font-size:16px;border:none;cursor:pointer}.header-wrapper .dropdown{position:relative;display:inline-block}.header-wrapper .dropdown-content{display:none;position:absolute;background-color:#f1f1f1;width:24rem;background:#fff;border:1px solid #d8d8d8;border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.15);top:calc(100% + 4px);z-index:1}.header-wrapper .dropdown-content.show{display:block}.header-wrapper .dropdown-content.right-align{right:0}.header-wrapper .account-dropdown .dropdown-trigger{height:100%}.header-wrapper .account-dropdown .account-dropdown-header{font-size:14px;font-size:1.4rem;color:#8c8c8c;padding:.8rem 1.2rem;display:block;margin-bottom:0;white-space:nowrap}@media(min-width: 576px){.header-wrapper .account-dropdown .account-dropdown-header{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.header-wrapper .account-dropdown .account-dropdown-header{font-size:14px;font-size:1.4rem}}.header-wrapper .account-dropdown .account-dropdown-header svg{fill:#8c8c8c}.header-wrapper .account-dropdown .account-dropdown-header .email{font-weight:600;white-space:normal;word-break:break-all}.header-wrapper .account-dropdown .dropdown-link{font-size:14px;font-size:1.4rem;white-space:pre;padding:.8rem 1.4rem;width:100%;display:block;color:#000}@media(min-width: 576px){.header-wrapper .account-dropdown .dropdown-link{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.header-wrapper .account-dropdown .dropdown-link{font-size:14px;font-size:1.4rem}}.header-wrapper .account-dropdown .dropdown-link:hover{background:#f3f3f3;text-decoration:none;color:#0056b3}.header-wrapper .account-dropdown .dropdown-link.disabled{color:#d4d4d4;cursor:not-allowed}.header-wrapper .account-dropdown .dropdown-link:not(.disabled):focus{background:#f3f3f3;color:#0056b3;outline:1px dotted gray}.header-wrapper .account-dropdown .session-notice-wrapper{display:flex;align-items:center}.header-wrapper .account-dropdown .session-notice{margin-left:.4rem}.main{position:relative;display:flex;flex-direction:column;background:#f3f3f3;min-height:calc(100vh - 60px)}.main.nofooter{margin-bottom:0}.main.noheader:not(.nofooter){min-height:calc(100vh - 56px)}.main.nofooter:not(.noheader){min-height:calc(100vh - 60px)}.main.nofooter.noheader{min-height:100vh}@media(min-width: 992px){.main{margin-bottom:0;min-height:calc(100vh - 60px)}}.partial--time{color:#686868;font-size:14px;font-size:1.4rem}@media(min-width: 576px){.partial--time{font-size:14px;font-size:1.4rem}}@media(min-width: 992px){.partial--time{font-size:14px;font-size:1.4rem}}@media(min-width: 576px){.partial--time .mobile-text{display:none}}.partial--time .text{display:none}@media(min-width: 576px){.partial--time .text{display:inherit}}@media(min-width: 992px){.partial--page-toolbar{height:4.8rem;border-radius:.4rem;background:#f7f9fa;box-shadow:0 0 8px rgba(0,0,0,.14)}.partial--page-toolbar.bottom{margin-top:1.2rem}}.icon--caret-right{transform:rotate(-90deg)}.icon--caret-left{transform:rotate(90deg)}.frame{box-shadow:0 1px 5px rgba(0,0,0,.2);background:#fff}html{font-size:62.5%}html body{margin:0;font-size:1.6rem}img{max-width:100%}.main-content{padding-top:24px}.no-scroll{overflow:hidden}@media(min-width: 576px)and (max-width: 991px){.container.mobile-nopadding{max-width:100%}}@media(max-width: 991px){.container.mobile-nopadding{padding-left:0;padding-right:0}.container.mobile-nopadding .row{margin-left:0;margin-right:0}.container.mobile-nopadding [class*=col-]{padding-left:0;padding-right:0}}.form-control{font-size:1.6rem}.dropdown{position:inherit}.input-group input~button{border-top-left-radius:0;border-bottom-left-radius:0}.page-bgdark{background:#ececec}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.input{border-radius:.4rem;background-clip:padding-box;border:1px solid #ced4da;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}/*# sourceMappingURL=main.css.map */ diff --git a/pkg/server/static/main.css.map b/pkg/server/static/main.css.map new file mode 100644 index 00000000..dfbdb1d3 --- /dev/null +++ b/pkg/server/static/main.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["../assets/styles/src/_reboot.scss","../assets/styles/src/_grid.scss","../assets/styles/src/_bootstrap.scss","../assets/styles/src/_variables.scss","../assets/styles/src/_buttons.scss","../assets/styles/src/_font.scss","../assets/styles/src/_responsive.scss","../assets/styles/src/_theme.scss","../assets/styles/src/_select.scss","../assets/styles/src/_shared.scss","../assets/styles/src/_marker.scss","../assets/styles/src/_markdown.scss","../assets/styles/src/_hljs.scss","../assets/styles/src/_login.scss","../assets/styles/src/_home.scss","../assets/styles/src/_note.scss","../assets/styles/src/_books.scss","../assets/styles/src/_settings.scss","../assets/styles/src/_header.scss","../assets/styles/src/_global.scss","../assets/styles/src/main.scss"],"names":[],"mappings":"AAkBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,GAOA,qBAGE,sBAGF,KACE,uBACA,iBACA,8BACA,0CAGF,sEAUE,cAGF,KACE,SACA,uLAGA,eACA,gBACA,gBACA,cACA,gBACA,sBAGF,sBACE,qBAGF,GACE,uBACA,SACA,iBAGF,kBAME,aACA,oBAGF,EACE,aACA,mBAGF,sCAEE,0BACA,yCACA,iCACA,YACA,gBACA,sCACA,8BAGF,QACE,mBACA,kBACA,oBAGF,SAGE,aACA,mBAGF,wBAIE,gBAGF,GACE,gBAGF,GACE,oBACA,cAGF,WACE,gBAGF,SAEE,mBAGF,MACE,cAGF,QAEE,kBACA,cACA,cACA,wBAGF,IACE,eAGF,IACE,WAGF,EACE,cACA,qBACA,+BAGF,QACE,cACA,0BAGF,8BACE,cACA,qBAGF,wEAEE,cACA,qBAGF,oCACE,UAGF,kBAIE,2FAEA,cAGF,IACE,aACA,mBACA,cAGF,OACE,gBAGF,IACE,sBACA,kBAGF,IACE,gBACA,sBAGF,MACE,yBAGF,QACE,mBACA,sBACA,cACA,gBACA,oBAGF,GACE,mBAGF,MACE,qBACA,oBAGF,OACE,gBAGF,aACE,mBACA,0CAGF,sCAKE,SACA,oBACA,kBACA,oBAGF,aAEE,iBAGF,cAEE,oBAGF,OACE,iBAGF,gDAIE,0BAGF,4GAIE,eAGF,wHAIE,UACA,kBAGF,uCAEE,sBACA,UAGF,+EAIE,2BAGF,SACE,cACA,gBAGF,SACE,YACA,UACA,SACA,SAGF,OACE,cACA,WACA,eACA,UACA,oBACA,iBACA,oBACA,cACA,mBAGF,SACE,wBAGF,kFAEE,YAGF,cACE,oBACA,wBAGF,yCACE,wBAGF,6BACE,aACA,0BAGF,OACE,qBAGF,QACE,kBACA,eAGF,SACE,aAGF,SACE,wBC1VF;AAAA;AAAA;AAAA;AAAA;AAAA,GAMA,KACE,sBACA,6BAGF,qBAGE,mBAGF,gBACE,WACA,mBACA,kBACA,kBACA,iBAGF,yBACE,gBACE,iBAIJ,yBACE,gBACE,iBAIJ,yBACE,gBACE,iBAIJ,0BACE,gBACE,kBAIJ,0BACE,gBACE,kBAIJ,0BACE,gBACE,kBAIJ,iBACE,WACA,mBACA,kBACA,kBACA,iBAGF,WACE,WACA,mBACA,kBACA,kBACA,iBAGF,yBACE,WACE,iBAIJ,yBACE,WACE,iBAIJ,yBACE,WACE,iBAIJ,0BACE,WACE,kBAIJ,KACE,oBACA,aACA,mBACA,eACA,mBACA,kBAGF,YACE,eACA,cAGF,2CAEE,gBACA,eAGF,sqBAsEE,kBACA,WACA,mBACA,kBAGF,KACE,0BACA,aACA,oBACA,YACA,eAGF,UACE,kBACA,cACA,WACA,eAGF,OACE,uBACA,mBACA,oBAGF,OACE,wBACA,oBACA,qBAGF,OACE,iBACA,aACA,cAGF,OACE,wBACA,oBACA,qBAGF,OACE,wBACA,oBACA,qBAGF,OACE,iBACA,aACA,cAGF,OACE,wBACA,oBACA,qBAGF,OACE,wBACA,oBACA,qBAGF,OACE,iBACA,aACA,cAGF,QACE,wBACA,oBACA,qBAGF,QACE,wBACA,oBACA,qBAGF,QACE,kBACA,cACA,eAGF,aACE,kBACA,SAGF,YACE,kBACA,SAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,SACE,iBACA,QAGF,UACE,kBACA,SAGF,UACE,kBACA,SAGF,UACE,kBACA,SAGF,UACE,sBAGF,UACE,uBAGF,UACE,gBAGF,UACE,uBAGF,UACE,uBAGF,UACE,gBAGF,UACE,uBAGF,UACE,uBAGF,UACE,gBAGF,WACE,uBAGF,WACE,uBAGF,yBACE,QACE,0BACA,aACA,oBACA,YACA,eAEF,aACE,kBACA,cACA,WACA,eAEF,UACE,uBACA,mBACA,oBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,WACE,wBACA,oBACA,qBAEF,WACE,wBACA,oBACA,qBAEF,WACE,kBACA,cACA,eAEF,gBACE,kBACA,SAEF,eACE,kBACA,SAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,aACE,kBACA,SAEF,aACE,kBACA,SAEF,aACE,kBACA,SAEF,aACE,cAEF,aACE,sBAEF,aACE,uBAEF,aACE,gBAEF,aACE,uBAEF,aACE,uBAEF,aACE,gBAEF,aACE,uBAEF,aACE,uBAEF,aACE,gBAEF,cACE,uBAEF,cACE,wBAIJ,yBACE,QACE,0BACA,aACA,oBACA,YACA,eAEF,aACE,kBACA,cACA,WACA,eAEF,UACE,uBACA,mBACA,oBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,WACE,wBACA,oBACA,qBAEF,WACE,wBACA,oBACA,qBAEF,WACE,kBACA,cACA,eAEF,gBACE,kBACA,SAEF,eACE,kBACA,SAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,aACE,kBACA,SAEF,aACE,kBACA,SAEF,aACE,kBACA,SAEF,aACE,cAEF,aACE,sBAEF,aACE,uBAEF,aACE,gBAEF,aACE,uBAEF,aACE,uBAEF,aACE,gBAEF,aACE,uBAEF,aACE,uBAEF,aACE,gBAEF,cACE,uBAEF,cACE,wBAIJ,yBACE,QACE,0BACA,aACA,oBACA,YACA,eAEF,aACE,kBACA,cACA,WACA,eAEF,UACE,uBACA,mBACA,oBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,WACE,wBACA,oBACA,qBAEF,WACE,wBACA,oBACA,qBAEF,WACE,kBACA,cACA,eAEF,gBACE,kBACA,SAEF,eACE,kBACA,SAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,aACE,kBACA,SAEF,aACE,kBACA,SAEF,aACE,kBACA,SAEF,aACE,cAEF,aACE,sBAEF,aACE,uBAEF,aACE,gBAEF,aACE,uBAEF,aACE,uBAEF,aACE,gBAEF,aACE,uBAEF,aACE,uBAEF,aACE,gBAEF,cACE,uBAEF,cACE,wBAIJ,0BACE,QACE,0BACA,aACA,oBACA,YACA,eAEF,aACE,kBACA,cACA,WACA,eAEF,UACE,uBACA,mBACA,oBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,UACE,wBACA,oBACA,qBAEF,UACE,wBACA,oBACA,qBAEF,UACE,iBACA,aACA,cAEF,WACE,wBACA,oBACA,qBAEF,WACE,wBACA,oBACA,qBAEF,WACE,kBACA,cACA,eAEF,gBACE,kBACA,SAEF,eACE,kBACA,SAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,YACE,iBACA,QAEF,aACE,kBACA,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,kDACE,aAEF,qBACE,eACA,mBACA,kBACA,kBACA,sBACA,WAEF,gBACE,8DACA,yDACA,sDACA,WACA,YACA,sBACA,kBACA,sBACA,wBACA,qBACA,kBACA,sBAEF,mBACE,gDACA,2CACA,wCACA,WACA,eACA,mBACA,kBACA,kBACA,sBACA,WAEF,yBACE,cAEF,cACE,qBACA,eACA,cAEF,kCACE,WAEF,mBACE,eACA,mBACA,kBACA,kBACA,sBACA,WACA,kBAEF,gCACE,gBACA,iBAEF,cACE,8CACA,mBACA,2BACA,qBACA,SACA,QACA,kBAEF,6BACE,kBAEF,2CACE,qBAEF,0BACE,kBACA,qBACA,WACA,UACA,YACA,sBACA,gBACA,WAEF,2CACE,KACE,UAEF,GACE,WAGJ,mCACE,KACE,UAEF,GACE,WAGJ,mBACE,+BACA,8BACA,sBACA,sBACA,yBACA,mCACA,sBACA,gBACA,iBACA,kBACA,OACA,SACA,WACA,UACA,iCAEF,mCACE,yBAEF,aACE,iBACA,gBAEF,eACE,sBACA,sBACA,WACA,eACA,cACA,iBAEF,0BACE,+BACA,8BAEF,2BACE,yBAEA,qCACA,WAEF,0BACE,yBAEA,qCACA,WAEF,2BACE,WACA,eAEF,kBACE,sBACA,WACA,eACA,cACA,iBAEF,6BACE,sBACA,iBACA,UAEF,yCACE,cACA,kBAEF,uCACE,gBAEF,6BACE,yBAEA,qCACA,kBACA,yBAEA,qCACA,cACA,qBACA,eACA,gBACA,gBACA,eACA,mBAEF,qEAEE,qBACA,sBAEF,mCACE,+BACA,4BACA,eACA,gBAEF,oCACE,cACA,eACA,qBAEF,0CACE,0BAEF,kCACE,eACA,8BACA,2BACA,+BAEA,2CACA,oBAEF,gFAEE,yBAEA,qCACA,cAEF,yCACE,yBAEA,qCAEF,yCACE,cACA,iBAEF,8CACE,kBACA,8BAEA,0CAEF,yCACE,yBACA,yBACA,WAEF,8CACE,mBACA,+BAEF,6JAGE,yBAEF,iCACE,GACE,yBAGJ,yCACE,GACE,iCLjbJ,QACE,aAJc,MAKd,aAJc,MAEhB,QACE,aAJc,MAKd,aAJc,MMJhB,uBACE,GACE,WAEF,IACE,UAEF,KACE,YAKJ,QACE,qCACA,mBAEA,oBACE,mBAIJ,iIAKE,iBFxBa,QEyBb,mBAGF,eACE,gBACA,eACA,gBAGF,SACE,aAGF,mBACE,kBACA,YACA,WACA,YACA,gBAIA,sBAEE,cAIJ,YACE,yBACA,qBACA,kBACA,oBACA,cAEA,yBACE,MF/DG,QEiEL,kBACE,aF7CS,QE8CT,6EAEA,aAIJ,kBACE,qBAGF,mBACE,qBAGF,oBACE,WAGF,YACE,WAGF,EACE,MF5EK,QE8EL,QACE,MF9ES,QEmFb,kBAME,gBH5EE,yBGgFJ,qBAEI,gBHlFA,yBGqFJ,4BAEI,eACA,gBAEA,iCACE,cACA,eAEF,0CAEE,eACA,iBAIN,UACE,kBAGF,MACE,iBACA,oBAEA,uBACE,cACA,iBHvIA,yBGqIF,uBAKI,mBACA,uBAKN,aACE,gBAEA,8BACE,mBHpJA,yBGgJJ,aASI,mBACA,cAIJ,aACE,gBACA,mZACA,sBACA,4BACA,qCACA,yBACA,yBACA,gBACA,gBACA,mBACA,aACA,sBACA,kBACA,8CAEA,mBACE,qBACA,aACA,2EAGF,wDAEE,oiBACA,iBFzLW,QE6Lf,aAEE,WACA,gBACA,oBJ3HE,eACA,iBCjEA,yBGuLJ,aJnHM,eACA,kBCzEF,yBG2LJ,aJ9GM,eACA,kBIqHN,cJ/HI,iBACA,kBCjEA,yBG+LJ,cJ3HM,iBACA,mBCzEF,yBGmMJ,cJtHM,eACA,kBIyHN,gBACE,qBACA,sBACA,qBACA,uBACA,qCACA,oCACA,oCACA,kBAGF,SACE,SACA,gBACA,6BC7NF,QACE,qBACA,mBACA,cACA,gBACA,cACA,kBACA,mBACA,wBACA,qBAGF,cACE,WACA,yBAGF,aACE,WACA,yBCPF,uBACE,WACA,cACA,kBACA,kBAGF,6BACE,aAGF,gMAME,qBAGF,eACE,0BACA,8BACA,cACA,gBACA,kIAEA,eACA,gBACA,qBAGF,qBACE,cAGF,iDAEE,cAGF,2CAEE,cAGF,mDAEE,cAGF,uBACE,cAGF,qBACE,cAGF,gMAOE,cAGF,4CAEE,cAGF,sBACE,cAGF,sBACE,yBACA,cAGF,sBACE,yBACA,cAGF,6BACE,aAGF,8BACE,cACA,gBAGF,sBACE,cAGF,yEAGE,cACA,gBAGF,sBACE,cACA,kBAGF,sBACE,cACA,gBAGF,sBACE,yBACA,cAGF,uBACE,yBACA,cAGF,sBACE,yBACA,cAGF,uBACE,yBACA,cAGF,uBACE,cACA,gBAGF,sBACE,cAGF,sBACE,cAGF,wBACE,cACA,0BAGF,uBACE,cAGF,uBACE,kBAGF,iBACE,+BAGF,+CAEE,gBAGF,sBACE,oBACA,mBAGF,kBACE,cACA,eAGF,mBACE,kBAGF,0DAGE,gCACA,cAGF,kBACE,uBACA,SACA,iBAGF,qBACE,aACA,SAGF,qBACE,iBAGF,+BACE,sBACA,UAGF,iBACE,sBAGF,qBACE,oBACA,kBACA,oBAGF,iBACE,cACA,qBAGF,uBACE,0BAGF,sBACE,gBAGF,kBACE,yBACA,SACA,gCACA,SACA,cACA,gBAGF,yBACE,WACA,cAGF,wBACE,WACA,WACA,cAGF,qBACE,yBACA,iBAGF,oCAEE,UAGF,+BACE,eAGF,4GAME,gBACA,aAGF,kBACE,eAGF,oCAEE,gBAGF,kBACE,eAGF,kBACE,eAGF,oCAEE,gBAGF,kBACE,eAGF,kBACE,eAGF,oCAEE,gBAGF,kBACE,eAGF,iBACE,mBACA,aAGF,0BACE,SAGF,oCAEE,gBACA,aACA,eAGF,0CAEE,4BAGF,gGAIE,4BAGF,kBACE,cAGF,uCAEE,4EAEA,eAGF,mBACE,gBACA,aAGF,gGAEE,wBACA,gBACA,SAGF,uBACE,oCAGF,yBACE,oBAGF,8BACE,2CAGF,0BACE,6BAGF,yBACE,iCAGF,8BACE,oCAGF,gCACE,yBAGF,qBACE,2BAGF,qBACE,6BACA,0BAGF,qBACE,0BAGF,qBACE,4BACA,yBAGF,qBACE,4BAGF,qBACE,4BAGF,qBACE,8BACA,2BAGF,0CAEE,6BAGF,qBACE,8BAGF,qBACE,6BAGF,qBACE,6BAGF,qBACE,6BAGF,mBACE,0BAGF,6BACE,4BAGF,0BACE,2BAGF,sBACE,WACA,cAGF,qBACE,WACA,WACA,cAGF,4BACE,wBAGF,2BACE,2BAGF,6BACE,cACA,qBAGF,yIAOE,mBACA,aAGF,kBACE,yBACA,SACA,aACA,cACA,UAGF,0BACE,gCACA,cACA,cAGF,uCACE,aAGF,sCACE,gBAGF,mBACE,yBACA,yBACA,4BACA,kBACA,kCACA,cACA,qBACA,eACA,iBACA,gBACA,sBAGF,4GAME,gBACA,iBACA,mBACA,gBAGF,kBACE,cAGF,oCAEE,oBAGF,kBACE,gCAGF,kBACE,gBAGF,kBACE,iBAGF,kBACE,cAGF,kBACE,iBAGF,kBACE,cACA,gBAGF,oCAEE,iBAGF,oFAIE,gBACA,aAGF,kBACE,oBAGF,oBACE,gBAGF,qBACE,iBAGF,kBACE,UAGF,qBACE,cACA,kBACA,gBACA,gBACA,UAGF,qBACE,mBACA,eAGF,qBACE,cACA,cACA,WAGF,wBACE,gBAGF,gDAEE,yBACA,iBAGF,wBACE,sBACA,6BAGF,sCACE,yBAGF,mBACE,sBACA,uBACA,eAGF,gCACE,kBAGF,+BACE,mBAGF,oBACE,oCACA,kBACA,cACA,SACA,kBAGF,mBACE,iBAGF,wBACE,yBACA,SACA,eACA,SACA,UACA,gBACA,kBACA,qBAGF,0BACE,mBAGF,8BACE,gBACA,kBAGF,iDAEE,yBACA,kBACA,cACA,iBACA,cACA,aAGF,wBACE,+BACA,SACA,eACA,oBACA,SACA,eACA,iBACA,UACA,iBAGF,iCACE,cACA,qBACA,4EAEA,cAGF,6BACE,8BACA,+BACA,gBACA,kBAGF,sCACE,iBACA,gBAGF,yBACE,sBACA,qBACA,yBACA,wBACA,eACA,4EAEA,eACA,iBACA,eACA,kBACA,mBACA,iBACA,iBACA,mBACA,mBACA,SAGF,+BACE,wBAGF,gCACE,+BAGF,0BACE,iBACA,kBACA,mBACA,kBACA,mBAGF,gCACE,cACA,4EAEA,eACA,iBACA,gBACA,iBAGF,+DAEE,mBACA,eAGF,mBACE,yBACA,yBACA,4BACA,kBACA,kCACA,cACA,qBACA,0EAEA,iBACA,gBACA,sBAGF,qCACE,qBACA,kBACA,UAGF,4C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","file":"main.css"} \ No newline at end of file diff --git a/pkg/server/testutils/main.go b/pkg/server/testutils/main.go index 018b95c4..fd97c1d9 100644 --- a/pkg/server/testutils/main.go +++ b/pkg/server/testutils/main.go @@ -25,6 +25,9 @@ import ( "fmt" "math/rand" "net/http" + "net/url" + "reflect" + // "strconv" "strings" "sync" "testing" @@ -198,6 +201,7 @@ func MakeReq(endpoint string, method, path, data string) *http.Request { u := fmt.Sprintf("%s%s", endpoint, path) req, err := http.NewRequest(method, u, strings.NewReader(data)) + if err != nil { panic(errors.Wrap(err, "constructing http request")) } @@ -205,6 +209,14 @@ func MakeReq(endpoint string, method, path, data string) *http.Request { return req } +// MakeFormReq makes an HTTP request and returns a response +func MakeFormReq(endpoint, method, path string, data url.Values) *http.Request { + req := MakeReq(endpoint, method, path, data.Encode()) + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + return req +} + // MustExec fails the test if the given database query has error func MustExec(t *testing.T, db *gorm.DB, message string) { if err := db.Error; err != nil { @@ -271,3 +283,71 @@ func (b *MockEmailbackendImplementation) Queue(subject, from string, to []string return nil } + +// EndpointType is the type of endpoint to be tested +type EndpointType int + +const ( + // EndpointWeb represents a web endpoint returning HTML + EndpointWeb EndpointType = iota + // EndpointAPI represents an API endpoint returning JSON + EndpointAPI +) + +type endpointTest func(t *testing.T, target EndpointType) + +// RunForWebAndAPI runs the given test function for web and API +func RunForWebAndAPI(t *testing.T, name string, runTest endpointTest) { + t.Run(fmt.Sprintf("%s-web", name), func(t *testing.T) { + runTest(t, EndpointWeb) + }) + + t.Run(fmt.Sprintf("%s-api", name), func(t *testing.T) { + runTest(t, EndpointAPI) + }) +} + +// PayloadWrapper is a wrapper for a payload that can be converted to +// either URL form values or JSON +type PayloadWrapper struct { + Data interface{} +} + +func (p PayloadWrapper) ToURLValues() url.Values { + values := url.Values{} + + el := reflect.ValueOf(p.Data) + if el.Kind() == reflect.Ptr { + el = el.Elem() + } + iVal := el + typ := iVal.Type() + for i := 0; i < iVal.NumField(); i++ { + fi := typ.Field(i) + name := fi.Tag.Get("schema") + if name == "" { + name = fi.Name + } + + if !iVal.Field(i).IsNil() { + values.Set(name, fmt.Sprint(iVal.Field(i).Elem())) + } + } + + return values +} + +func (p PayloadWrapper) ToJSON(t *testing.T) string { + b, err := json.Marshal(p.Data) + if err != nil { + t.Fatal(err) + } + + return string(b) +} + +// TrueVal is a true value +var TrueVal = true + +// FalseVal is a false value +var FalseVal = false diff --git a/pkg/server/tmpl/data.go b/pkg/server/tmpl/data.go index bee3d785..321c3697 100644 --- a/pkg/server/tmpl/data.go +++ b/pkg/server/tmpl/data.go @@ -28,7 +28,7 @@ import ( "time" "github.com/dnote/dnote/pkg/server/database" - "github.com/dnote/dnote/pkg/server/handlers" + "github.com/dnote/dnote/pkg/server/middleware" "github.com/dnote/dnote/pkg/server/operations" "github.com/pkg/errors" ) @@ -52,12 +52,12 @@ type notePage struct { } func (a AppShell) newNotePage(r *http.Request, noteUUID string) (notePage, error) { - user, _, err := handlers.AuthWithSession(a.DB, r, nil) + user, _, err := middleware.AuthWithSession(a.DB, r) if err != nil { return notePage{}, errors.Wrap(err, "authenticating with session") } - note, ok, err := operations.GetNote(a.DB, noteUUID, user) + note, ok, err := operations.GetNote(a.DB, noteUUID, &user) if !ok { return notePage{}, ErrNotFound diff --git a/pkg/server/views/books/index.gohtml b/pkg/server/views/books/index.gohtml new file mode 100644 index 00000000..46ebbecc --- /dev/null +++ b/pkg/server/views/books/index.gohtml @@ -0,0 +1,20 @@ +{{define "yield"}} +
+ + + +
+ +
+
+{{end}} diff --git a/pkg/server/views/books/show.gohtml b/pkg/server/views/books/show.gohtml new file mode 100644 index 00000000..4d84d095 --- /dev/null +++ b/pkg/server/views/books/show.gohtml @@ -0,0 +1,4 @@ +{{define "yield"}} + content + {{ .Note.Body }} +{{end}} diff --git a/pkg/server/views/data.go b/pkg/server/views/data.go new file mode 100644 index 00000000..d36609f5 --- /dev/null +++ b/pkg/server/views/data.go @@ -0,0 +1,152 @@ +package views + +import ( + "net/http" + "time" + + "github.com/dnote/dnote/pkg/server/database" + "github.com/pkg/errors" +) + +const ( + // AlertLvlError is an alert level for error + AlertLvlError = "danger" + // AlertLvlWarning is an alert level for warning + AlertLvlWarning = "warning" + // AlertLvlInfo is an alert level for info + AlertLvlInfo = "info" + // AlertLvlSuccess is an alert level for success + AlertLvlSuccess = "success" + + // AlertMsgGeneric is a generic message for a server error + AlertMsgGeneric = "Something went wrong. Please try again." +) + +// Alert is used to render Bootstrap Alert messages in templates +type Alert struct { + Level string + Message string +} + +// Data is the top level structure that views expect data to come in. +type Data struct { + Alert *Alert + // CSRF template.HTML + User *database.User + Account *database.Account + Yield map[string]interface{} +} + +func getErrMessage(err error) string { + if pErr, ok := err.(PublicError); ok { + return pErr.Public() + } + + return AlertMsgGeneric +} + +// PutAlert puts an alert in the given data. +func (d *Data) PutAlert(alert Alert, alertInYield bool) { + if alertInYield { + if d.Yield == nil { + d.Yield = map[string]interface{}{} + } + d.Yield["Alert"] = &alert + } else { + d.Alert = &alert + } +} + +// SetAlert sets alert in the given data for given error. +func (d *Data) SetAlert(err error, alertInYield bool) { + errC := errors.Cause(err) + + var alert Alert + if pErr, ok := errC.(PublicError); ok { + alert = Alert{ + Level: AlertLvlError, + Message: pErr.Public(), + } + } else { + alert = Alert{ + Level: AlertLvlError, + Message: AlertMsgGeneric, + } + } + + d.PutAlert(alert, alertInYield) +} + +// AlertError returns a new error alert using the given message. +func (d *Data) AlertError(msg string) { + d.Alert = &Alert{ + Level: AlertLvlError, + Message: msg, + } +} + +func persistAlert(w http.ResponseWriter, alert Alert) { + expiresAt := time.Now().Add(5 * time.Minute) + lvl := http.Cookie{ + Name: "alert_level", + Value: alert.Level, + Expires: expiresAt, + Path: "/", + HttpOnly: true, + } + msg := http.Cookie{ + Name: "alert_message", + Value: alert.Message, + Expires: expiresAt, + Path: "/", + HttpOnly: true, + } + http.SetCookie(w, &lvl) + http.SetCookie(w, &msg) +} + +func clearAlert(w http.ResponseWriter) { + lvl := http.Cookie{ + Name: "alert_level", + Value: "", + Expires: time.Now(), + HttpOnly: true, + } + msg := http.Cookie{ + Name: "alert_message", + Value: "", + Expires: time.Now(), + HttpOnly: true, + } + http.SetCookie(w, &lvl) + http.SetCookie(w, &msg) +} + +func getAlert(r *http.Request) *Alert { + lvl, err := r.Cookie("alert_level") + if err != nil { + return nil + } + msg, err := r.Cookie("alert_message") + if err != nil { + return nil + } + alert := Alert{ + Level: lvl.Value, + Message: msg.Value, + } + return &alert +} + +// RedirectAlert redirects to a URL after persisting the provided alert data +// into a cookie so that it can be displayed when the page is rendered. +func RedirectAlert(w http.ResponseWriter, r *http.Request, urlStr string, code int, alert Alert) { + persistAlert(w, alert) + http.Redirect(w, r, urlStr, code) +} + +// PublicError is an error meant to be displayed to the public +type PublicError interface { + error + Public() string +} diff --git a/pkg/server/views/helpers.go b/pkg/server/views/helpers.go new file mode 100644 index 00000000..ee82eb4f --- /dev/null +++ b/pkg/server/views/helpers.go @@ -0,0 +1,176 @@ +package views + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/buildinfo" + "github.com/pkg/errors" + "html/template" +) + +func initHelpers(c Config, a *app.App) template.FuncMap { + ctx := newViewCtx(c) + + ret := template.FuncMap{ + "csrfField": ctx.csrfField, + "css": ctx.css, + "js": ctx.js, + "title": ctx.title, + "headerTemplate": ctx.headerTemplate, + "rootURL": ctx.rootURL, + "getFullMonthName": ctx.getFullMonthName, + "toDateTime": ctx.toDateTime, + "excerpt": ctx.excerpt, + "timeAgo": ctx.timeAgo, + "timeFormat": ctx.timeFormat, + "toISOString": ctx.toISOString, + "dict": ctx.dict, + "defaultValue": ctx.defaultValue, + "add": ctx.add, + "assetBaseURL": func() string { + return a.Config.AssetBaseURL + }, + } + + // extend with helpers that are defined specific to a view + if c.HelperFuncs != nil { + for k, v := range c.HelperFuncs { + ret[k] = v + } + } + + return ret +} + +func (v viewCtx) csrfField() (template.HTML, error) { + return "", errors.New("csrfField is not implemented") +} + +func (v viewCtx) css() []string { + return strings.Split(buildinfo.CSSFiles, ",") +} + +func (v viewCtx) js() []string { + return strings.Split(buildinfo.JSFiles, ",") +} + +func (v viewCtx) title() string { + if v.Config.Title != "" { + return fmt.Sprintf("%s | %s", v.Config.Title, siteTitle) + } + + return siteTitle +} + +func (v viewCtx) headerTemplate() string { + return v.Config.HeaderTemplate +} + +func (v viewCtx) toDateTime(year, month int) string { + sb := strings.Builder{} + + sb.WriteString(strconv.Itoa(year)) + sb.WriteString("-") + + if month < 10 { + sb.WriteString("0") + sb.WriteString(strconv.Itoa(month)) + } else { + sb.WriteString(strconv.Itoa(month)) + } + + return sb.String() +} + +func (v viewCtx) getFullMonthName(month int) string { + return time.Month(month).String() +} + +func (v viewCtx) rootURL() string { + return buildinfo.RootURL +} + +func min(a, b int) int { + if a < b { + return a + } + + return b +} + +func max(a, b int) int { + if a > b { + return a + } + + return b +} + +// excerpt trims the given string up to the last word that makes the string +// exceed the maxLength, and attaches ellipses at the end. If the string is +// shorter than the given maxLength, it returns the original string. +func (v viewCtx) excerpt(s string, maxLength int) string { + if len(s) < maxLength { + return s + } + + ret := s[0:maxLength] + ret = s[0:min(len(ret), max(0, strings.LastIndex(ret, " ")))] + ret += "..." + + return ret +} + +func (v viewCtx) timeFormat(t time.Time, format string) string { + return t.Format(format) +} + +func (v viewCtx) timeAgo(t time.Time) string { + now := v.Clock.Now() + diff := relativeTimeDiff(now, t) + + if diff.tense == "past" { + return fmt.Sprintf("%s ago", diff.text) + } + + if diff.tense == "future" { + return fmt.Sprintf("in %s", diff.text) + } + + return diff.text +} + +func (v viewCtx) toISOString(t time.Time) string { + return t.Format(time.RFC3339) +} + +func (v viewCtx) dict(values ...interface{}) (map[string]interface{}, error) { + if len(values)%2 != 0 { + return nil, errors.New("invalid dict call") + } + dict := make(map[string]interface{}, len(values)/2) + for i := 0; i < len(values); i += 2 { + key, ok := values[i].(string) + if !ok { + return nil, errors.New("dict keys must be strings") + } + dict[key] = values[i+1] + } + return dict, nil +} + +func (v viewCtx) defaultValue(value, fallback interface{}) interface{} { + if value == nil { + return fallback + } + + return value +} + +func (v viewCtx) add(a, b int) interface{} { + return a + b +} diff --git a/pkg/server/views/helpers_test.go b/pkg/server/views/helpers_test.go new file mode 100644 index 00000000..d469a9fc --- /dev/null +++ b/pkg/server/views/helpers_test.go @@ -0,0 +1,183 @@ +package views + +import ( + "fmt" + "testing" + "time" + + "github.com/dnote/dnote/pkg/assert" +) + +func TestToDateTime(t *testing.T) { + testCases := []struct { + year int + month int + expected string + }{ + { + year: 2010, + month: 10, + expected: "2010-10", + }, + { + year: 2010, + month: 8, + expected: "2010-08", + }, + } + + ctx := viewCtx{} + + for _, tc := range testCases { + got := ctx.toDateTime(tc.year, tc.month) + + assert.Equal(t, got, tc.expected, "result mismatch") + } +} + +func TestGetFullMonthName(t *testing.T) { + testCases := []struct { + input int + expected string + }{ + { + input: 1, + expected: "January", + }, + { + input: 12, + expected: "December", + }, + } + + ctx := viewCtx{} + + for _, tc := range testCases { + got := ctx.getFullMonthName(tc.input) + + assert.Equal(t, got, tc.expected, "result mismatch") + } +} + +func TestExcerpt(t *testing.T) { + testCases := []struct { + str string + maxLength int + expected string + }{ + { + str: "hello world", + maxLength: 5, + expected: "...", + }, + { + str: "hello world", + maxLength: 1, + expected: "...", + }, + { + str: "hello world", + maxLength: 7, + expected: "hello...", + }, + { + str: "foo bar baz", + maxLength: 9, + expected: "foo bar...", + }, + { + str: "foo", + maxLength: 4, + expected: "foo", + }, + } + + ctx := viewCtx{} + + for idx, tc := range testCases { + got := ctx.excerpt(tc.str, tc.maxLength) + assert.Equal(t, got, tc.expected, fmt.Sprintf("result mismatch for case %d", idx)) + } +} + +func TestTimeAgo(t *testing.T) { + now := time.Now() + + testCases := []struct { + input time.Time + expected string + }{ + { + input: now.Add(-2 * time.Hour), + expected: "2 hours ago", + }, + { + input: now.Add(-2*time.Hour - 59*time.Minute), + expected: "2 hours ago", + }, + { + input: now.Add(-23 * time.Hour), + expected: "23 hours ago", + }, + { + input: now.Add(-23*time.Hour - 59*time.Minute), + expected: "23 hours ago", + }, + { + input: now.Add(-24 * time.Hour), + expected: "1 day ago", + }, + { + input: now.Add(-47 * time.Hour), + expected: "1 day ago", + }, + { + input: now.Add(-48 * time.Hour), + expected: "2 days ago", + }, + + { + input: now.Add(-24 * time.Hour * 7), + expected: "1 week ago", + }, + { + input: now.Add(-24 * time.Hour * 7 * 2), + expected: "2 weeks ago", + }, + + { + input: now.Add(-24 * time.Hour * 7 * 4), + expected: "1 month ago", + }, + { + input: now.Add(-24 * time.Hour * 7 * 7), + expected: "1 month ago", + }, + { + input: now.Add(-24 * time.Hour * 7 * 8), + expected: "2 months ago", + }, + + { + input: now.Add(-24 * time.Hour * 7 * 52), + expected: "1 year ago", + }, + { + input: now.Add(-24 * time.Hour * 7 * 55), + expected: "1 year ago", + }, + { + input: now.Add(-24 * time.Hour * 7 * 52 * 2), + expected: "2 years ago", + }, + } + + ctx := newViewCtx(Config{}) + + for _, tc := range testCases { + t.Run(fmt.Sprintf("input %s", tc.input.String()), func(t *testing.T) { + got := ctx.timeAgo(tc.input) + assert.Equal(t, got, tc.expected, "result mismatch") + }) + } +} diff --git a/pkg/server/views/icons/book.gohtml b/pkg/server/views/icons/book.gohtml new file mode 100644 index 00000000..a04a3e68 --- /dev/null +++ b/pkg/server/views/icons/book.gohtml @@ -0,0 +1,17 @@ +{{define "book"}} + + Book + Icon depicting a book + + +{{end}} diff --git a/pkg/server/views/icons/caret.gohtml b/pkg/server/views/icons/caret.gohtml new file mode 100644 index 00000000..0a9f60a0 --- /dev/null +++ b/pkg/server/views/icons/caret.gohtml @@ -0,0 +1,26 @@ +{{define "caret"}} + + + + +{{end}} diff --git a/pkg/server/views/icons/lock.gohtml b/pkg/server/views/icons/lock.gohtml new file mode 100644 index 00000000..f43c202a --- /dev/null +++ b/pkg/server/views/icons/lock.gohtml @@ -0,0 +1,10 @@ +{{define "lockIcon"}} + + + +{{end}} diff --git a/pkg/server/views/icons/logo.gohtml b/pkg/server/views/icons/logo.gohtml new file mode 100644 index 00000000..fac485e5 --- /dev/null +++ b/pkg/server/views/icons/logo.gohtml @@ -0,0 +1,14 @@ +{{define "logo"}} + + + +{{end}} diff --git a/pkg/server/views/icons/logo_with_text.gohtml b/pkg/server/views/icons/logo_with_text.gohtml new file mode 100644 index 00000000..7f9d7c66 --- /dev/null +++ b/pkg/server/views/icons/logo_with_text.gohtml @@ -0,0 +1,26 @@ +{{define "logoWithText"}} + + Dnote logo + Dnote logo + + + + + + + + + + +{{end}} diff --git a/pkg/server/views/layouts/alert.gohtml b/pkg/server/views/layouts/alert.gohtml new file mode 100644 index 00000000..661753f9 --- /dev/null +++ b/pkg/server/views/layouts/alert.gohtml @@ -0,0 +1,9 @@ +{{define "alert"}} +{{if .}} + +{{end}} +{{end}} diff --git a/pkg/server/views/layouts/base.gohtml b/pkg/server/views/layouts/base.gohtml new file mode 100644 index 00000000..fe6ace0b --- /dev/null +++ b/pkg/server/views/layouts/base.gohtml @@ -0,0 +1,40 @@ +{{define "base"}} + + + + + + {{ title }} + + + + + + + + + + + + + + + + + + {{template "css" .}} + + + + {{template "header" .}} + + {{template "alert" .Alert}} + +
+ {{template "yield" .Yield}} +
+ + {{template "js" .}} + + +{{end}} diff --git a/pkg/server/views/layouts/css.gohtml b/pkg/server/views/layouts/css.gohtml new file mode 100644 index 00000000..4d248a56 --- /dev/null +++ b/pkg/server/views/layouts/css.gohtml @@ -0,0 +1,5 @@ +{{define "css"}} + {{range css}} + + {{end}} +{{end}} diff --git a/pkg/server/views/layouts/header.gohtml b/pkg/server/views/layouts/header.gohtml new file mode 100644 index 00000000..6d8451e7 --- /dev/null +++ b/pkg/server/views/layouts/header.gohtml @@ -0,0 +1,7 @@ +{{define "header"}} + +{{if eq headerTemplate "navbar"}} + {{ template "navbar" . }} +{{end}} + +{{end}} diff --git a/pkg/server/views/layouts/js.gohtml b/pkg/server/views/layouts/js.gohtml new file mode 100644 index 00000000..50b23673 --- /dev/null +++ b/pkg/server/views/layouts/js.gohtml @@ -0,0 +1,5 @@ +{{define "js"}} + {{range js}} + + {{end}} +{{end}} diff --git a/pkg/server/views/layouts/navbar.gohtml b/pkg/server/views/layouts/navbar.gohtml new file mode 100644 index 00000000..ea7c026f --- /dev/null +++ b/pkg/server/views/layouts/navbar.gohtml @@ -0,0 +1,68 @@ +{{define "navbar"}} +
+
+
+ + +
+ {{if .User}} + + {{end}} +
+
+
+ +
+{{end}} + +{{define "accountDropdown"}} + +{{end}} + +{{define "logoutForm"}} +
+ {{csrfField}} + +
+{{end}} diff --git a/pkg/server/views/notes/index.gohtml b/pkg/server/views/notes/index.gohtml new file mode 100644 index 00000000..168430f5 --- /dev/null +++ b/pkg/server/views/notes/index.gohtml @@ -0,0 +1,91 @@ +{{define "yield"}} +
+

Notes

+ + {{template "pageToolbar" dict "data" . "class" "toolbar"}} + +
+ {{if eq (len .NoteGroups) 0 }} +
No notes found.
+ {{end}} + + {{range .NoteGroups}} + {{template "noteGroup" .}} + {{end}} +
+
+{{end}} + +{{define "noteGroup"}} +
+
+

+ +

+
+ +
    + {{range .Data}} + {{template "noteItem" .}} + {{end}} +
+
+{{end}} + +{{define "noteItem"}} +
  • + +
    +
    +

    + {{ .Book.Label }} +

    + + {{template "time" dict "value" .UpdatedAt "text" (timeAgo .UpdatedAt)}} +
    + +
    + {{ excerpt .Body 160 }} +
    +
    +
    +
  • +{{end}} + +{{define "pageToolbarContent"}} + +{{end}} + +{{define "pager"}} + +{{$ariaLabel := ""}} +{{if eq .direction "left"}} + {{$ariaLabel = "Previous page"}} +{{else}} + {{$ariaLabel = "Next page"}} +{{end}} + +{{if .disabled}} + + {{template "caret" dict "direction" .direction "stroke" "gray"}} + +{{else}} + + {{template "caret" dict "direction" .direction "stroke" "black"}} + +{{end}} +{{end}} diff --git a/pkg/server/views/notes/show.gohtml b/pkg/server/views/notes/show.gohtml new file mode 100644 index 00000000..7051bee4 --- /dev/null +++ b/pkg/server/views/notes/show.gohtml @@ -0,0 +1,33 @@ +{{define "yield"}} +
    +
    +
    +
    +
    + {{template "book" dict "fill" "#000000"}} + +

    + + {{ .Note.Book.Label }} + +

    +
    +
    + + +
    +
    + {{ .Content }} +
    +
    + +
    +
    + Last edit: + {{ timeFormat .Note.UpdatedAt "January 02, 2006" }} +
    +
    +
    +
    +
    +{{end}} diff --git a/pkg/server/views/partials/page_toolbar.gohtml b/pkg/server/views/partials/page_toolbar.gohtml new file mode 100644 index 00000000..4d1abdfd --- /dev/null +++ b/pkg/server/views/partials/page_toolbar.gohtml @@ -0,0 +1,5 @@ +{{define "pageToolbar"}} +
    + {{template "pageToolbarContent" .data}} +
    +{{end}} diff --git a/pkg/server/views/partials/settings_sidebar.gohtml b/pkg/server/views/partials/settings_sidebar.gohtml new file mode 100644 index 00000000..0d3e5edf --- /dev/null +++ b/pkg/server/views/partials/settings_sidebar.gohtml @@ -0,0 +1,23 @@ +{{define "settingsSidebar"}} + +{{end}} diff --git a/pkg/server/views/partials/time.gohtml b/pkg/server/views/partials/time.gohtml new file mode 100644 index 00000000..b05b3a86 --- /dev/null +++ b/pkg/server/views/partials/time.gohtml @@ -0,0 +1,13 @@ +{{define "time"}} + +{{$mobileText := defaultValue .mobileText .text}} + + + + +{{end}} diff --git a/pkg/server/views/static/not_found.gohtml b/pkg/server/views/static/not_found.gohtml new file mode 100644 index 00000000..baef1f4a --- /dev/null +++ b/pkg/server/views/static/not_found.gohtml @@ -0,0 +1,3 @@ +{{define "yield"}} +

    Page not found

    +{{end}} diff --git a/pkg/server/views/time.go b/pkg/server/views/time.go new file mode 100644 index 00000000..dad46c46 --- /dev/null +++ b/pkg/server/views/time.go @@ -0,0 +1,103 @@ +package views + +import ( + "fmt" + "time" +) + +type timeDiff struct { + text string + tense string +} + +func pluralize(singular string, count int) string { + var noun string + if count == 1 { + noun = singular + } else { + noun = singular + "s" + } + + return noun +} + +func abs(num int64) int64 { + if num < 0 { + return -num + } + + return num +} + +var ( + DAY = 24 * time.Hour.Milliseconds() + WEEK = 7 * DAY +) + +func getTimeDiffText(interval int64, noun string) string { + return fmt.Sprintf("%d %s", interval, pluralize(noun, int(interval))) +} + +func relativeTimeDiff(t1, t2 time.Time) timeDiff { + diff := t1.Sub(t2) + ts := abs(diff.Milliseconds()) + + var tense string + if diff > 0 { + tense = "past" + } else { + tense = "future" + } + + interval := ts / (52 * WEEK) + if interval >= 1 { + return timeDiff{ + text: getTimeDiffText(interval, "year"), + tense: tense, + } + } + + interval = ts / (4 * WEEK) + if interval >= 1 { + return timeDiff{ + text: getTimeDiffText(interval, "month"), + tense: tense, + } + } + + interval = ts / WEEK + if interval >= 1 { + return timeDiff{ + text: getTimeDiffText(interval, "week"), + tense: tense, + } + } + + interval = ts / DAY + if interval >= 1 { + return timeDiff{ + text: getTimeDiffText(interval, "day"), + tense: tense, + } + } + + interval = ts / time.Hour.Milliseconds() + if interval >= 1 { + return timeDiff{ + text: getTimeDiffText(interval, "hour"), + tense: tense, + } + } + + interval = ts / time.Minute.Milliseconds() + if interval >= 1 { + return timeDiff{ + text: getTimeDiffText(interval, "minute"), + tense: tense, + } + } + + return timeDiff{ + text: "Just now", + } +} diff --git a/pkg/server/views/users/email_verification.gohtml b/pkg/server/views/users/email_verification.gohtml new file mode 100644 index 00000000..969688a4 --- /dev/null +++ b/pkg/server/views/users/email_verification.gohtml @@ -0,0 +1,2 @@ +{{define "yield"}} +{{end}} diff --git a/pkg/server/views/users/login.gohtml b/pkg/server/views/users/login.gohtml new file mode 100644 index 00000000..1ee81737 --- /dev/null +++ b/pkg/server/views/users/login.gohtml @@ -0,0 +1,76 @@ +{{define "yield"}} +
    +
    + + {{template "logo" .}} + + +

    Sign in to Dnote

    + +
    + {{if .Referrer}} + + {{end}} + +
    + {{if .Alert}} + + {{end}} + + {{template "loginForm" .}} +
    +
    + + +
    +
    +{{end}} + +{{define "loginForm"}} +
    + {{csrfField}} + +
    + +
    + +
    + +
    + + +
    +{{end}} diff --git a/pkg/server/views/users/new.gohtml b/pkg/server/views/users/new.gohtml new file mode 100644 index 00000000..e2d703bb --- /dev/null +++ b/pkg/server/views/users/new.gohtml @@ -0,0 +1,86 @@ +{{define "yield"}} +
    +
    + + {{template "logo" .}} + + +

    Join Dnote

    + +
    + {{if .Referrer}} + + {{end}} + +
    + {{if .Alert}} + + {{end}} + + {{template "signupForm" .}} +
    +
    + + +
    +
    +{{end}} + +{{define "signupForm"}} +
    + {{csrfField}} + +
    +
    + +
    + +
    + +
    + +
    + +
    + + +
    +
    +{{end}} diff --git a/pkg/server/views/users/password_reset.gohtml b/pkg/server/views/users/password_reset.gohtml new file mode 100644 index 00000000..6d7201d5 --- /dev/null +++ b/pkg/server/views/users/password_reset.gohtml @@ -0,0 +1,52 @@ +{{define "yield"}} +
    +
    + + {{template "logo" .}} + +

    Reset Password

    + +
    +
    + {{if .Alert}} + + {{end}} + + {{template "passwordResetForm" .}} +
    + + +
    +
    +
    +{{end}} + +{{define "passwordResetForm"}} +
    + {{csrfField}} + +
    + +
    + + +
    +{{end}} diff --git a/pkg/server/views/users/password_reset_confirm.gohtml b/pkg/server/views/users/password_reset_confirm.gohtml new file mode 100644 index 00000000..ddbb2ac7 --- /dev/null +++ b/pkg/server/views/users/password_reset_confirm.gohtml @@ -0,0 +1,66 @@ +{{define "yield"}} +
    +
    + + {{template "logo" .}} + +

    Reset Password

    + + +
    + + +
    + {{if .Alert}} + + {{end}} + + {{template "passwordResetConfirmForm" .}} +
    +
    +
    +
    +{{end}} + +{{define "passwordResetConfirmForm"}} +
    + {{csrfField}} + + + + +
    + +
    + +
    + +
    + + +
    +{{end}} diff --git a/pkg/server/views/users/settings.gohtml b/pkg/server/views/users/settings.gohtml new file mode 100644 index 00000000..a2af6649 --- /dev/null +++ b/pkg/server/views/users/settings.gohtml @@ -0,0 +1,241 @@ +{{define "yield"}} +
    +
    + + +
    +
    + {{template "settingsSidebar" .}} +
    + +
    +
    + {{if ne .Standalone "true"}} + {{template "planSection" .}} + {{end}} + {{template "emailSection" .}} + {{template "passwordSection" .}} +
    +
    +
    +
    +
    +{{end}} + +{{define "emailForm"}} +
    + {{/* prevent browsers from automatically filling the input fields */}} + + + +
    + + + +
    + +
    + + + +
    + +
    + +
    +
    +{{end}} + +{{define "passwordChangeForm"}} +
    + {{/* prevent browsers from automatically filling the input fields */}} + + + +
    + + +
    + +
    + + +
    + +
    + + +
    + +
    + +
    +
    +{{end}} + +{{define "emailSection"}} +
    +

    Email

    + +
    +
    +
    +

    Current Email

    +
    + +
    + {{.Email}} +
    +
    +
    + +
    +
    +
    +

    Email Verified

    +
    + +
    + {{ if eq true false }} b{{end}} + + {{if .EmailVerified}} + Yes + {{else}} + No + + + {{end}} +
    +
    +
    + +
    +
    +
    +

    Change Email

    +
    +
    + +
    + {{template "emailForm" .}} +
    +
    +
    +{{end}} + +{{define "passwordSection"}} +
    +

    Password

    + +
    +
    +
    +

    Change Password

    +

    + Set a unique password to protect your data. +

    +
    +
    + +
    + {{template "passwordChangeForm" .}} +
    +
    +
    +{{end}} + +{{define "planSection"}} +
    +

    Plan

    + +
    +
    +
    +

    Dnote Pro

    +

    + Fully hosted and managed Dnote for you. +

    +
    + +
    + {{if .Cloud}} + Yes + {{else}} + + Unlock + + {{end}} +
    +
    + +
    +
    +{{end}} diff --git a/pkg/server/views/users/settings_about.gohtml b/pkg/server/views/users/settings_about.gohtml new file mode 100644 index 00000000..bba9a8f0 --- /dev/null +++ b/pkg/server/views/users/settings_about.gohtml @@ -0,0 +1,57 @@ +{{define "yield"}} +
    +
    + + +
    +
    + {{template "settingsSidebar" .}} +
    + +
    +
    +
    +

    Software

    + +
    +
    +
    +

    Version

    +
    + +
    + {{.Version}} +
    +
    +
    + + {{if ne .Standalone "true"}} +
    +
    +
    +

    Support

    +
    + +
    + {{if .User.Cloud}} + + support@getdnote.com + + {{else}} + Not eligible + {{end}} +
    +
    +
    + {{else}} + + {{end}} +
    +
    +
    +
    +
    +
    +{{end}} diff --git a/pkg/server/views/view.go b/pkg/server/views/view.go new file mode 100644 index 00000000..db139c3b --- /dev/null +++ b/pkg/server/views/view.go @@ -0,0 +1,203 @@ +package views + +import ( + "bytes" + "fmt" + "html/template" + "io" + "net/http" + "path/filepath" + + "github.com/dnote/dnote/pkg/clock" + "github.com/dnote/dnote/pkg/server/app" + "github.com/dnote/dnote/pkg/server/buildinfo" + "github.com/dnote/dnote/pkg/server/context" + "github.com/dnote/dnote/pkg/server/log" + "github.com/gorilla/csrf" + "github.com/pkg/errors" +) + +const ( + // templateExt is the template extension + templateExt string = ".gohtml" +) + +const ( + siteTitle = "Dnote" +) + +const ( + ServerErrorPageFileKey = "500" +) + +// Config is a view config +type Config struct { + Title string + Layout string + HeaderTemplate string + HelperFuncs map[string]interface{} + AlertInBody bool + Clock clock.Clock +} + +type viewCtx struct { + Clock clock.Clock + Config Config +} + +func newViewCtx(c Config) viewCtx { + return viewCtx{ + Clock: c.getClock(), + Config: c, + } +} + +func (c Config) getLayout() string { + if c.Layout == "" { + return "base" + } + + return c.Layout +} + +func (c Config) getClock() clock.Clock { + if c.Clock != nil { + return c.Clock + } + + return clock.New() +} + +// NewView returns a new view by parsing the given layout and files +func NewView(baseDir string, app *app.App, viewConfig Config, files ...string) *View { + addTemplatePath(baseDir, files) + addTemplateExt(files) + + files = append(files, iconFiles(baseDir)...) + files = append(files, layoutFiles(baseDir)...) + files = append(files, partialFiles(baseDir)...) + + viewHelpers := initHelpers(viewConfig, app) + t := template.New(viewConfig.Title).Funcs(viewHelpers) + + t, err := t.ParseFiles(files...) + if err != nil { + panic(errors.Wrap(err, "instantiating view")) + } + + return &View{ + Template: t, + Layout: viewConfig.getLayout(), + AlertInBody: viewConfig.AlertInBody, + Files: app.Files, + } +} + +// View holds the information about a view +type View struct { + Template *template.Template + Layout string + // AlertInBody specifies if alert should be set in the body instead of the header + AlertInBody bool + Files map[string][]byte +} + +func (v *View) ServeHTTP(w http.ResponseWriter, r *http.Request) { + v.Render(w, r, nil, http.StatusOK) +} + +// Render is used to render the view with the predefined layout +func (v *View) Render(w http.ResponseWriter, r *http.Request, data *Data, statusCode int) { + w.Header().Set("Content-Type", "text/html") + + var vd Data + if data != nil { + vd = *data + } + + if alert := getAlert(r); alert != nil { + vd.PutAlert(*alert, v.AlertInBody) + clearAlert(w) + } + + vd.User = context.User(r.Context()) + vd.Account = context.Account(r.Context()) + + // Put user data in Yield + if vd.Yield == nil { + vd.Yield = map[string]interface{}{} + } + if vd.Account != nil { + vd.Yield["Email"] = vd.Account.Email.String + vd.Yield["EmailVerified"] = vd.Account.EmailVerified + vd.Yield["EmailVerified"] = vd.Account.EmailVerified + } + if vd.User != nil { + vd.Yield["Cloud"] = vd.User.Cloud + } + vd.Yield["CurrentPath"] = r.URL.Path + vd.Yield["Standalone"] = buildinfo.Standalone + + var buf bytes.Buffer + csrfField := csrf.TemplateField(r) + tpl := v.Template.Funcs(template.FuncMap{ + "csrfField": func() template.HTML { + return csrfField + }, + }) + + if err := tpl.ExecuteTemplate(&buf, v.Layout, vd); err != nil { + log.ErrorWrap(err, fmt.Sprintf("executing template for URI '%s'", r.RequestURI)) + w.WriteHeader(http.StatusInternalServerError) + w.Write(v.Files[ServerErrorPageFileKey]) + return + } + + w.WriteHeader(statusCode) + io.Copy(w, &buf) +} + +func getFiles(pattern string) []string { + files, err := filepath.Glob(pattern) + if err != nil { + panic(err) + } + + return files +} + +// layoutFiles returns a slice of strings representing +// the layout files used in our application. +func layoutFiles(baseDir string) []string { + return getFiles(fmt.Sprintf("%s/layouts/*%s", baseDir, templateExt)) +} + +// iconFiles returns a slice of strings representing +// the icon files used in our application. +func iconFiles(baseDir string) []string { + return getFiles(fmt.Sprintf("%s/icons/*%s", baseDir, templateExt)) +} + +func partialFiles(baseDir string) []string { + return getFiles(fmt.Sprintf("%s/partials/*%s", baseDir, templateExt)) +} + +// addTemplatePath takes in a slice of strings +// representing file paths for templates. +func addTemplatePath(baseDir string, files []string) { + for i, f := range files { + files[i] = fmt.Sprintf("%s/%s", baseDir, f) + } +} + +// addTemplateExt takes in a slice of strings +// representing file paths for templates and it appends +// the templateExt extension to each string in the slice +// +// Eg the input {"home"} would result in the output +// {"home.gohtml"} if templateExt == ".gohtml" +func addTemplateExt(files []string) { + for i, f := range files { + files[i] = f + templateExt + } +} diff --git a/pkg/server/web/handlers.go b/pkg/server/web/handlers.go index 854dbbb7..3eeeb6dc 100644 --- a/pkg/server/web/handlers.go +++ b/pkg/server/web/handlers.go @@ -22,7 +22,7 @@ package web import ( "net/http" - "github.com/dnote/dnote/pkg/server/handlers" + "github.com/dnote/dnote/pkg/server/middleware" "github.com/dnote/dnote/pkg/server/tmpl" "github.com/jinzhu/gorm" "github.com/pkg/errors" @@ -106,9 +106,9 @@ func getRootHandler(c Context) http.HandlerFunc { buf, err := appShell.Execute(r) if err != nil { if errors.Cause(err) == tmpl.ErrNotFound { - handlers.RespondNotFound(w) + middleware.RespondNotFound(w) } else { - handlers.DoError(w, "executing app shell", err, http.StatusInternalServerError) + middleware.DoError(w, "executing app shell", err, http.StatusInternalServerError) } return } diff --git a/pkg/watcher/main.go b/pkg/watcher/main.go index 041fb774..4b2b4720 100644 --- a/pkg/watcher/main.go +++ b/pkg/watcher/main.go @@ -19,11 +19,13 @@ package main import ( + "encoding/csv" "flag" "log" "os" "os/exec" "os/signal" + "regexp" "strings" "syscall" "time" @@ -32,6 +34,23 @@ import ( "github.com/radovskyb/watcher" ) +// splitCommandParts splits the given commad string at space, except +// when inside a double quotation mark. +func splitCommandParts(cmd string) []string { + re := regexp.MustCompile(`\r?\n`) + s := re.ReplaceAllString(cmd, " ") + + r := csv.NewReader(strings.NewReader(s)) + r.Comma = ' ' + + fields, err := r.Read() + if err != nil { + panic(err) + } + + return fields +} + func command(binary string, args []string, entryPoint string) *exec.Cmd { cmd := exec.Command(binary, args...) @@ -52,7 +71,7 @@ func command(binary string, args []string, entryPoint string) *exec.Cmd { } func execCmd(task string, watchDir string) *exec.Cmd { - parts := strings.Fields(task) + parts := splitCommandParts(task) return command(parts[0], parts[1:], watchDir) } diff --git a/scripts/server/test-local.sh b/scripts/server/test-local.sh index 9f99e08a..ce50d7d6 100755 --- a/scripts/server/test-local.sh +++ b/scripts/server/test-local.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # shellcheck disable=SC1090 # test-local.sh runs api tests using local setting -set -eux +set -ex dir=$(dirname "${BASH_SOURCE[0]}") @@ -9,4 +9,4 @@ set -a source "$dir/../../pkg/server/.env.test" set +a -"$dir/test.sh" +"$dir/test.sh" "$1" diff --git a/scripts/server/test.sh b/scripts/server/test.sh index 89d51a79..a2d9ac02 100755 --- a/scripts/server/test.sh +++ b/scripts/server/test.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # test.sh runs server tests. It is to be invoked by other scripts that set # appropriate env vars. -set -eux +set -ex dir=$(realpath "$(dirname "${BASH_SOURCE[0]}")") pushd "$dir/../../pkg/server" @@ -10,7 +10,11 @@ emailTemplateDir=$(realpath "$dir/../../pkg/server/mailer/templates/src") export DNOTE_TEST_EMAIL_TEMPLATE_DIR="$emailTemplateDir" function run_test { - go test ./... -cover -p 1 + if [ -z "$1" ]; then + go test ./... -cover -p 1 + else + go test -run "$1" -cover -p 1 + fi } if [ "${WATCH-false}" == true ]; then @@ -18,7 +22,7 @@ if [ "${WATCH-false}" == true ]; then while inotifywait --exclude .swp -e modify -r .; do run_test; done; set -e else - run_test + run_test "$1" fi popd diff --git a/scripts/vagrant/install_utils.sh b/scripts/vagrant/install_utils.sh index 1c06d413..46322005 100755 --- a/scripts/vagrant/install_utils.sh +++ b/scripts/vagrant/install_utils.sh @@ -9,3 +9,10 @@ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | sudo apt-ke echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | sudo tee /etc/apt/sources.list.d/google-chrome.list sudo apt-get -y update sudo apt-get install -y google-chrome-stable + +# Install dart-sass +dart_version=1.34.1 +dart_tarball="dart-sass-$dart_version-linux-x64.tar.gz" +wget -q "https://github.com/sass/dart-sass/releases/download/$dart_version/$dart_tarball" +tar -xvzf "$dart_tarball" -C /tmp/ +sudo install /tmp/dart-sass/sass /usr/bin diff --git a/scripts/web/dev.sh b/scripts/web/dev.sh index 96cfc6f7..11f1e207 100755 --- a/scripts/web/dev.sh +++ b/scripts/web/dev.sh @@ -3,17 +3,9 @@ # dev.sh builds and starts development environment set -eux -o pipefail -# clean up background processes -function cleanup { - kill "$devServerPID" -} -trap cleanup EXIT - dir=$(dirname "${BASH_SOURCE[0]}") basePath="$dir/../.." -appPath="$basePath/web" serverPath="$basePath/pkg/server" -serverPort=3000 # load env set -a @@ -21,20 +13,22 @@ dotenvPath="$serverPath/.env.dev" source "$dotenvPath" set +a -# run webpack-dev-server for js in the background -( - BUNDLE_BASE_URL=http://localhost:8080 \ - ASSET_BASE_URL=http://localhost:3000/static \ - ROOT_URL=http://localhost:$serverPort \ - COMPILED_PATH="$appPath"/compiled \ - PUBLIC_PATH="$appPath"/public \ - COMPILED_PATH="$basePath/web/compiled" \ - STANDALONE=true \ - VERSION="$VERSION" \ - WEBPACK_HOST="0.0.0.0" \ - "$dir/webpack-dev.sh" -) & -devServerPID=$! +# copy assets +mkdir -p "$basePath/pkg/server/static" +cp "$basePath"/pkg/server/assets/static/* "$basePath/pkg/server/static" +# run asset pipeline in the background +(cd "$basePath/pkg/server/assets/" && "$basePath/pkg/server/assets/styles/build.sh" true ) & +(cd "$basePath/pkg/server/assets/" && "$basePath/pkg/server/assets/js/build.sh" true ) & # run server -(cd "$basePath/pkg/watcher" && go run main.go --task="go run main.go start -port 3000" --context="$serverPath" "$serverPath") +moduleName="github.com/dnote/dnote" +ldflags="-X '$moduleName/pkg/server/buildinfo.CSSFiles=main.css' -X '$moduleName/pkg/server/buildinfo.JSFiles=main.js' -X '$moduleName/pkg/server/buildinfo.Version=dev' -X '$moduleName/pkg/server/buildinfo.Standalone=true'" +task="go run -ldflags \"$ldflags\" main.go start -port 3000" + +( + cd "$basePath/pkg/watcher" && \ + go run main.go \ + --task="$task" \ + --context="$serverPath" \ + "$serverPath" +) diff --git a/test/cli/test-cli b/test/cli/test-cli new file mode 100755 index 0000000000000000000000000000000000000000..a4665feb1b360b5ca6281879c4c1a8fa5665eaba GIT binary patch literal 16934752 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%0D)FG+w$=>& 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ⅅ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�ixNJ{K+EKdW=)i)L|sP8o_{R@_JB|HC- z<(Yunvix-{B_#(8d?-)^`QdK~

    >7-&sWOGbrWrYW+Kp<9pYhg1*Az=Xv@{c)@FS-SIK?8Q+j4Go9(tA!zE=zQ9@ zG}dRGHm8(dgk@xSL0eM63p0zN(-HLS#3q{Lt$YdQr{prdb1y|i1PMa09yd7^w?%#x z9-Lfn;&sa^u63vc=*?MW%h!SB56@mjk1IWv<{ zizE5kyl}w>-)0wRw6Hb=%Rz?r-xl5s(Lrjcn(+owX$kzTT0!|?D(#uu^+#ydZ0RRm zC`5a%O47jqyY27eK0;GjM#SD860Q!tkkV4SL*VFV$C=V0rL04YMc#~ixkzP@7qqFx zs|D7WM8f>6f~;nBULZ*(PoG{nUV zib|{l3MQlEkZNzn3346^quZJW9e53hHn$bf}=v?cP3-; z&J8{De4n43@B8t1!=H)!TrlI|d%=MjN|Zt!TGnF+GR)8i)^wx@jnA0j-&S^-s3A&H zoQMXgD;S_fVi2yQDZmA*qu*U?ESk~N_Z89{ExpR?!P0YXbG|E_tvf`P!cV#FOxc5o zPSS_381LUBT+`iVJh+iJKnIayt7~`?YodV{OEx#U|BJnSsa67WmGY66>rE-m=xMf| z%xxn|3tn^H@?|F)_tQYJzm23Y+Mo3?`h+pyMv9i`4tSHve!`$qpV9?JFy}0zblc~O z2XmZ`i6~1iN?{~m(A(KP zJUF&<+DNNUXDr$2te5Ou{Q{6KKZC+cMqoit=i$Neo$=`b_{cs934;f`!8P9CM`79i zfmfe$+8&Sjk{!e72rrzUhckV5PFW7}a6P4=JOm1pY|1=(tpiKJ1o$D*`#h2_K@QStmC9nSlzo zb<&0mq6B$@_mlhrEk?S1o3uGzx5<$t7G+1<)U)Ybr$}9pD8#DCXp{bl0TrskAj-~^ zT4c~ZmQ^W)!{5lD(KYVYs9-DdY5^lLi3|1N?sp#9DXu_K)MK3bLUuOhf;w$Jo zt)LlH3RR2Fh;6oiz?LXvtT-{cd&kwc4z*4nsY5L|>=F_K|Fmf=ZLN5TXzEug-m*eg z@7MaNnxI%hFM#Rn4Aq9SLTqkEg`j#R28ElZ4CSa1BS3m9RZdM;n!!PhB!zVUsFYrc zPgw9t+v#&Dn*+6lf+RXJiKz3b@M~4FIGOI z!Wok4L@q@_wQ%wyB78ab(}fJ0RR9*{$hqIj?a18LLte!bj0vmBlGYxXPH6$|(_QVC z-#!`kt4)gI@XIQgR5Ha#)Qe~pw;iY4)*;hnP;r;YuQF^@a1vJ>@(kL)S~dmOCQYL> zn}(=_hl0wCA#(AHg;rk9!f-_z#^2#H_VHd_xE8F_*+L5Zq!)1OAbuC4hlds|oEdhG{WJKZ;O-3y%H8 z!rS@cqs`Er9{O?GI+?NQK0Gvi6~>=mVbin+S78KFbIbweR;*mU9-4-zVz9@XQ)l?- zpuAR+XJja4m4P^z^eI595h2BBgcNFpDnbgR8}smu9pg|EoS~c|rE)F#IUCDE6pm{4 z1TtUCLb#?GuAZGSOD-XGU`!!#pubhy4*d32Ctu;=Q&wOsQg2XprNgk_reLm@2*H2V zvPP6=17u_uJ)V(rVp1Gr(31VP|0n?ylR}C+)NXB3KFE)VZzGCHhE6zDwSo+6;BJiX zvq{iOBn}*G#5MoHdj&h;Q&n_Z38T|jXT7D;MDaA1E<-xl-+!mR7DiT5^jk0Cgg|VO zYU3A9_17rU6s_lT@Fc!VIyyx_uczz}o2plclG!WjWNfFPtGwOjJ_8HV6Q3O?fxJ)- zyLvDc;KANdfUZv7#*u&8Y)M-j2Rrl6ztCF9{5kwygNWsSoRaD{MqeW1{TC4_wm04f zw<&WW^9-&K@4}K+*PGX@L_3fyE49x=F0>r0UAJ9uh@ECA2&$$d63s*;`jV)Ps)(M) zJ{?FF5g+VBw*+}jQENy z6uKEWgKNVS=Mi<$@M8=Q#uKn7;4kV+kJjh*ZkHX0)vdjaIpHg*~R~)5Ykd80gt4j2A2-$o@DL_d@E6eq}s~N_DWOm`% z;BydaB>H2y+>s}LkSEc1_KtVn>4PUV_RQm93;J_I0P+xm4xLAEoD4}fn4X8U|8XN$ z|0eeT4xg`1--QM;)pvDK{g-?%Hcn-}H=POH=z2yjQNZe9J_}WgG^6rt5)1h!=Dm89 zUk&zn9)`IQR^&96|DKqj43Gu@v7(XXbOZxXH{{2|OtrIgk+#Ftq}dA#;QF8!*MpNP zzhmDm*QtMD%!Jx3*Ss5?MCMB+@|{`d=(CBDXAk08jXg0Hl<5h>sm)%i{=U|3f6Hb- z1#g>+9_7@RkB;e#%)pS#JFrcjjIGPMFQJsT>eCUre=>?m_nT$*5ncI)Qbp1L-jU{X z`Mk5F%USaxU4H#~r^|2N%x*p01Kr9(w_5e*{`UJb4*hxJA=RHVWBLTn)iB+#R@%)huAcFe{!krq;WfzKUeDRtM6# z;hviJ#m;}ibK@nIj~t&2+^TkEfq+awXYv>EoBYD6(~(BkVPFI3PmriFeRq2j4F6Js zdH@|=SA;V2;k?8pp3FSDXw}fjkE)57>-;PUM&P!F{P;*%=yUhO_WOJD<1)SMQ0G|f%z_& z5-6~JhExIah`H7gI7o-D(>S@RGHB8H)wya#o(>}E@I+k$8~MUGyf9x(HkPSi?xX=` zP9>Zo?g(_ENhclBNn-?gwBTlrN(&kOlcB+~A9i3a?yH@#D}?&Jv7V$1U+ZuyT&!hA zO^SA7JjVTxVf2CkhjENVbHcZ(6=Y`vsbvS=R_+I)!RU*zMNd2=@uU`K5?)7a2#(Po z&cmy;Rdi~^8JjrY9nHq@*k|>to}0*yWSKdKFq7y|gVYII%nv{hipgb$6W{~X&aQn% zN8v``bs6p{G@9$lM@GKbUj{o4G(Rh!yVQq&OZAAWnOi-xW|NLqL`fD-IB)CO`poN< zdn#h7T6EZ1UV#Wg?;GIMiiC`KwEteGB)O90!KSlrY+fB9rIs>|03?SJ9E4tR#3<YxiZmnc%NXi+BuP`D@m-QY zl#EbHmzt=V8K}s6iBpr-^8IWtR?B%KR?8D04FxhBija`{`CAwxukO9Kq_P4e^>;7+x#-BegILqu8N3 z?4S5lL^Bhibj_>Ti&B0F)k?MQxkihGW@IDLjAbNm#4>Wd?uYtS-B(`Glb}b@=@zybuhV<~b`+iFT7vd;I#i196|DRX@jIlT2KqwVWl^4k=9R>zk9?0xO z41;|9P9Gu^`Iw*b=mao1Tg$Ph3q1n@0KDW*v^zl`891gnF=n(139;>)BCR2sYJPxC z!O=QR*%P{;cU*(kx<3z7{o>XGvYAqUUD;sb2WjHsg`CDRH+I|SuoQKAfIb!h53_X=x3oSAzP;7=CRYhe9$H0u1O{@V% zl@7Xm8H`6w*4 z^S_G8eQX>ujq`cP&R&99#HN!uOB|@=w7;(sV&Kl@ zhidHzmmMPZq*d1ls>)`eQ7-(K)f-$-hNt)_U=63%*0?=Cd#P1Y5azz3AI6U(rxb7rxRe@odX7pJ{b6E zPF1ZS0~@5J=2yK|%7FZ;{p#cDkwh@Kya3y|Rp3?ik@hu!u+nFB7>kfGKpxcF`SN*I zA&T>Z=mX)skRMh0Q$UL1W1+y-#h|!eK!AO+A`~AJ{rrAWGATX=tJ#-xTY?vW8#aRA z;+Y_^1_eBlLTx{wZJ#A3pD(R6}Koc}OQ{=K-laKS!!Ax^Yx!tkCQlr|eH<0DL1Hn5v=NXUZ$pEf z0b>n0tpY!On5;%)(QlLW;2?Af7W(NFVQk@-DO`~cnWIw~QY)ffKBs7io5|5*;K19o zz0@cU*;L?KFwY5G{k43Gch<|hY7KaiqxvunI2#D7DxJ09MV!~68Rje#U%?Us7-#M_ z$z|aeNM(0tN0x<81ChL#!}5kk;^s(j5=(rq!_DEG)KVMBRw_0(t<;*~Lm+bD;z)@#g3}N8oOF2d-DZ(2s!u#&B@Sh&;R$ zSkeib(I99!?U4wbn53X8O6*et>8^*qaXau(84@N|?zl?YdJb_#<1ldNFcD&;1|ij0 zO=|xc`vfTC<=+8xoM--xDi$n1pEaTZ#yO#~?a(1vuR8uyH#h9?Plh_QXNcOnAB%Di zOrRpEQu~6V1n99DF)Gj*POzvx4MZk#V1`|;i()T3n2@sLB)oFa4r7-(s3+}t&JZ?t|E2i>TIkd;tRDF3jjM|zf%_@AP|cqdYwmgA!xy`vi1*R1&K1HcQ8`f z*dGT$6R1PTa#GZzx1q+*$UvL&_SD~EO?w&s1ozwT;|Hnqy_Jz{^tKUB=Dey9lLZ+2 z<)h4yV7=P9WxRUf-RKLCMqVh?FQlQ2Dt(qJ{YbR*_($iP0Se7w1!AJ&_yWvKb}Uth%f#ds%f zGB+H;?n37jytvEG;4=xfBhEk10~-&X|(t(UJ2`RyhOEI{Xap;{h)=VhKgE)A91l^<`Pi^X zUowEb%J(f#<0>T&X-s;0k*I)3q6cJ&B4(2}xD^YWWr1%?L2!3LFf6R`hQ=g&LnrX_ zB;R9rm$M>@R21|GFDU4WA!Ibl)Ji)UyS+h2;y)aUFE4uO9Gy^%n!!d8dZ@t-V?kRQ znXA`B#=A7yM=V}mkInGB!LU8QFBfm(Dr`Rh5IbktLHuI<(|#C??HDA*8;l#xnDZF8 z@r6ne>`*C!BFGNs&kFgA6ajyAa5j%124FS##rVemp^Tkpt3~^qkGTgoWP?FJa_?oV z&r1ExcU;P#bD&ttz}SiZXg}11g<2|4)vm-d(;CB zfowjT^2RBid}}ho4T#b@G8bwu!`|64EtIG9fM|F_mqW5n!b<;)yCx_b76)XPDHKHAYiZfw1q1^(y4pccMF<4 zSJ46!8nZh=j<_C7WCOWx=CvFrkSzV+9oT4~yi85T1}0!Vy0NHhQ3@7|cr%89U2GpK zM98kTD@2Aw+Q*6$Rtb@tlZrZsNCj@-qYb1fviS%es+bfnX~xRv+4{?kdbf;FaJ2D4 zHCq=cMWe$iy`e7N(4{zV=fp?K$c(K(Cb(^Ljd9N1VKpvc%L_#y0$BO7UYShJn6gq3Q|hXDXu zfpx+v9=srFAEbZOdUy;hPo%7rs_At;v{J9&TfkDLf$<3`WJf0MQjk@SPU)~qW>62) zR37~B*An5TC6ql6JFtE-V`2=YSrDFQ3nd!YwYaXaCtr+s4C94DKCN0&gdZ$1KzFD9 zePx0y|J`l8f-j`%xt#DIf(*SB3nD=y^H9I*rLqO;atK~x-F0&5gio+R%{lv8IC1?8Q;NrhK%@DaEPV!n$0`tFD) zro0#-=4p9tiOEm2Dp5WE0jyRKX*Y!{N$_qZZV65FY#ex`@BrOzkj~K*P>T|J<^xzG^T8vX^IQ1tZ|CFuR z%a{O^Pez!B!RH&eFdZXJb1&8y@(r;TORpM$-Tw5pi3#Be$Yyc@1SFw2yiZb_UH7|0NX$Td94fWSC1O-nv z;iNtOuEUts@yg-*P?m^5*=H-KXmw5ekzeh=zT~#P2jTEv`s1iwsjm z`mWOr6~z@{coNQ#BG`Uk&R3YL)>?=9=yMiAkIh3NiZ5mixgN2n@$hnol9ViR0csb{ z!}kKtHBNpSz)PW+cs$o%&vBMjzUu-xVx9ydPoC!!%Qd$&l3{r60w=?8GHigCZ&|J$ z7eyELCGQyzwRH>8D!f=dh6f8*O8RPYYm z0TRfn;%3=|p(MYho;t!(k+B`y@CJAC=$7C*e7T9e!@a=`68cSt!gBZbo|5K(kb6TZ zc%p|}Oo03sKH6(t(E%G}8?!hEnfK-y>FCGMU>{7|$b6`pZaj==#tWCTn@B;;Oa`}w z#^Pz*`y0De`fp=b7f;JFTh?(K?!Yy;&bRJHreANgnzPTCbqsFGi%wNb&N>UN4Gt!` zmjFl@Ts_3yAwPs=&yZaEl*cKXG%z7CJ^BBxnR!UW9Jy35+#VXDIxtIt)p}f9B{M-K>_H^q#(!tHO~m6jqJE_VG=nW#Xoh z@uvMrx3|rs4dhrI)(v9dxO(;hGC_kHzMOJrxzcsLG^4PkSXe{!QO|VL^APHxZA2BQ zjB&k%gjT~nkHGRKpoQFa86C5a+=EJk51s2jPDnI+C|CDZ5{E9(@jO zX?+zj=dAB}xaVkWk)yHXyV^l3@EmkVQbkx+Fl!_gYGsl;9iiFCc)@H#_%G5_{9BA= ztP;W&MSG-Dj+}oI3PT(UDa}f`2a8UP#qcLI2>bm(sr%uM*BSw(-eb&K0&?ap)k@t1 zqJ@`r(nePo9F$=%{!1${f~FivjIbxP5~J{$l;F1FFmlo_5QjG&Zml70#o>L?MD^)D z@x>^~wP&KLT+0Ofxt0kpWa36t8ZQ$u6cqB1VnDwM6p@{0!k?oJBpt(Fu`M4qK$|mH zgl{lJV)v90U|m{2XAd*ori43CQTwJ%F}b;PNXG=1p>~Xh znBg0t1T$`gsCHmN(|9L66NWf1ILI9yC5kSCDc8M6YwSDO<6LZ$SgF=+$cT)4NNx%r zLddN4&&Lvy5n0$jAA2Vmi`V7ntTtvnN`~}Xjm`4&>vv#Jyj5yXycX=nf54lF6O0?N z6+`$wRH3%Tn z5K5|+#xMZIX%N+2p`6(N^kyyGmg~y_PKYz;ynT8u){xCfnhuxgd`@c)RG~w->1Oi|eVSoyWtGl1YF{%WRmnTCyGEv8>%Mx=_p8xSq#fU?mn5Ey7NAeQqVk z4TuiX`>Dobzmu!R3%=acTOkqH`I5MVU`E^hPL3%w5~x&Y2LQ|hWdu?5PYv1aIf`qU zEM)f{7#*^+H4NGDk=%9r{>Wut@{t^EV(8~TlFM7;r2W*MK}hBKa<95YF#lNhTLtWS zS^8_dcsXdR9+qoj%atHbYdQGnU@JMespbfBP&gn)4%jtiy`Nj7D_#!pkv@bi5OGYt{`jUB<#(3tXWYgTtHW5 zr9QjUK_DWCPviJvGV+%RBE<(%cWwXZm~4#HNtu3b{|_*9r+x~wPCEL0%NC*|-l9m5DmSn)8MR=4_&4?Nd+TY6bWmx(Uh{yj zX%u3H{{fUX7G2qMZ$r8fScMT?O(<<&1AY_Av8zlGYQ_@pPz6qX8ir|&Eyk=r;F)P% zi?1<@+$)nVgnAXBK&d;Fofq6+|DTQ~hDyFaVpaqnDqfK1YwA42cz&g)df){u2}{uy z%Hta)p_keQ+VFpj(b?N39&rRV0c$%0ui*~ov1uQbo0*KK7r zxGU3792AtYE2@8jq?!cI4{pGAM`4L8;0xGQETUS^t~LRVc9qS>boC?#y_F~t)7!^E z@n~-unmfr^+{QEtOY?l+qjb2MWzgYLM({*dqE8+XHv4Lc?IMF7_fJ$?n9!R~)e^VT zlM30;?I6d8EgyY5KqjQJV|{GFvcxR_T?lPGsiPp2#v<$EAidfHe=@ZkOg1v zsW&yE>w!Le8Z1nwYgHnQU4N8EH2gL6^RO=Vvz9XG@C@3ufU^*PXN-mY6-3iebh<8z z_QYAL>1VM~R%-Qx*k}MI0GO%#1xXCma5iq^vJ5LVc!4UO$@#D>6+CcJJmk&;g-n#k zbU}Dr2m})BYgVa6#|;~^aCy2wMT>qDzZEF2@Y`Uat_>hDt=X@D_{Fx=Lh1z9Aybin@b-qd2ns=51zP4m9SKSIK?do9m3lUD;Ov@9gj^(& zZ>X>Q9cuAQfBgzb#OLf21;%jeGI(C3uUR$Am#`elvm8~9&xv=j{Hhs5+OJYxqrN)% z#XO4qVi~FYV)HrqU6|3tU&ME{{HBQcc;tujE%O* z+Zglk!=b3?Ksb&29K8g!;mY(FUAU5Z?Pamb1}hCT9ZG}k4d?MOM`~0qV3S~HHBfu_ z-33I)6Mjp^Llp{(_4e|;3b`fhKmcZ9OLzk(Ie@Re6|Tp1UR4uUmdn6yXkw$8_$hW~ zsgpTlA5^%YUt@uVk4SmI+K!I-INQ0P?}q$D?8${A`x?X8Y(SrlhA;M8%*5?x70$@U z7uPfMFe4H6qK3D#for%gF42o`#_KUyWIBaj9n%kpZ0zEIGrmBl5I~J(&-AB#!7(`g z@uXs?YI$!nI2dfe4tH>WTgYqnCyYG`(89E2d-Z=0v^0Ne`ljMj?7&&L8};aYkKoa# zoYu;lyF*#^-@=V=H+=I@aZ5sYh*a&$IvVm%`F{ra>#jWt@>3-~1oA0@Jlx-hoY44L zjr!z8Y7};e=s+%q5R^D32zzEgD=Kl@aAV($BWP5nJd0`+g^C(h!(Wv5;Fsdda-=e! zQ+l%HSMA_NjWT)tziL#FCST1~?CI^yR`mSl2RXZYt3Bzb102G0mzd=Mg9nz%QFV6_ z-VGhIy@dAjnD2`L(FHiwJsGL4aTjbO8nW-737SGzWVnb`8%Y@uP1w}b?F)+01)>4v za&u#lsP5TUzN0v+$BBY5P40EOok54y^|C7{*V=vAU;S2QHav2ZV-?rmPjM0s@8MaE z{r63XzOhcN^?nDZxX^T~^XM5G`R;}=`?tYtI$zcK4)ri*ok~0EJA^Nm-ly-k#(ayj zCKItCpRkD2X~;p9HoIDICgP#c023SO)i-yfWIu^M*6dp;aqcN*XdImRWVbhTF_x2z zM4Y>IO1f!X192MXMjkB-d>`%(b*Q(Y7LAi%374~7>|b)F(q^3$Lqx8%nHjwY^&aY( zG*ON=kLL^@Re8*KYC{jD$~Y(<@fAJ=#{>xXE`c5-eFr7}!od)Ft1!f71;FqUM&eU& zezn4IWzu&k^+JUPbhebI`{Yqrp{jmW3ccY!8tC3XJJdK8I%P{}vlQhj~Rm=4m-Q zA{&l9YCrxp?&xf|XLl?cFd!NS-I)wwAsYsR4cJ?Kx9HHF5SGap5S!(Tw5J!Rk)S&RGfd;3aC+32#6xOG zEaSS@#J96NGtaacITw5GeJk9BL5Ou{GnOQFG-iIqQocjI$_)I)$B&zF9sY`eLr8r= zQ^rj_E!4~=b1&JTGTq2hxw%ne~P5u19*r~)yeeXefZl-9k-CkhI* zJ{oZ>WOFK(FK#AFqr*L5Hor8U{BU!ZsB+rn2%+p!{<)QZCh*U7{4*YZYU~MXVLs~- zam1`Am+zUkQ5Jo>vbo<6QaSjeQ(fs2BQZy@w?4hRj5evzSh}OL;S1pvK*^KEP?_vh zCcBL{G*rZU#l$WPrX#z7UdG;;8X0I%u29xv2H^nEZ`47W{B4#h;o-xy=4bdLZQhB6 z*$Ba*fhKttmr740IFh5pO8nEFw{g z8CTTGTWW7WM&#AJcFu>R!-vmKMn);YerMI;sDDQ~n3gxlAy4W90~8+0!7Dt(9MDr$ zEM}8rHe!P;hBzcq*r3Y8BbuQu(GI5`ni*S@>Tpb$eoF}-J7M~7h*eOk=Rg>3cz&)& z_I*E4!u{`o(I>+sIE>c6QGP?-@0uDf>qHZVHo^XAg9=TOMO2HnE0W4SdZ){+o0`-u zH*rEKL5!*%6>`h<@g&t34=HT2FVV;L&NV=g`+vqG7>A!RlV3)(j%S8|EBH9@IPFusJmzy~DTgHPPoV!uS28^SH2LNp$H%Zz2R8jCsNf|_o4 zv^a*z3>WREUsnWmn9OECS$oiz(I7FafO_o&iOc&?q35p#86ma~8C%is+4kwzN72s+ za2Hx*X;bo@v6TEk#-2x_^MrUniEUO11ErzoRB;cCGQ+| zR7$!&iB*^0q~y4TN2cU=pGPVA<%n2HRuF-m;!oPL^1Dlpiom>iZCJVWwId^NM+^ct z#UZe-H(9x16<9ea%1Tj;R{Zo+6x*CBmnG~QNvTbzgxY_IeD(uV0+FIU5t9ihOYwaP zYD#z`o%I<*>rG$d@mgmlA`17dNMnHe&hUsM8GF3rNQMit8ASHlHtO5jpZjS! zt0m+s@x*eh+eOG>EaKb{eEvk>>06Nn4+(9t3neI^CTMReh6W*C>N8Ji8O;4nj0_^Q zpf+b0Mr07w(Au1CfA1W{*|9=c){86{xe~p~iS1P@`X?)WiirwW-@kDjZkkSL!h|vB+UA&xFG{}CH zY36Cgl(XCfTIRWO-yv};`d+2vv4ZhxDdW{rW?VSs!91gqN=&D{g&Q^ege34cd4yaB zR!D&4$lk~e+C3+oOg2O+VY0(=9ha%hEY9=;HWL1zgam>RtV|ZKm3k!palX8R>9Lu? zV;`a160(k^46FT_IiEl)B)K2{Lj6mj{uK@8zWQR^FanoC71e4vG*fJ2QD;<4iX62n zfm%KMS1l%Ibcqp@BBfTRdm~~}5-|uR_KqFOAQWry?_V9u&Kpw6vfSlhS#^wv{~x1& zcj?P7q(_=WVB4NU(}(f=J;F09owj{d1nX!^g9+?M{&4nHFOuWO2@|0^?N z>Aw>Mslx= z?Vy~ojgM|v7GFDppq#_u*M>56in%lYS*VuNb{;&)_w7Fh$DZS@{5J+)H*w;aiGRF- z%W<>utRSb05qJcT@+$96z9|nM(O}_v5%v+=6ykQ8}Q)=++3GXN!#FfZn; z<_a#KorA~xQnOI%`nNIrI@~bszX*T%;ZO;*yddzoF=rQoXzmzqvRv@nq1e&S$oIXQ zmXE|7y2*0+p{w2IzJ`;jKmIY@xeWK(L~qW{>6>}9$`u?~!CEX-Sy?QUO~*UbL}p?6 z5oaA5tIQQ z5y}OdBOS-_ayi=!t1}DZ(?H*S$Dt~q#O6&a=_;fTR=SZ$ScybJ5_0#94489;M|0bE zPG-BXPBb)dx$1+RfzglxV{uUy zi4@4BNCcJW@H6NIK&}SD<%vw0C*Xa2EeJ$w1YwemLyOVdl{H1!q^842IpFVnsV!TC zMkm1JWhh19&s@O3p;S$sn!ZDj!9k#vx^6IhALr+8?}{Q2ND&4q(p7v1Hz)|kqN{oa zx2q$9vF+@87`AgGZ~ggO=qdz4<(D*~T;Op$GJ|Pvpab}Ytda02C^Cm?=R6}f7HZ+c z_t&__GKc1YT=iRvi+pP;zJs2v3RtOU0YRMSl#gj2H4PV6Bea3ni`>5FG?IC5=3`cZ}T}a>sDeS(pZsSRm z*DH+W2yY`$jytlx7~(4^VEw*t57*A9)4UD;aFoCruP@GrC|c)k=MUET23|lJB&Sj- zzX?Fdv<4YCv7yCmS#9pyVD`mAZwdQwW+0Y_919OlT_x&fFa^F}^-ko17yG6A3@)v% zNi*nss8jU)P$677`3YPk*!eS98%O2nvS5w9@_%aq%!wk7m7p-;xq-SQo>kg_&?F+N9vY}wU_`$m8>C- z(n0>Rc6-TG_$Zvu{s~t%smMp_lUMM^Z6B{UEKj47_qIHLW-Mb3PJwHVc%7Z7CUPSR@hsjc-^A8B6WXwr{`bzUoJ zT>+5t<*v=sI1I#O^7wVh;T_Ck1fgu3^I>1GH^Dt<9#h>!YO;W!?7Hw$Ddx{8rtrdq zl<4155oR5>RIOk%15MJ3qa~FTAa`(n$XyJzP>bAwuvo5<7jp0+QWsSNx7~B;LGClN zt@%EC{=d~rB7%Xs@KUBdX!ytnh)yyqgQ(X?rO16!>;&|1;J%-n+^v;*;h@n8;XCmpPp#6(OWeZM8Qz=*W7Ze&B>DaDwOB*A z31gAC2!k#j5eB~QYG8Oe1JCi1SwX*lBX~2hEo42mg}e+-B<~Fq1VOY>!hIpXF#@OH zrW}I!O+n6khL39w-n8Ha#y9#ARHnZ5-!MV;wGs62iV>E&Oz zNasb9RKz6GPOn0MRXgi871D^=#qPif%dZ{-;BD2@U)ITdtRk@}Q2mFCLk2)e1gdb$YlWZ% zqZj9>QSCwmZBEBWqVGS!0farmRo8tVYPZj$vQd?IM~a}b5+@X^>@8?HTewH&NErmS zv-Xif=#zB8I`KlOs}I=WSQ(B;@ajTEQy>90XlcL&M0#OHk<>wRc=m0|7}v4iXihbQ zjRHd~V)gK_YhrNmnlu?4?>NRjaEuIJ$#hN-cwzKkkKM;E|exk8J6!-Kh)Ks;JS^eMam_mF2OIR*O~r~7!|SRR`{8mu^X?lLG%{n zvo=f)3Q(%BG<0a?i|jKR2GK6u){bUcBh&+Yw!lFD|H|5 z{i5C`bN*=VF0lXU8Ls|i_8D6N56=8ywevVK8MS0-F>{(^&OwLU;l*>eTr4l-I`dm# z=#e!_Hl55__sgles=Vbt*KtSiLPx*9;V}38I`jS*zMsrl?Uwz`mYsz%XJ0h#oJ$J8 zUKvK5l~QUuM9bzZ2$;r0VwwoJ*0=z~hoa#(tJm(-izv-W(9lsnoOZ^{Kf)lMO!AmFPGop@Z{eqT^%VmV#ShHkpxUZ1c*F=|Jn&V zvpknpj9yMYf&@z8Q2D`meui=huwLmP6KFi?%wFlvC{6E9TusTo&AIV?IhU2Ck*DyQ z1$c|*;XIDV`c$Ws3M^3X?C-vFxqatZyhGuX zdW2ITSQz1mhZPS4F2oNjNljPd7&TTn#qydF4%D8HyTVzy)F+&yK5>PkY{(Uk0$U5m zBRmljzAM)|T?q@WScJqRv!DuZRM%~SwQT0IS=5kCJF)BJV6AmSr`& zVuJ)O3_?EOY}=*`BOjBD4ExPVMgUG$zku{qYStQSk*QIDvq zgoaS2u7)xD7W4%&>!c0+E}kkyzZG{v<5DMlfdQS4m*JzHB-K#!=jC6OlzJvTK(TZ7 zKNyYZhL8?OdL}0oPV7TUaCJ(}>A9_-UpW3DHy1R;YFf#YwNhjIuHu&31~{%_VKV2i zZb}WDwm&LGgAb5F8+`&ob^ER$R6>X|+*Fb1z5X!f7scdcM69|wAyTc$WgC*aJL|R* zV_{Vz0~SWeIwLN%7~xbbN5-(kV>Wm07c?7IJYLu^d940Y2-=^k^qLU{<(oO9vDcYM zMcHt14Ad*9yxmgQjOq^b+WoX`Vn*xbM=|hYe=_C}N7)M$y3_L=pM7uKw@HK!jFdsd zW_WB*61oyIZDE9-x5OoMh>uWbD+qO6%&4Z3P@TxYs=p>dq!^`XD3|k}#V!eJ4)9`i zAxTK)yedBmRxN)tSPeiK_W#X=DIbGm&YH4(xFBZFTkO;ILp3=wpLRK6gQ#;gc0^FH zN&RolnT6b?RCO1hfeMI?;C3sqV@zHb(Qsz+jhfOzJBD2|3ahhvU(lYM_EHPDkmccv ztfFKF4mW_uk#Re-WZ*#epr9yvM+(3^ws{alt>ok&D=^^Q5@nSb{8HyS;ILz4@s)?d$|6;t zi^Gw!$@1dpPRM#skV_XLRga>y`vNYzn>qs@F}whOb{#(PP6joQTy?rUgS*@@AU&}T zUoiO}nHTMhb0ylC(LIcjz1(#?>L>p((q0Ll!^f7F-~09TLLHDB#(Bgs=P?5xn6_bKBWks5*N6p9p1 z46Y)%>(LY=oTq2^JKw|0=sG(~|3M-ZN~tu551TI@V6LBdTA=$7H&CQ*MUkLijNek% z;@gY4{4@)n?DIF_7v9-?JbrX84&_EXeSDH9)Lz>H~{r} z|6DHJO!QuLAO+SQPkla&-l$EQpWnr2XTm~d9;miIt9j=l@BDVlU&O->+ebxZmZyz} zUH7w;McuLoZP_Z6VgJXy5DV`YbN#L0-AUoyjrmg%*Mk(1IoOO@46;M~TrLEOm(UEH zsqa${nTraZFSvLC; zz7^8)pq+D>s7YkhP7(7u2VS*>T`TzndD=-V~VvR!Q2rL935wK4IvvRl>%y}CH zut--6=JfRj%vmU_b;@T0u(tu2KU@X#&kqEF;C-)vN7i>m%CCUNo~1F)7BYop*_rD61|iA8|`^%g3_L&p$PxNXXk|1kTM-44Q-6% zH^cw2RvRNj7f3Tvd|H^%pazk;cHxCycy9ZN5L|f=AZQOa=!GDlKTkEJKiGZ$ieR*P z$M{GwxO1#-U{UT-3>d{&scV@ zA^RG$Ip-4B>7ItqPWzYrPIuUOUbv=yFMeX(9@bAx`NsH(t58X_G@x*g1@f^!M=$L4@3#`qS<^FS;u7g_gU#oVHpp! zL-$~4G?Gw4O7x=#fEcA7$+75u$!v&>By#`~jjIZEZxLforVZR*mgJqZT&^A+RMQGu zZmIT||DSpley+ta2{>~B+RXlKV(X5P%I`rEPEP2H1I;V{P4W_3u}APxd>WKI^}7Ms z88~Lpaj^Neqp=BqgWg!>!VX}*Gf(H(AZQR(?DVcha#G73H=;v}GL5%x;*(1>=^#HlU(UbV< zbiyiu6mIk;scDiLnK+mxl-Sy({4V^(UZvykJv|G(1>3k8pfg3!R(s;@^X*_~i0ze1gJnf+*^$>+9J5Ey_ z<)cYEpY6JUNOJ^}|H4ehB3~Mhxe+)E=j69Yii)A5Yz&QI>h*yNB89akJ-;wWp)~}0 z#?T}8CR8}BO|tAZsoSM$d~v>g9xy3`H=2V+b(e@5h|j8f&v=b5ig)55%6OMo(A`AP zoDO+j^RPhvHHQmZUO)#NpTzAq{^7O9Khm&APw5lB(zPs2=JeRrTI+XT8f#sGvRdcv znPBHkZxZU-YZCKU)BrhuW(`#({CrQ^P!Wq&jEAZ2%y0Nrjh_O2;Lwe~K_&u<8X=|P zzj-!iPF2697havj+~>5;x3?EQ6axl@3mIzDA>EoDZ7*k7Y3$>Q&*O5CzWpziV z&pe`J#+pQRrVaC|&f*jm;I0`^fLvIV$!R?D@6)wb34c#va>EZGnu|jPzNG#aO>CCm zm6-GSD8=j?E|Trjb*vIA^j|M)fmkQ=4Tu%8wv!ZxF3A+DTeCh>1CoRSRq9m=TC5+k zHmKNY`Bysv2otr-AO<3{6W0sm17cMgYIJ6b(qqU=Se&qg953b}0DO-BX|Wb+v3ehK zPQ4XSzT$yWAa(JS{)Ni@vgOBl;f-m6k(c{P#xYb@!_^T-1d2VZe-$t(jI0VV>M@?(ZkWy zvRKO&k$I<~X(nLMvJ%}LljdH~%`IfOZ;M7(N~03^y=@i5^(GUcTiUvxgw`N{?{;F* zvSziaev{aFII>N|^29hmf|n0`W$ym{6OKhBW)4Z9ktKUL=DbPwXQQ~b0)r<+{r z73+&%OO)uiqm2@qKQ&R}ZdLYcXIu7$ExSRLed3nQwq?UnR_jD>hN^smK@{U$@NYn0 zbbnj?yEngPED@#?n_G^#21e1M{2%z+(hN34UWJa6!CV*d-g-bg4=LhVX>_%aPs^g|C! zoLfuu?zOXl>F%EqdMV<~+eibo{?0`jlD_J=FRJirpvgiqnvr71QZ= zW9|>*qX?@RI{GNPgLjuM5A+>pBNjAw4iW=mct$)fqRdO_*2~CIH*i$E*!k!t$nQ3- zW4`tx6Xk?P-+k0-@a4r3a$`$49&1TMF{PD%L*y*5py6keooJ)ALkVLNM1Eov9@wW@BWhco%++V&I3PN+m?xyEF30 zMJDyqnkDGN#de-@3He69VDEn$zEM%W>KE+Zg^Xv6t$p~I@~@DHgw3MUyj%}mOU{l+ zg0n+?rXd;|;IBPnQ}+0&T_Spw4-klCiJUhxrWZSvSM4XAMH~=7M}#pL=s0ZO>&sY@ zUI)X9HX|Z7NOm%AHBl`4<-G4CbN+d;R)zfc3?w$!XuqRTg_DOWRX7C7b38sfiynnp z7J^0`eZlTzDHRLj#pToS7v0GizNcrCTB6JF_QPN=ZhQTQxcycG+&*wMg3oh2DRL_$ zw>aQmA`@T6f)n5e;~WX8!^c4M(uq^!HNX!lvKcp#f5}cXsEJH^M=H2gR<&Z=DTES4bM0$ zc=rTefsp9Z@LVA^j`ojA0f)06pwatY*ad^Y#-b52lUP!^m|roFVZLfD$cao^D8{iF z+QH2^Ryz)tA&30#3$RC6e2c0^v?ZT}j4;}=03-VRp8gTgbi(3r5I7>nV-cq>NMk}i z=kklRD&)MIp$g4AQ586NctWYDLTV=F6!G;4e083E7;>B>KNy9$&?v-Ar4WCoLd?Po zxu%tSr5dPQ686K!!; z(RSZai5ADBG*+~)rW+FNAf!W$7p)k|{?Pu^*Z4!(K(|^!2`MzFidMpPGOEVdLyPyw z7%;SsOv18k0)pW+nea)*{=h1bR*^~5SSJ&#G5Fg8=ekL?)^qslX&K*6sYB$|=IPHM zrSmS-y0GPKk1nWe>z58jN;{pPbfFbVOy;!1SLY#4ee-TmG~3En`&#q#MEsD$aTluH z`OQ_(e93~qQba~x4qK4;p}Akpah2Z|unY5ePQlQ24AZ$b4X~R_HxDu* z^O~nWSD)rLPrrvZ+hNF!B{j{{OYwC({U8p1!{BRTb9>MC2f=aA3p7mczU6^Qjkmv* zZ(!PQpn^$`j!EVe;HxuX4q$qM-YFT|2o`v*m|`JM&7h1heYoMV$aONWogw}02``id z(=5%>AA+36&e!Ce;3Y?O9FMi+oOQY)X9sCLXAr(RKVh~y#j`Fz4*UbjDd99`Vsj+k zTN*Lsn7Nh}hOYQd{IpymQoswr-y()#PDpto(ux0LxoMuCPu>E(ga z-F>AcfzoEa(!)@S^Y5#GGWRy0J!83BNoBwpGr{LyOc9972}vqG1@?o_)zWfSd8DNr!dH1l(|0;nsn$y*0}cUS zo!dA`E=OIU0Yt>Gnd{EJnd>`*389x`X%fr)GuInisbvyyk)iZ56WI%h6hx|`+Or@a zt27EnPgZ4f-Ll7Q*)Fj#Y;d3Y_q_|O8 zf6MR+CQDR`+p|3dB}3n+s7-1%6$8a(&QF{&pC(_;(6=7m#!6HQfvFK<#^`STYz9KU zW)x*kMMj^b5MdSHI0eHso%qts>DO1a5~F7Y`kVvBsH@b9aYIpA44cm8n-7oY-=rS& z5DXDqT5ENGD7M>%-3GDj#?i*q9{eu)k1U<@?g*;U@hhV^PfB8ePxu4nWC?=o3= zy{fZj0I~yhb2X1Bmp3rbIGGi-eMb)wanj$8NUfAfTAU|m>+PA#R&56stf8BrS6zao zCs`L*e4hV8>yU0O86{(m53yeSr^g)SLH7a8N9steyU2JYhkmS|R~J8DBf5B0E31pc zRvBGfgfg6eG#*mD%^jn0^)1<_>t;3n`!qxKgbl(r#(&3ys8~|}3+kMH9i0Zz?OwN#SLsfVj@kTnGZaUf^v zz9PLVzl~r`&JbM*hACo5wI!!wp8#7}l5@!bs)1K9k6)x8mjk}8WkV1LTNFK(#Z*+W zZo034TGcK|xH_4HKVuVkgNoq|gstqefBJl+Njs#xdGGp1)66Lix(PSs96Up01-8#}uWVMgU)B zsiY*L$|h5n@(bWAalU$*?uJ5OcMDI#?+ilxO?E!|EbVUIk(H&|-Ncl;d7};4lO8pE znm%i2lrs?%@`RU834r9he}Y%5bKlui=X698iIQQ>t!htOZd7~SYO40aYk|o@u4;$% zw|Q0q;wRb}Nn-g2&6Z;927Ulr?p6{SqEU&fL(P6(GaZP>0M3AJPu9$@quJv3I+k%~yU=Q{niDV4&7E;qKK z1szDuD2i1d2S2@^$tN*+!>0kK9_b5kbwyYoP z9KQ^$4;Z5J%<&6}g2$l%j%!*H4%i4sHLI}_n8_a|^|^|pgcuaTN^n3L%bh)Lrw=&= zg`4F(bF50h<4!JruKpm6Fpluxw0**pNkL2$0?jbl`5d7Hnbf8Q9!lT_I6eDg3+vhn zt!pcE9>tV9n}LwTwQW1b>)H-}gFvR2>SzhyWqdZvYFdj&Ua9rc!y49L6*`bhoIi7>l!Twg3v?-`GH6NfGc8 z7l#PU=fS>y)c=%o@>mc)0AWb?UNXk#j+9=1aQ?GM#^Z-eu!m>lcvU2E7{0!U|C?bs zP1P6pzy)ManM5O@04WqaUxES=x7-Q9U?Z)~T=R5zP_Wb8_(2sp7o+ zt%@0ppMsgTWF~b8LwsUOm-1Wicjx#W^5agD60A(^CjUYK+8m@bRt6<=BQr#=Va&ib zB}Z;1a~?QNlh)Hqni^v}N=ReZdiBYwYkd$tFTrQ$TTF7Naj<|pXxp=wSAQ?V;HGDi zXm?*>2@8`s4{x#FI^sBt5 zh>N;4x+8lY#ds95w-aNuoVO0}m^vc4EllQ&K2-zO?iG(L)r3gvy4N8~0=DW31sFfS zhR@E+m@fAKlGoFV*0IaU*iK~Z2yHa3y(9mAUgh6+Q*|e7fWAO8)d^Cur2ZFa_Gz2`Q)phncM8+}kA`#+oIRkN&Pias|(Gc{oed6ixe zpW}bJ7u1YUAZ1;EpOCUP=`kz}F3~dV_mW43YUcC!-;E68H@Pyr7@wWZn8)_Wa6k{Q z46j*68UB4VWEh3VcI7Aj`Z3a(HJp=W%tmyxHJl94G_fsv!MGQx`o_}QKK zXXG=*IzC4tRTGu0FouVKF+6g}!ix6z-1*v?i<=FPe7};P2g~O}R#ptgx7|)u-#&`m zvY>K7dB^9`fIZAqxnHR(X6wr7uIgHjUshJM#b3UgUWJjw*ID7#8!zT8&eJlg7{O!- zybWMgbrDE*C>}gnYeKIV41Dm+yP<8If4a?R!n~(kO?U*Ko$oNg?R9>~9OXrD)Dj{% z@Jb-K)TGe?{61Iu023fZh~Eg$47n=5$%_w!o(sP&b~(p^Az-&0zvpz8&xb6p*p1&J z>f1u_k3nz5al@t_Roy;L{$pYMpJe!d-oyVcivO*K|FyTd{J)3K&Uct<_wj$EkN?HO z|0wXEV>Bqh|J(KOpDe+=#MV}0^bQqd#k|indEA27uOC8~(b!J!#&-IIcX**!8jV%| z8e8*CY)@Q5h85x;asXn@AH4ew$*u2F?!uU|xl&%6Q*cgpbJV*ktP{J9Qw*Ei z@k2nqk7e->dU!~zFR{5}WYRAJ4E7`8I52WAV?cxDZ5h?=hmSIGTtgfo}HCE%jmC4>95cy z;Aof~?%N@Lo$QCYELM$f;&u0`{Aon28bJ$Bjv{)w`W?7lxTAt*j_eXr!eCdFIYDIJ zFm~9ccO4ILagb`V{Hq2LC!4LFY(K%vW7OD5{KoV&HV|_zob3imL{S|mqX?8Q#V9NU z%2S^)!a~bY)kiz&7fMS4Wr^*hl{js%=zuh%rz3!$(cKGp^{WK#{7V7cTP|?gDgyTK ziNX(@jfCm}Km5swO;so8IIlhJBMBSwp{Uq5Kbdb~6R)|nv-%A5Z14RUv_`6Wv#dJ8 z{wxO2`{x2CByMNqvSSAn)kT_>D+E1kP~gz&ZbF0@gG}gwGu(r*7UQck`Isdi~B~n|yQ+M*uGON0i%vhHnOkg3d4!9c;G93VYltCz25rLHwX5tlnAl}k}o;fMZ zCxwaLUv4DS>gL-v>F25!c37hB=HN+{rDh#i_oQJeW_X-pCVQu zEz$cnscK(EP4#iP=&)X#kFGvD<(E@H#}U7UX=ZLdx} z!)MS5`_`Jz{?8fnx#wjut<4*3+3(MqvINR%owF~77KE>#7XRN?$-k@~`?*T&=iC^X zJm&5O?1#Dac;HE`N9tgfgG+X37JI{;!pyogb7u2FrBBk>tD-=i~$P zQcoPf)&!kF%qsMFKK!2%gLYKEE|lhyy4gtI<%(|DU;KR8Vp~3pzl@UYpvC++LLv~{ zBt|cCxd);a?Zbrfoq`jU*%eGS8w2cGAHJ8yk)#XD@NlGxAtcp?aRhtx)>|!|^t92^ z&t4D>KGH2a%a$!fS*>#i&W=x2SeXRmp7BUd&*~#Oqg9JR zpcGwlhf*_u#+YD$)4fLJ=2cSu+mGrrCnb*USONGgqlAiRyrcAPuud{&PHBvYMf$8H zfZd#y`+G&jfPWQQ!{Hg+7JlLP)Y&{45Hwgidt$0R{XHv26C55J#MvJS9hAa(d;C&| zGZ7W1Rp=$xrT&IL5MXX3LCjOz;g56Hg}`rFHvHZv6CwRSjh`&vC4N-^3HUMOsICC6 zcAztsSQecu%f0l-CH2*b-%e1eJi(6Ps^hfnp7)r?c9}ZBbDsU3wcSF-JK2|9=ya(B z5)sAOW#sHi{2?_3Isgtiz-!Et=TmXcy8w*m;lcXy!GX%2i#_oV)s|>fE59&GQZ-BS zU!wW%Qhv7Z=G4qoq==V-%Trzojf!}he0QornNrEC20%vk@_&hg$snQWBQ!G}E(PE`2rywThNh=r{ z@G!ua#Nv-4FRtC@iLq9HV-5MOCyXI?LK)8g zIS-iMZj~)F|86YaouNOXZy9*+a+`sJisr@p%40R&xsPPx-Eu=VykDH)$9q;+(?B@h zIrZRuSDLM4#rqUCRUh6{_w(Yt;90?Yr>`yEmp^XsegI{)&UNQDfVT`|g^Y)2`{wjb ztQt!8phng7^ds?u(1C{v{)8>*R3Gfe>A;mw@BsV?-=eMD{yaJe`SD%V8}c;v1=*)N zM02q)4bsX-6nLXIg@hK*f5-?4Nn!!$Nc28uJ<;RPHB(-oA?aTDJugyJlw*`*=|p|1?n;dJa&?Hr}y^@_(>*|9Fq)|6R-ftzQ0x3ogvO5`u4`*Rs>?bdwXL z%qR~ETXK=E&QH2cZk}fEbwy9jeAyfi^J-{+m}8i4D_6g@NuP^1em@e-?{FQM#|8~7 z$;AD5rq1Ej&C^Nzm=j0VZ+@(9o~j@++z&I={Tali{(;|>!-JXErHTRo=5_S~cjvZKfCZeWD`GQJ&wzd+@KytcL8478%y`Unb?QL z((8}b9&g774U(u+$I=e#2&Fs7s$=OY%%U4hI+qa0GH1MIVrjd*y%6+zQhd;@YpoC3 z`jGKKg($0awhjY4T>dLo7Qjc(FGDZL6Y^ywN=zcOe%)jAYoA*(@R74IRDJ<`aw9j% z!$fL|Mhd~uk5pZ4Hl+UELnF1}{!FCSeI1I_y_i2YNO4t5>MX4+2C21sdXd`UUxL)7 z|FKA|ookTV^aPPweHM_ae}4HSSt%WMKAtFQQ?B zD+`vYp?lTd5_CLf6Lc&aQ=MzG-Ylg1vGYi8_k&}Rf$qnlyR#;a55x9jOn9U0d8h?m+6d5ds1K)U*Eh_Z{)34OLAgzh))`5ac7c zw2e%Xw4JN<)qXA_0beKGp#rF3A=_>R9LFK&yFf6ev6bcDH%hZu7 zJI4=U_c+S#alf@>UeanPl5}HqfCPCZ&Sp$dr`J;f3!L9>bQQDEDkd_-&Zi!(6*G00 zL02<}?$}rVv5Hwp#ni#_Zq|&B=NITwbNf%~OpS>N0iW&S#U%1DF-iR$n9!PtNdS*c z4d5ZJvKn}Fni9aHApSS9Q!1dao0sT)s1ThMmM_X(SiY{#220PwG%WAlnF-6ApNGOS zi8X6jP9`k8$Bn9DU|F`a7naR)3Cp{u1D0<+suGqjJVJk;u?H7IH|H5)8(at2tP;sN z5* z1!Iwatz!E~b6Rbe=za5Ngbr79 zx2bHwCgAV5dVvb7yzKK(1im$ds*AOTn(k zX}OUpGCP&bSzE0A(fnB+M4*ARQlw6Hh z5(Ua zede+lrOyxZX|Mr3cJ(<*p_Yg~%jh$lVyWo!Ie3~!5S0z+u{kn@bCbNI;iecX0m#6D zi?@?GANA9U{lmXH#sQ*4L}p%5OV;F$qt_9x0WH zV#69mu?v)97dm_L8INKE2&>q|sc)#*V3Vdh=3uQg+glEyHL6G1PFkR*tSWM~<~4kF z7GPqWaGL;^zoS=cj=x{D=8iR1YijQ_TC=YzyWTCkz?QW`S*`QcDbNfa?P)bbeF($@ z-9LhZ8ws02{|MGgb9orof?%)HO4_B>l>)VHv@CQ(KH?@Hr;Y&%4!QjlfIBzdxXDWz6?>xlAc)OlM*jb@VFg?X#(HqfY{O z4bJyA5xXdl#sGZkCKs`at%H{7KUvd5mFZ&qFkrDKN* z?3en7IiQuy51Sc)&_-cQog)97w2M>Z`{cHgwr%D6cq*^XRyp?1OOXsqF$x75Pv&f2 zq#f;pHyiNbXw_!nd*37WbhJ*M>u5j4XXlJdfWMw;NBKkfUid%0OZ@ENZ&^PZyUqC7 zS5;XTw`^Bib|1=WopA#pwlmzUGT>jMN?yRsKfcLHEO4iLarY88^x`6w zp7qRo(Bjn1^;hs?5ivw?@T^&WZ6A%{kXjFh1&{&m|9jV{)1!KkH&yE%F7nRBG`&Zi z&fMNha?Ks0PG7CE>eTjDqfS?#tkxM213Q`PnPomet?*hhp87JD@fhLDI4eDn)nj?Q zmkK9(C=GUso`D(L;r4|#KWtQoD3rqmunsx#(N<({*O^UigEiaRbr>^Y&kzRZdQqqJ z2i>b%o|G5N)h%e1&zhjE2fS3&)`LX4bJIEckP7E&>Kff5#9iK7i3nfBPb2uL8{BK| zqw2w82IweGsu1mj2ThS}2CV`}&?ss9CN%qL9}gi3+t<__iC<8PNp)l3;L z-P|GGrPhuq#Q7U2a8{3#KD4fZW1<732;({NYjn|Sf8`iEu7Z%!!)MnqQP+VfeHjN~k>lyD!<7qIbD|Dl$N)JGwO@{3 z2G#V>=f$?Mnqzq`YzYrSg%n@zI-ZZ$RLh`Zb*rv*?~p`L#rW=qZ@edoBxQ*>cmdq*D99d%=|qxz403}=)Lj{Xj{v5b``;kS<1Wu4%IPhK46Vp^Rd$^;(DYWet}QV zn<@1zS2r=BK`~d3B0yQoWY`Hosu2ChqQ%Z*m@`kSyVtQl?3T09KGh?cb9moj@;TqB z9tv)%;o*}xyL%qk!9Fm858#aUB{l?qlQbcC!z~h?`?_T>+OpNEY=5_Gsx5m~mF2l* zr`ocaD8u^iC*jQkH89ffvvoeXY+VQ<#SiiJcv+{AV>b&RiDbYK%oceO6 zVzfHvQT!;<6}i_y%vCGO4`(MP%i;Lo7A#A5f!iLyDY9yuvVsA@Mh}-uFSIKZxkkwqjFcv8ww#ubKV>Q`jgo~JSztxJ zLJ{*HH@RZ|={O_i*cl?`;}%*me}j!OVrb4AwG{K5-r&9ixexHG^X($$rx=IfAJEs0 z`vg8Gc}l2H)0v8SJR0*sQxC z8p1)pL2W+x_)#So$XKUaXsD3-HG&C=Vhj5P7Gc&_?Vpd2BrQ@Zq*k{}^zOKnRvl#{ z<3Vk#`uJRaR&Q78&6fIxMjRl$8P81TbI8|b^Ivl#Q}SwgZ#;f;YKOvjUvR4-4|Yu| zK;%QDGoOgA)n5vn+s~#3PM2R&W1!S-4Vn-T@B<#_e>}~a#BlI7NQ^81K)R-7U1PU8 zr|y&l&FH`?+*V_fF16b^b&by86#p>yJ~wEyNFC@YqH`eZj+b z1P%KvCfePYSQe#PGavLQlk*0ctHlfEQ4!Kw7 z+ONEQ$9xh8@@H!Di%8H|(|w#LM=LUGf*Xl)zaAALMxsibS}eRHjin^7jNXZbxlZxl zl`PnOJm_Wm5g>#&a-COe=s;!`!+|VTsoVuI-}=2BXriI#O(~Bd@;0ie2sIXH^i@{i z>lD4_8R-|HH=)?;)wK?{Ei)*PSbPAZq|0>MFj<05oSa&y3YA%p7rrHynARj?GYX~q zQmPH?vsE=2>hfOc*hwu<*bqqads zheS9k(t@?r5$W+uD^RJ_2GM?D3|28U<NQ3c@r*MEfk3h@@F@}#c8 zICm452zB=pkys=@MMQlQ(gvW9ZBI;R|w zt8+XG|3D1m<(9k4go{%nRohh;gfaVC$ggbcreIr=7eEKq)(fkEQR;Yc>U3&oVhg0Y zPOGBz8N8-QbMhs}eW=IXgS9t~ll%C7GN)q*&B>gckYCx>Qf%J!q1G@b&Ys=jL|@+O z_BGiB3&h`NDYA_IbTLMfR3O!+?-}4mf&yfhj@9WGk;~_n&BWFgV+K-v;zuw(beo({ z|3~+xvIm~51F;wJf(zcx#`u8+$gvrh>9x$%Lfc}2f`E4JG~3_B&vkxIklRpygdzLy-C%k z{0-`=dsqO5=@A}5hMd>Ad%SLoEL!OLhs$pJ$oOkCIh$ z&NItB%o~1%dBtKF^I}afSNRhtSjB4xvPmQ1| z!25~?K{K4);qCdg8!e)U#sI^S?w#o^8QmTUMNjaC5bp8Wj!%}xBCobc--yuUw9%vf z*Iz|1h=Eid1fULDzUEnuOUP92*D_0yh8gGFg3nHOOy#F}_IWEVn3f7(Ymt%zWc9J@ z{O$Y}slA~xc&v3Cd+;R)w9xZDG-Wkk(Dw&kb4&|H>&}>K*(n%&0O00Gfk)kf{^g9u z;fU_>z{yLA6H+BXw@uTr5tH8XLBo!_DxrK&{m69Yo0P_&;3QAP2Il(pPl3St5rz` zvuWRYF1lE-5SO#Qgtg;wQ-n5^7D?4@;E0P;WW4b1b_UcsR;fQ z8MaVWit+Sgai}LRlY^~8oJ2Hr0kufHb28`tmKxNfFV6t=$R`b`r~d4M+7F+d4SoHf z_S)bD_40ob)Y!fNmFH??=1V-XxpDsXO=bTr;uS)Feg2kbX2l{0;NUFW^@D66L>sG` zj3u!1Io=6St1DV)%vxXO!Av>c7XLDs?Yq`>ygTEw^L(E$$BPjQdRe*eLOb4_)lXi> zD~CIPBcsG`toP#m#~9-MVOQXNODNux8izOhZEJZFYqZp?D0QYj%rRa+?(dBNzWvWl zRbnnh!4=4fFPbBzb6Y4~ke&&+a*Z>d*CKU}g3Ifl2l1z5&a!4&P-kE25tQ=JXFhHO z_4GHcf1ZQSPUM74|GX8rqFt|lmfok|KflMPJYw1>{|T{)T8|HniYO!Yb{b%M51tS1yA$ z2dRVvHvtb0Mfx`;eh12RFISeI3=%z~#XXh78mwR(()QQ}geJjAAU3eLD=grDpXcrDtTG*?bjC9(5?Uhc?t0fJeKRea4~x^P0)SqyUUPRr^M(+omOS?}Z)-Dc^n9TqB`PI><7&Ue6$V8m9EUxpf(k-=>72eYXh1Q9tS2T7LRB zfAUz*#sBrn=Z;a7PkcAXrz}%GYs3&X#5i681Lhyp1b4*o1BOtV0zZ}UQ@-=<5Y-m? zejE~k0bs?bu^MXxc(lMdzMYa+SvLET*NdbnPEzP`6rKFQxU|eJGF*~h@y$h+U$BZ{V}cCRGxPLjErOYFGrrlmc^9yp2bOJY=7#|PO_le- zjhFXT?f+NuF52Kxvc}2#)}8-1^1gOu1M=R`wlQ9)Y4R@mRyEo*dEd8FBji0_*>^Yp zd}fJfd>1C}&W%9dFe3Ry?o~awa0NOG#S`1-nC7pGY#cPhMT=zAk}B*@2h#Xy)K2qr z>Hp|I@RK$mw59c#qpYvVEsSWBtwmn^Q7jLZkF8bdiO#|TFnCZvp#%C{n-O1>->8|a z<`-maO4jQsLO|9fWgfDkG%`MJl> zb*Vv&aC|nI)8}G$8$rKGYAzZ^J z^#!q!Npjb*T&qwiXiI2PF$zKx9Rt12Ay_`d`Xo|7k?g>}geKouCLaUi%!MD~2!}tM27rV zrHZ`wb|5}GE02Jp6uQU!%Hnn&^LqybgL`k|5Ug6F3DxM&#n+oZ_9h$$j9|jisxx$; z4#4`NQ6L^w48m9njETz>)mlZaLZfN*7?@wKZhVD>vN$1^c*wj9k6N)3ANR;cb#Z2T zjq^iCP=2?+!GOPbN9iAA!sAZR=BS$Eb5UCq|6>w7FTQMPGa4Zlp0t?7bt6=q*W zTIV}ce^DKkLQ+|PWq?K4gezl0$<8;we2Q9?eJb1FojAA;r)E_CpyUb72lV(MGU*d) zE{tgeH(Zo*mqtk}+3TZ3?@4z-7?G-nMYE0GwfYDmjzWi=0f%}V7L-lA6aA=oF72>N z#9*DrVSv<0?3K@Zc&Xtf@5tN9i+;3B9>oJ|UjoInZe<2f{R-x=?%`&*2B^RCH-;Q{ zW(pfkJ-eUWOckx zha>ZHt}Mc8-DKWzGxfrwWZsPVi<;r=1GbRsT&$P5fE;3<>|8L7&UkhFz(ntHchD}e zk08}OhWX>z+yKeZY&c+S55mY7+6}oIM3obrBs&OJR z7J+{fKPXlW^GcdfAOSFzl!#8acfW*A%-SRF+vpbS(KC)&qrKBhr5U$=?4PI@4E%xX zNV3U4x|QRIJul5EKY-nCO62s<)B*$n1ws-E7$(u0S{d&Q?nqfbnh*AG=T#p?Z2CDi z{iMsGzx~V6ew6JOvwc*ixICBbV^79{^8PrP7gj1XD1}7{Y)OX-SSfgS=6|7xD^#kB z-^%##2oi_skR{Q@&a*p^xa~n)>Yw6%$DskxcPr_u%|;(CE)Jvb5=&o6qv-1b`gq!E zH_`_pWiUt58SLD{k-Vckgl;ELhNHIBIlFbBSjKD*Zn2&n?oEj$QguAP zRqvfT8vnY|3C5=rzE{lma@;u(%g1RVt__$@ zymLce+B)&|C>6~HnhycW#477d;H-4#xd?HNfRxnL8s{k0e+IQ`njsGV`sphT@~`NN zv0d4;fc%v92@<3E;+j-Yp)a}_^vRXkz(d@GhWh{^rLsLxxta175A_7;csQf|M&XmC z{Vz2>MaqqVgr2HK?s)kU)vRq^e5tw z2Todw=F3@WH;V<~nZ%OZ#ZHoKQ7iLl9jmF8s|-}~Zvd|cD1tx^qvd}Ki_jk8_JZ<% zis12~vl?xS;3jn_rpaZHmW}>K_XgSxrk~Qi0d=!V>sw53V2^#=q4|#Z?7WOgdCy+@ zytlon{;|Vwk?D)u5x!1s0pGD&9STJq&fzz>*fa32<C)@a?Ujyt{fG(J21Faxi2LG>_ayx~>5 zioE%FjMq&Rk*ncjmUY7%^&(l>`C=ArS_D=K1{NO!-C~as6XnyukQJ z?(_a~1_~Yl=7fv4F@r4~xaiVu+jq!!E-*$cBbbNM>-qY_3Z+zYB z3tv7RzVN74l*+W`;CWZ=Rg^F6#c%K|W_N8MK2-c~gEj>2!kb z1G%}3m@ZrDmEq=7A;Zxvh|8OGo9omZybke^Dz5&f3^|(_l-~&GZb<*Glx<1>4kImD z{2GULnmfrWe5M6_5E}JPb=`IOxH$YGEtwO7-rwU`t;``pMC3H+ggP%8!?{6=3eIgxOIE98lLv3lWae3Ni{=4HGM() z3kbgx_L#`1`~qCumCX6>W36IUeLX6sT)>1$M#UDdQYv-;-k6Wi&QX}XPw$HM*B>;<+`H;%C%!N%5c>0P%fUIX!6HMm8>v;a$)!9i}(eB{0A*f$SPpZ?+H>230~($%)0J^q#c#+Nv*jr0`IOBK^g z6+-WEO!FgsI7;K=w(WL%$7Qa9e>dAKw!g4P@(u92H_TNeW#;s7kCgwYh%YV^< zVpxW6<;;*Qui*I5PZ)$AxFVxeX6 zmWJj1KZ@T4`Y_E#<=?Be5vhGZU1Rd^|ATyWmOisp#KSSvk|iJBi$!8j94}}k-;B>pYz|zV z-OXNi-TC-@_v-8&<+HguJ70YZUY(6B!DMJipfS#1>&n>_QCCUhm$0j|Uqi9II=dA< zJw;#2aftXD9eCRx;|aVxO(4-bcf1L_6LF6a&c#Y|3&z#FQ|ML9Vv}c+w{8EjH?&3v zAhf=;nbPgN2^#jKFSM@aH-y&ZCbUY}@WsQ(#Gjd0gy*;aWs${useE(s&WoQ|%f;mc z$OSv+-`l%)Z9goZ&0X8~t8eSt~Eft z-^vAQB|bYBV6B2zvz~py3)DCL0Mxmg2-Hg(0n{d+X6>kf+8+M~@cAk`KC<-B>eF+X z_-x?;0Ap}KL?%AwB5!~bc9C}oEUi{+SnfF51B)7NzHOX=<*V&oSU$vO=cFC{u-x^$ z7nZmC0+xZl5S9mi0xWetSoYAcbiu#j`tz5@gWuJ0C&RzK@G}YO2!a~Q&nhZl>rwWP z{PWQlQxjx9`jo8wHD|9XmH$fSrSloJs?^TMVX;LSvf~kSpnAA$S0S$AN1$~j@qx9; zymXS8?Brnn+L-Z1q+8kMnUGsahd@uE7m}?~{#N8XFp1I8&KFi~Bxe8l0c15L-}=9! zZ;4!hV(I((C(?IpQ|bE){Tp^|iXprL=btw!sC4>2R7iRDd#G2v{TueL{_pVlN)x3A z1(yCueD(lQO_JYV$Zx1i#iC!%^V56)^$=Qa`K}T6cTxEG#2Kn`^lOoOWYKR#S`1*w zIA4K$#yyu9(kOHunWCuCqZE(gZ0A}hhtwqOD8)UrwpnyaK7U zq(|nbaMneZ`6;0Rhbw@F0;gZnhsb_H!-Q`^!%d_?robO14RieM|8L~6+~4}c-xGnB zY`ua0>R<849(|ZR)yMt*(tct?!|gYzznn!cNJsYmGE2u&YvHxcQab6~yXSsIoq}3L zzn)*e_&egaI1SrsYJd5EBhS+5K0H7Amh>DBdK#1`PQ?!!|2s_y3-;A3t9|highm}d zB$5<5pKD6AZJi>NJiH#1OtQ9hKlR$K`uxApZ^!R%R%{?1dy|R=^gA-~|DgZ7UNC9o ze9dk`>zEXDsS*9(CfUQE8$bGO26d}R<40$viT5+kpRk9<=wlZD>RdHbQHoS+5d1SV z6Y9zFBV;jJA75Kf%60-}jnv1#lK<_1E3fS$zsBc(LSn@_fY*rpuQBx1;|EKu{Yus+ z`Jed4B4@gxK9HY|OA*5NziAwOe?^|B&T3Ge-#a8`0ElUvJR=h~W$$l&Ej=vSDaT9D z<3aB?s#JoK07~P(mcI4s%jT+g&C=7__Q77sqk?kKDNhPN{!ym3j;Mn9=km1 z?SY+Cj2_tXb%bfJuOTzu*GEsRrW^Bg4=l&+f%*J#Wa0{iR{?V#^q%=g%@IYaxWSg5 zf$Nzfm9s2f=xksHrMxruC5^s>-R>fqU_J)N3vk9|&a8zRjP3_{z$n1`iQWfaZorth z-knyx8lPqU-LtUih)29^4nImxA%E>6uHbHkHC{Q-b~h(54@8->^czC3$5()0XWgw* zi=K|w5FDl<2=Je|UR*gtHX;k~IDYMnnXpk04#<*{4hHVE)g*=+gy}TR!D>Ji?%on{YGT zE2lq%9NxqKtDW!G0t&8=w-k}1+)7xP(NVD4EYdWwLdFs`+Al;KQ-n)TSa*LM@}~nc*|A&SFbQ- z!S;3i1*zBP+D8KKgZ>*WZ&}{YPhEr?c@;ca@SD>BesgREP#{>#L24N+0zn~&j3uvM zO$Yx29sCc@t5_NutGP^^d%hAivbna<^3AKZ(iv8R=!6)97><1fBs#eKA zBL8L6%md!{-TIeJHRZneR&he6Khge758kLabOim`RS2Q1JtGdS_!K_tFYT9*`p>&c z`yYQya4uo>cE3b8e-lITQ}(%Xnc+QXUoJlVEWqLQS6Bgh%TYhR0XWoVDD_ z_g7o}8$NqN4p2z{C>a}`hM}(Y7-|PBrp;=o8}Amfy!5*K4tM$W`UAAr&9qmX+1|Za zZkn~d^OcPT=`S+%b<3=8a7ca2)W${EH|#Ro3vZL~`4RbZkY3c!jgoHt+XD4_{D+d? zti@geym)H)6{Oa=F5FwL^y3lNxj_NF<@){n)TD;u&$6VU!TY`|&tnw@nhCgG}^yU4XF>Td;I(BHa!UE|DxLsd%kj)zacJv9{ITCa~dcg-T;2@hrVy+ zdj>8`RB-s{v-0}@N05hyR z15dKgvnUQ9=}F(wkwI_9M^3oX;s}i2Hv*4OHmhL=q&!?+omg0G({_b7_Axrc z|C0reSN>fu9-Qw9^_IKxnmExkkfq#K;KL_8e%_6xt3g<7{2V#Sw4@LZ))&%0Iyt01u{O{=U7aBi@H&DKB1NhAdec#Hr3r=Ycq0iEPn@!)d_?H<8Q-PO1 z`~KV;OmUX?=Tw>pyzjgC%$Z=yn;btM)n{8~ix=Y=KaczKC)suiAekYx`DT zCfVwn6ff?@vec}GpHN+JFTJjQZF2c-LcHi+Uwa#Mdx84uix*#DNokP&jCk>=>q{s_ zpZ{v0KRO}2et&hyu-`i&(?o|KN$FIw)$23S7&YC>dP4(eGB;8LWK8h z<8xPHiD_2DKTX+SI6uo=ego~*H$K`>uZO zd$pm#Tfe6~e7rS0UL1o}tU=gqyl8JVLxJwYJ3L-|hgF}!`oOv;UhEg1KU@A^$C-kv zxh(cM&jJ+Ae`}Y2kNjMGmi^N-5dQv=q3>Jy&bP%r`YioN+4SF6?zVsAn6UQk`~53S zahCV{jWrL1)88*{%9|W7PW-0+ctHo`y#Gd)cyaLJzZx$NMEid-UI=M5rEa{qXHg^L z#XJkR7f)^Pj~OhflHPJxo)s4F@bWp?`Y-SYJ3o=Fe%1cK$Fr4Nec8{WZvlTxOtQs` zQ8_DpsEc>+{EpU)><&rQh;@n@#_-*ylu-ztDK_ zegow*8o;l0Xg+A=JINON=(F^VR zn;b88SzCX+XomZbojJ?0#Eaiw{;Tm~+kgIayb#jPI@OIASAd#E&0kEkfP3-O_CCj8 zNel3nyLk7rc!!r)g!U&tlHTVroczVBOQdc-^KaKKrx!f00f9edR8HLtOqm@^Q=O zG*CXg0sP(%ec#IW44W?c=(F^HV7m{#a+kjMFEOZkR0Wr&)UA#m)C9ee*0`7UVQPNdJKQD>cV>Ib@l5= zm)|DDi!1AE?{H=FK7Z9fyx23OzKnR0c72J+FFw5@tJF@_v>Q={&C^Q^(gxNO6S7)+0m6(z~9uD zPYSh|-O34Px!-_$^P z_;|9#=XE>W?8C?M-`}RYzH+zyer3kSy!6}m&p5{vXL*0fx#j`y`)>V1{$a}f`RE9! zTm9=FrH)R_Ia|y#Ij3HTiH!$(|CDOzZ|$2opFL+D3T&i0;xQzqHw5=jaldS1+kf}C zjZ91nnq^;D1B%>*nD}BBVv@9*@xGjYm|o<}c@&8$_w#1KbD0I)i>I=eb~6l?L49}e zo@nv*zVDXL8)@qBmMedIz-^{1Sifq2Xx8?vz8vMzw}Ahv&z}s&^3<#y(ZiLEdFgfe znd9;sXsdWxAXSunI;2WsFK76`|*Y6*{=;?EZKK)Yi z``EXj$BU=+C;NrxBT7Hk+rCmzKdk&;4}ag4SL?Hlj(OYnl-q#qi$~%4=9~WAARfH%=*B(Cm(HN(;*+emjAhK5**Io3=2?r`8N%e&ly%fe&OTER=&4c zc*E)MV$)q;xhtO?hlaIp`QPtMQ=H}fea|uvg!8{|Y5nDMLfcpRy}s7GEAkGF_rooL z;q8a_mz4gu%W6NW{iCO4Yv1D2#iJiyeQY2f?Y=rwF>S}Iqw11}^E1)qH_%>v{&67| zqh|HT3Ef_xzWV&*l_B+I_{W|;_u{wL2Jq<+UccWz?qY||lt1w3hmzlm$p++CkZNPY zr%%6?eyntz1O1Jsek=dE;qSZl_01WE2Jicxa$7;rpM~t-{o~Dg#Q27_G=am9cesCi zBBVaB?(vW7Z2A(;|0LTT^p(5(Rk-|l@N>(THBdgW0sJJzpncNZdxi%<5ocK)0J!7hGEN4^ABUy zNL1ylidDxjW9Dry;ItrCw*a?X|BTnyVb&0#+LidM{5=L8Te=Wi*!Hh=wi zHN5{zFvmK_X3h-{o34fKe_-Fd?teHH$E&jZ;F%n++*9v(cu4!jk$LJYj^cK5XanSO zDt;)%M!1sPkULfVhc=wl&vY{6&<3Y>uzjyTNxgQ*7PF4a;Nvg9JEPo>-!*VQ9@!gx zYa6RxgsU!N)$i~I!Tw77pinq5X4l-R6C| zN$bvukSYd((m4MPBKAo5+`A#+k>0~7z=V&!$VB>n&{#tK#O0>=V~m^!?-lV6j`GZ@ z*awe^ji9MgegYBtTsWL+qVJRL#!sVY8Q7>ei08t|^~$g$bHiq?^C=J1A-(nnVyI*c zM=%a zeXzx{E+*&We4+3_yQ?YyX+6JgHA%(8JlR#L z_%IeN57fa6wF(u{Jr-WSOW*Db`c^K8&y6KdX&&qGj*W|AF|Ew0V9&5&( z)qNAG1ie=m4#F#?^z(y2{HQ_dnqAZkVMinLHpQwo#Ug!I2?7I?yTZ5F);^H&m`K$* zlpolOg1y*cEeD}adlR%X7(frBZR02*Xyqte-P{lR8=J?fd6k~}(t=;|>)wsb!-dD> zL<@(Us>{=DBhG(s2Zpcul_}HzG*@_*5KrZQSnVi^X+&)Q~`sXKT6-3%dCzFH`m;$5-R^0#Op@2yOUq9H4Bho482k-D~Hkar73I z5Jjap0T>^Qjf(mvdu^wgS5H(c%SU5=1Cy(1PbWIVXxT7)=LLKHda@Kvle4N%uAN4K zXva5yCb0TaunOqgfeIhk2G4wgz5#JLTrh?Yax(zbU3frKP_%MXDN6s27X2$?_F=C8 zbNx!x`%1hW2`hxA6fHJw0Sr%D=0zfhDzs8V2#uqu{2pCx6xA|RR0q8rSb&$STb;T} z^i~n?r$Jiw@tfkQ;m&2Z0mX?JqYo@5X%LTV*yVmk02_dA7?-j(7 z*T7=NQ9f{>&O@Srx*6@Mqv@8hI;k2*i@g(>@=xrA@BT^UC-D?>&g!>P30A9~!0o4a zq8*>8i&oaf9lnb@u}?=3W66CjZ3(_#JA^DrVo_5HKn;aQm_VT4q=Bk^8<9KV&fpp=I4ezf;4?bJZA~RrJz|DdI=W8wCe$h?YEUkg~p#^!Qkf zxmNbnKy3`s$>Sg%<{z#$;d5ozhln{;3v#RM`lNiJh9owOhbd~Qi=z&br|OB!8%9+& zB#|0G7^PPzNp%zm4OKjc&Lf!QKSf`1{~vYl0#{X)_5p)}lA)Ullgg4BDy*ikv>>Cv zNH=;=DoZL$rmU#3B0UI|7wB-r6Kq3^X09chEGDZCA)|LL9V>$%D=#0t(#ssp2GmK8D$FYb~&~$gEZv_K_liP(3)sH0^)c$r~W0*-D`}@X*r-|xVf<Y z0aSz5qLIk4L+{%IKjJ+aNI!}fndXq)fa+oKq14JJM4j<*nvH%*4(WeK=2)_{xx1;(5MFct*Op2 zL2NP<4%!0r$a&dqEqsS=>8S&Q5CmcMx9ou*vj58BP^%4%p0{)QP&NlEEHa8Vh3oJL zRFsD4bE2(GCEvqE>r%GT9YVnXumv$y@u3LTLRHXA(ZQv`vk7u^)NyLj6P{1^W~ z_$NV_8JJ?^iPl{C9<_5}kMNPCTdwU~491S)mZK8Ak+*C5h3qz0PJRu6i)0MW~Oa7#%hMWUU->HSFld6XkGYXJAf{vlKiqRpneTMHq_5_#XU; zjopAXJA=4}45Vjir2(`tN&)Q3M=}FQus; zX$xnO_OREt(!B^a&-}q&{~6m$;}@>u;Vq8$*H{@uF90XSIF1Gw4WBrUo=GW<@yad) z5aCCqe=sT7sI%{B&p>~W$Vm**_y_%=4Gui7Wy)nbK`4|R-wg<1QjX-qyEFAWA0Hbp|_0$|->N<@nQm-98tXYqe z9PPfBZj4bj;h5pU2o!lRI%jpGkQWr={l&#dauzqHrj<1M(o%dO09@jvrUml9=*yFY ze+>e68yP~)K>m7p(r09d!<+=k4&XduUkINF+^S|_em)FOu|-Ye)tkOhv-siV?nRgi=)p#Pnj!JANI6B z_tZ&$)~vI%{sl`;0@I++f+bv>0ftAb!o2cvJ}u^DhDX4&e3_xmcf?6-K&+sK|9^Fy{VL;*;6+7v8LYswmnd)H~ zoT@-Z0}?yv$){0_fb0U*@JIXY+}@{tI4$e4ipggU^7* zb;wufjK*=0bQTAwG#`GF7%l*Y@d5EgA+Qd1fOtAA%0Es~R;xX<5Qj8Bi+IDX+aj)2 zXi-)z+jYz@Os4jtO*Dl>E7Ayb(}HhE|Jtz*_c1A0Sono}4hRtzEVmp-?#P6mBhfeo z!t|03(n4ot`JAL~#p@N(S;IdmqPrwRn~~tRh(MUVX40_$-^#^LjUYAb(4I@QCh3I* zrbUJrn!e`+xCVKK(X1>;8U{2D3_IhPI&^#c?+`mz%@YS+`pA?6h>0nd}@<%(V z-AbI|HfS--;haE^RFi&ziJe%G)L`BW?D-Z!Tqr02$J=?Ure9%X(s+3$w328%1hP11 z!AgD04l9ZW?qoNh^)$vTIuH5g$v-GSH3G;QBBQ_7?@&qEA}aqy&w! zXW%UF@pMr@Lby<+bh|*f5A1=MbfNoB7nNd$@2~uu3RmtxS!!qec{;T#2^UW*Su6M38l^e;qGACV>%id@4zqy^V)Vamu_EHS02kx44#xK{}ic`d7ZZU?G1gD=|Q$9$gy z1fK-R;BNrDj`?$>ozZ219s0yx7DoQOe|La{{P}L?&*>uomNGj;uf)&5N#sRhE&?Tz zf(6?~QY48TNr0dN^5>h7Kc8UmM9xcsc{LO%w;K&b&;!Y3k~^`pQsPL^Pe5xGY$j8E zFD@QQ>r{#~A!+{O^abP|9h9lt>wuDf$B2sXP&@p5rR4?8N*~Vi0dqY_fD%e3hMz67 zcZRHDCxJ&uu!a~FI+oL!a_t!s>!x(nla*+;mzKG%yYx#SXv!D|Sik~lfW7d&Y`>Ex z@EQ!{mV#N-XrGcTS>ce=2>*vwSY-5UE}!)ggnOvq=XI zD|dUchDRweLjQRN_h(5w{x34PFLm9(FUa6dHZ5r`i_PFJ-34{M;#R2ZD{vL3uG{PD zueJZv&c1_E57P35ucU5ycN^Wqf&zTr3-g1(zsE}GKfOO#aU?y{w0)fl+t1<3JN057=`*3sen!p zd*t`AfT^cyz!W>7H6lzAXTPUbz!%Pm_5C?wnZm2DMy9L7exO41pTYjq=sTcKOeF<3RSjatfCK;(XCOa| z9+Toz$!K0D5Y_8!_|N=82cnFjRj={umd%mj#`FKw4(tDPfB1?tV>WzmGgmrAhmFOV z?36End9ka;5yM27mk+i%%uMhAl7?U0!Y`cV6PiXsgXGpwEG+^wzmG!G&VQOZ3gbsV zQD0KSGnCzCM?uNZo$VLNokfZ_# zCb37O3s}s?w={@?bF+3oX3%3{^0LJ_oDM*iHP($X#&MRbKULgZnDqnZbD`W)n8-HG9<`ue+ z41se3`M1au%qx(WJhdS63hUI#cViGTuaLznnOC?2s}fa^e{!ah2zN{Y5so!PpboKu z=NUH1$8QWRco|QRb2=S+)hVP0s8fKEF_Qy*sgnpoikv8zsy8~vN?S0fXs^}8^77a& zNk+8A-ILMtqwHC<#mnq@iJ4FHlmTYY z;`+B{&DZpXN@OYlJeW(1WXDhzS??BncG5{N097}D5vOecL#E3@R%DG^rW7Nb$Gg-O zAodEpDq3MUbs9MpCYDg^{301QTjfcN35^<^=`z`^S04Zsm*P*8T6r_5IA$O8vSuyU z{!bhZC0M6wf`+0P<+WIW`7GxQE&z<%)8GPa$AXab?LMdf_7dkZvg&qUq4o;9)Dc2e|Zw z`fvAzrfp9d9V!ZA+H8@2)r?o8L+RnsAzv7PjRY@UkRgD5Ljp98-v*$Hw&7LMHUP8D z=cH~!>N-W>S%D%W8+~5`ma$)_01zzx@@-2S>7;%=+R6I5#F>}{u^Gmms?Grb+kBz1 z+fpz~_cZ`GQ63*MGL-Z+K>8XzcpL-wPkl1473uMRae8h8E}6N5L%30`lIL&xLhICv zxWtrR_^&=>*h9Xcqrs?Re3i+0y zd4UXlSAExM-_SOaP%QcctXeCzkr_S|z$# zF3qTFOxt6EA^a=^h)*ii)$*hTpIKh5>w{7tRiq8sq14~64}Nf_)u5d_EEE2k){ zNzZqdV>%b}om<&NXi=8puVa3}->vdoOAC)2*Q|(unoA#L#zUQ+=}8MH4-xahe1+zi z>9YA*X;Kj02c~l}GFiFO&V$PL_vhfUQ3R?1v@;OZtt6mW zmPvj$Q*sbl(1lX8#B2#q@3t%e8qJkZ+B3ZBL*G%I*$Oh6ZN^dJJC4d93+-4)o0I^Y7- z+1ah?8kUh!H)4{mW!jlDBI811XszA2%$V_NukSj=2YRi7P|w$WS+DfM2dbUqmqr*y zzVL5!_sGbrN2DBt#sm3dz9*|%C^h9+`MR@vCtl!oB5qZ;JHR%zd;n}z9TaW()n4vk z5+txQxe98)ba+)S_bC0eM^!`QX=5)}R#O3uY+{Z^ob^hgyNuH3B=`5=d-j|{8As@1G4ro(W7)X6h-M&!cz_n%eY9A)IL2;M1CyJ7~% z-bor*4LvmjA7C|1)r{KH3J%{tMn#=XWHf$3y&a=YK6C1Si&0amdV3sVE$$V*3*8?O zWhLwRUhXuql9Rlr$Z+pHe~sY|{h{_dzw@}`>k|z3a*vXeya~6esDa$e2mD;S<#h*s zJX&+K;I?N(aCcI(n+(T$>gwPNf|>eq|vK#>#_S#ynba&uL;+XmRH zn}s!@GpUQ)>njvngJM0G;9WxaL)^AZJ;2lnuf^@Mspj3wSiSQCu-S)dO82!5ePQy+ zQ^!tn?{f{oAA2$e1&4^bTKKJpf|pLyD7Ye3Q1B#f)rFk=FX03B09~U8j2;r)KAM}J z9%dRJ(XsJ!BaKfmjUS^MFJ z`0)Vgp@u!AvWGAEAkdm_HJsf;t`4$-6VVSC7>vbL0M0NZbLSZELeft2ji02w9Ptli zrb$e~@K3b+ieMNv!I~?}P*7jHR@#x#Rm7=YU>cTeDk`XZ-_rHrRn=!T3;&3_?#;b0 zWDU*FS|V>27$c=8tF)2w)8Bf@_*;y;;6;|wP0vtgnznQ3d1Z5Wne@}77iOe!E$!-g zvjt{uRzTyz4vY^_!8cZq%A#hSc(Ur>B_;O4VxC_Ia69M|;Qe5vhcN3Z6bKfO77*sB z7dl@%5!&aINW>elOz}TwTujXJ#|}AL-6<^rK14UUh_?@6A8{=kJ&hp&Te1G_q02S? zfgU)f8b|a$qkiY$z8Ef$iauE)xW=D^oN)>+tolb_pPOp)7pA_Jkl;VU4)uNnSAqO> zUxU@5K4`r|$#>%!J;;s33GlXAaAWS-N zMlavtq_LC#D;7J7#b)7wDYlsJ_R>o{s{T_UOr=5vuX#cUrfDaHdnLl1-)bTVl`Hns z86O|11d3H)-PwQ5t=${i0G|O&XJ0%(S76gm`P=`fJS%X{*ud8-o3`FlMa&oNo+^nCSUNmZ5pn?AR8{ zBP}d6El5itQc-5VsS4;6P*-KBEnBw1ZJL&qnJ{fKB}^A4pacd*L>Wj^R0e!pu511+ zQuDu*Q0N+M-M2)fu|BI-hPq$T@#|r)9{eAv_g|FBh#no02CuMbzy&Zuz^$a*sV}aDT0%vzD5I!mv$Rj%zSQ}U1r@wR<#h>E} zY?1AR|O$N6|T2`q=?i;0J&!bxJ$dc|pdf>r`LF@>4Q6#xgulzyU_(Fh!GK z$^+PogSS1WV9|dOXS1*+vDup6j8x7#DGwbkgT@? zy&Z1VpN9MJ0|eL#9LSOY8sv!pZPi2OFm;c|zXU00wT(!L8Dy78@sJo(zH6jpo@S8p z{6ig(^6aM`QhxZAM#?(052SpBTXhX5%3qZST_Z(M;bKOEv1vNf0*sq9VdO*%w0Qri z28^7JV4Ngiq@vV~8Vnrc;Ur&47(VBO_;|3Op~`gnPJz~b{s%QxZoqwbApwA@7xj{M zM675JdxDZ1eY@@O*tc2j-o6|6wg=uesm)`7g%?d?-%Jv>lc(p&4uLJ@od1H4fj{cZ z6Uiaef?(}cwa)>ISUrw=pr(R?4vWJSyx1sQ!3xo{_Ovx_GI^ubCL%`VF4QasIYHG@ z{KH{co;H#8w11B1Ox;kZwMRC42amEb6e(ax1%X*g=K~W@1PXYb@cu>6BbffV_d$t^n(L(8Fqw;SlHc%Ohvza4Bx)r! zOSva#_@QLH{Zk?+(TSNdXoPZ1%#6$6=CsfU>MKl^V?SE>DH5yx zKYySj%pd=QFdrth>`+e)vy;4I%5@3uHI!;;ZZ@alzfg%6qSceYy0P>BECCF2Q3tva z?g4f5UQ?Fy-h*IV@zJ3`JN~#*^9MqxNt~tPA9yKcSafC0Jy}!!fz~VF$;Q+8KQk{MRha0j_axHHh-Tba}(icv2EtUOce5c*jPb;NUyDxx`6I zguC0baCSNl{%E%IHctEAZamu#jluLE3Dvxb$m#7PEeS{f`}H_Od<&-X!&{bSqRsGq zJ5ZB>9Km}SyCHam zCccS*o&LSJb`VfhrrC{H)U>_&TCz99*AH=Uk}xw#(f0Ol33}+WK8M{627YWmqeSrz ze89=g(?235um+571KE^t=~F`RL@CU+hq;J=JtRNyz9xcrReT7x^D`bEpOvY43l;oa z7Q%%kT1GYO?pR@X4>Cd-yDJRg&q9uxx*|xj1sFZ$CZ0r;M5to%)TKPBKP>GiLTUeX5h|G;gO>Ip^!9uEBSIr(ZFsy0 zUGv*m5z2U{3lVDEwZ9_tS?u8|+Pdydgd)loBD5dMcEq$8EVLJ)HSg|CgleyECqk#J z?}9aczf+4)XR202=$Vmd?u~XL6emAZxt=tWwkU(PD1+;oa}e~1mdh6X;3SWHhD(=d zNEjU=hn1kJjh54q6UD$Q{PeT2g_Bu$BnpSut6u?{a9w-(JzvLv5THrW+$|{*dm!f+6hW^+!{yfAnu?ki zNX==mWOYy|*q<}2FP;oqQ@_RvGH67&7(TdQ_Oy&PyX~#wWo{DIjACIMnogU1w9hGM zD-L14{a=>P>DzD5BL3DJB*=(9Z#)LtHhE=n1){pE?qF$|>U5N0Mhz+c)SRv!eYqkP! zpa;Tte6Y=OWO455Wk6F~@xYd1yl;3E?`fC{d;^=g_kYJmTf23U!;G0Pps|)NtDdE{ zp&4{+T+q44Np68sv?30sbcL;#7~cC##I%l;ir)kTC8m0!}_|)pj^p>Kp!X@du2kJ~O``;+MDytygc! zWeYD?$R*ZSHmi(E7Ldy)@w7=jKOB;Omq>n`yj!zIK_nsb1(dg3${UV9h7VPb71|d9 z77n|V>$MlGsNdx8ZtsBKEPmyT?AjSzA$T|n`5K33__`0zEDkhxFAlUq5MKeFWjwVr zD6_ZX7KJiPFqwUTto}Yto?s(UabRbPHLsTJkF`=(a0sp_h= l)%8}i9YAboTAwi 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


    *+TYhk~}^9f4R925Z31pP?l>JPKrW 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