From 68c198e1771fa42a9de42d220ae08b1669db30a4 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Tue, 4 Jun 2024 10:02:01 +0200 Subject: [PATCH 01/26] Fixed lost lang parameter in certain routes --- internal/webserver/controller/auth/login.go | 2 +- internal/webserver/controller/auth/signout.go | 5 ++--- internal/webserver/middleware.go | 3 +-- internal/webserver/routes.go | 1 + 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/internal/webserver/controller/auth/login.go b/internal/webserver/controller/auth/login.go index 1eb1ad66..30a6fac2 100644 --- a/internal/webserver/controller/auth/login.go +++ b/internal/webserver/controller/auth/login.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) func (a *Controller) Login(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/auth/signout.go b/internal/webserver/controller/auth/signout.go index b185e723..4b99281d 100644 --- a/internal/webserver/controller/auth/signout.go +++ b/internal/webserver/controller/auth/signout.go @@ -2,7 +2,6 @@ package auth import ( "fmt" - "time" "github.com/gofiber/fiber/v2" ) @@ -11,9 +10,9 @@ import ( func (a *Controller) SignOut(c *fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "coreander", - Value: "", + Value: "void", Path: "/", - Expires: time.Now().Add(-time.Second * 10), + MaxAge: -1, Secure: false, HTTPOnly: true, }) diff --git a/internal/webserver/middleware.go b/internal/webserver/middleware.go index 9c306716..f234c432 100644 --- a/internal/webserver/middleware.go +++ b/internal/webserver/middleware.go @@ -111,10 +111,9 @@ func forbidden(c *fiber.Ctx, sender Sender, err error) error { emailSendingConfigured = false } message := "" - if err.Error() != "missing or malformed JWT" { + if err.Error() != "missing or malformed JWT" && c.Cookies("coreander") != "void" { message = "Session expired, please log in again." } - return c.Status(fiber.StatusForbidden).Render("auth/login", fiber.Map{ "Lang": chooseBestLanguage(c), "Title": "Login", diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index ebb15354..e9a8f65f 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -72,6 +72,7 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se langGroup.Get("/logout", alwaysRequireAuthentication, controllers.Auth.SignOut) // Authentication requirement is configurable for all routes below this middleware + langGroup.Use(configurableAuthentication) app.Use(configurableAuthentication) app.Get("/cover/:slug", controllers.Documents.Cover) From 6abb29ab8758a469aec1892222997eb0317bb6bb Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Tue, 4 Jun 2024 17:06:01 +0200 Subject: [PATCH 02/26] Removed backward compatibility changes --- README.md | 5 +-- config.go | 2 - filewatcher.go | 2 +- filewatcher_linux.go | 2 +- go.mod | 2 +- internal/index/bleve.go | 2 +- internal/index/bleve_read.go | 4 +- internal/index/bleve_test.go | 8 ++-- internal/index/bleve_write.go | 2 +- internal/index/document.go | 2 +- internal/webserver/authentication_test.go | 6 +-- internal/webserver/controller.go | 14 +++--- .../webserver/controller/auth/controller.go | 2 +- internal/webserver/controller/auth/recover.go | 2 +- internal/webserver/controller/auth/request.go | 2 +- .../controller/auth/reset-password.go | 4 +- internal/webserver/controller/auth/signin.go | 2 +- .../controller/document/controller.go | 6 +-- .../webserver/controller/document/detail.go | 4 +- .../webserver/controller/document/search.go | 10 ++--- .../controller/highlight/controller.go | 6 +-- .../controller/highlight/highlight.go | 2 +- .../controller/highlight/highlights.go | 10 ++--- .../webserver/controller/highlight/remove.go | 2 +- .../webserver/controller/user/controller.go | 4 +- internal/webserver/controller/user/create.go | 2 +- internal/webserver/controller/user/delete.go | 2 +- internal/webserver/controller/user/edit.go | 2 +- internal/webserver/controller/user/list.go | 4 +- internal/webserver/controller/user/new.go | 2 +- internal/webserver/controller/user/update.go | 4 +- internal/webserver/document_detail_test.go | 4 +- internal/webserver/highlights_test.go | 6 +-- internal/webserver/infrastructure/sqlite.go | 44 +------------------ internal/webserver/jwtclaimsreader.go | 2 +- internal/webserver/middleware.go | 4 +- .../webserver/model/highlight_repository.go | 4 +- internal/webserver/model/user_repository.go | 2 +- internal/webserver/remove_document_test.go | 6 +-- internal/webserver/routes.go | 2 +- internal/webserver/search_test.go | 4 +- internal/webserver/send_document_test.go | 4 +- internal/webserver/upload_test.go | 6 +-- internal/webserver/user_management_test.go | 6 +-- internal/webserver/view/paginator.go | 2 +- internal/webserver/webserver.go | 8 ++-- internal/webserver/webserver_test.go | 8 ++-- main.go | 17 +++---- 48 files changed, 101 insertions(+), 151 deletions(-) diff --git a/README.md b/README.md index d9d83f66..312403d9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ A personal documents server, Coreander indexes the documents (EPUBs and PDFs wit * Fast search engine powered by [Bleve](https://github.com/blevesearch/bleve), with support for documents in multiple languages. * Search by author, title and even document series ([Calibre's](https://calibre-ebook.com/) `series` meta supported) * Improved search for documents with metadata in english, spanish, french, italian, german and portuguese, including genre and singular/plural forms of words in the results among others. -* Estimated reading time calculation. +* Estimated reading time calculation. * High-performance web server powered by [Fiber](https://github.com/gofiber/fiber). * Lightweight, responsive web interface based on [Bootstrap](https://getbootstrap.com/). * Web interface available in english, spanish and french, more languages can be easily added. @@ -56,7 +56,7 @@ Coreander requires a `LIB_PATH` environment variable to be set, which tells the On first run, Coreander will index the documents in your library, creating a database with those entries located at `$home/coreander/index`. Depending on your system's performance and the size of your library this may take a while. Also, the database can grow fairly big, so make sure you have enough free space on disk. Every time is run, the application check for new entries, reindexing the whole library. You can -avoid this behaviour by setting the environment variable `SKIP_INDEXING` to `true`. +avoid this behavior by setting the environment variable `SKIP_INDEXING` to `true`. Even if the application is still indexing entries, you can access its web interface right away. Just open a web browser and go to `localhost:3000` (replace `localhost` with the hostname / IP address of the machine where the server is running if you want to access it from another system). It is possible to change the listening port just executing the application with the `PORT` environment variable (e. g. `PORT=4000 coreander`) @@ -109,5 +109,4 @@ On first run, Coreander creates an admin user with the following credentials: * `SESSION_TIMEOUT`: Specifies the maximum time a user session may last, in hours. Floating-point values are allowed. Defaults to 24 hours. * `RECOVERY_TIMEOUT`: Specifies the maximum time a user recovery link may last, in hours. Floating-point values are allowed. Defaults to 2 hours. * `UPLOAD_DOCUMENT_MAX_SIZE`: Maximum document size allowed to be uploaded to the library, in megabytes. Set this to 0 to unlimit upload size. Defaults to 20 megabytes. -* `HOSTNAME`: **Deprecated, use FQDN instead**. * `FQDN`: Domain name of the server. If Coreander is listening to a non-standard HTTP / HTTPS port, include it using a colon (e. g. example.com:3000). Defaults to `localhost`. diff --git a/config.go b/config.go index 4dfcc53b..83ba03f0 100644 --- a/config.go +++ b/config.go @@ -4,8 +4,6 @@ package main type Config struct { // LibPath holds the absolute path to the folder containing the documents LibPath string `env:"LIB_PATH" env-required:"true"` - // Deprecated. Use FQDN instead - Hostname string `env:"HOSTNAME" env-default:"localhost"` // FQDN stores the domain name of the server. If the server is listening on a non-standard HTTP / HTTPS port, include it using a colon (e. g. :3000) FQDN string `env:"FQDN" env-default:"localhost"` // Port defines the port number in which the webserver listens for requests diff --git a/filewatcher.go b/filewatcher.go index ac7fd8cb..4ec690a7 100644 --- a/filewatcher.go +++ b/filewatcher.go @@ -3,7 +3,7 @@ package main import ( - "github.com/svera/coreander/v3/internal/index" + "github.com/svera/coreander/v4/internal/index" ) func fileWatcher(idx *index.BleveIndexer, libPath string) { diff --git a/filewatcher_linux.go b/filewatcher_linux.go index 2c6c531c..9cdb6685 100644 --- a/filewatcher_linux.go +++ b/filewatcher_linux.go @@ -4,7 +4,7 @@ import ( "log" "github.com/rjeczalik/notify" - "github.com/svera/coreander/v3/internal/index" + "github.com/svera/coreander/v4/internal/index" ) func fileWatcher(idx *index.BleveIndexer, libPath string) { diff --git a/go.mod b/go.mod index 9af70501..4cbd687a 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/svera/coreander/v3 +module github.com/svera/coreander/v4 go 1.21 diff --git a/internal/index/bleve.go b/internal/index/bleve.go index 81e4fa3a..2de52b57 100644 --- a/internal/index/bleve.go +++ b/internal/index/bleve.go @@ -19,7 +19,7 @@ import ( "github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode" "github.com/blevesearch/bleve/v2/mapping" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/metadata" + "github.com/svera/coreander/v4/internal/metadata" ) // Version identifies the mapping used for indexing. Any changes in the mapping requires an increase diff --git a/internal/index/bleve_read.go b/internal/index/bleve_read.go index 614dc1c9..01f9bed3 100644 --- a/internal/index/bleve_read.go +++ b/internal/index/bleve_read.go @@ -13,8 +13,8 @@ import ( "github.com/blevesearch/bleve/v2/search/query" "github.com/gosimple/slug" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v4/internal/metadata" + "github.com/svera/coreander/v4/internal/result" ) func (b *BleveIndexer) IndexingProgress() (Progress, error) { diff --git a/internal/index/bleve_test.go b/internal/index/bleve_test.go index f15fb9c4..26b71843 100644 --- a/internal/index/bleve_test.go +++ b/internal/index/bleve_test.go @@ -6,10 +6,10 @@ import ( "github.com/blevesearch/bleve/v2" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/result" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/metadata" + "github.com/svera/coreander/v4/internal/result" + "github.com/svera/coreander/v4/internal/webserver/model" ) func TestIndexAndSearch(t *testing.T) { diff --git a/internal/index/bleve_write.go b/internal/index/bleve_write.go index c8f666a6..e8810faf 100644 --- a/internal/index/bleve_write.go +++ b/internal/index/bleve_write.go @@ -11,7 +11,7 @@ import ( "github.com/gosimple/slug" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/metadata" + "github.com/svera/coreander/v4/internal/metadata" ) // AddFile adds a file to the index diff --git a/internal/index/document.go b/internal/index/document.go index 9d2c5f13..d5e0af66 100644 --- a/internal/index/document.go +++ b/internal/index/document.go @@ -1,6 +1,6 @@ package index -import "github.com/svera/coreander/v3/internal/metadata" +import "github.com/svera/coreander/v4/internal/metadata" type Document struct { metadata.Metadata diff --git a/internal/webserver/authentication_test.go b/internal/webserver/authentication_test.go index fcfc2840..56cf729b 100644 --- a/internal/webserver/authentication_test.go +++ b/internal/webserver/authentication_test.go @@ -12,9 +12,9 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" "gorm.io/gorm" ) diff --git a/internal/webserver/controller.go b/internal/webserver/controller.go index 1f309ef1..0e6a0dd6 100644 --- a/internal/webserver/controller.go +++ b/internal/webserver/controller.go @@ -2,13 +2,13 @@ package webserver import ( "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/webserver/controller/auth" - "github.com/svera/coreander/v3/internal/webserver/controller/document" - "github.com/svera/coreander/v3/internal/webserver/controller/highlight" - "github.com/svera/coreander/v3/internal/webserver/controller/user" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/metadata" + "github.com/svera/coreander/v4/internal/webserver/controller/auth" + "github.com/svera/coreander/v4/internal/webserver/controller/document" + "github.com/svera/coreander/v4/internal/webserver/controller/highlight" + "github.com/svera/coreander/v4/internal/webserver/controller/user" + "github.com/svera/coreander/v4/internal/webserver/model" "gorm.io/gorm" ) diff --git a/internal/webserver/controller/auth/controller.go b/internal/webserver/controller/auth/controller.go index 56a04ec2..0d046034 100644 --- a/internal/webserver/controller/auth/controller.go +++ b/internal/webserver/controller/auth/controller.go @@ -3,7 +3,7 @@ package auth import ( "time" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" "golang.org/x/text/message" ) diff --git a/internal/webserver/controller/auth/recover.go b/internal/webserver/controller/auth/recover.go index 9f17c798..b2c1a126 100644 --- a/internal/webserver/controller/auth/recover.go +++ b/internal/webserver/controller/auth/recover.go @@ -2,7 +2,7 @@ package auth import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) func (a *Controller) Recover(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/auth/request.go b/internal/webserver/controller/auth/request.go index c30d9e1d..589d16cf 100644 --- a/internal/webserver/controller/auth/request.go +++ b/internal/webserver/controller/auth/request.go @@ -8,7 +8,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) func (a *Controller) Request(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/auth/reset-password.go b/internal/webserver/controller/auth/reset-password.go index 2884eeaf..033a73dd 100644 --- a/internal/webserver/controller/auth/reset-password.go +++ b/internal/webserver/controller/auth/reset-password.go @@ -5,8 +5,8 @@ import ( "time" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" ) func (a *Controller) EditPassword(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/auth/signin.go b/internal/webserver/controller/auth/signin.go index d2ee251a..dd8013c2 100644 --- a/internal/webserver/controller/auth/signin.go +++ b/internal/webserver/controller/auth/signin.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v4" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) // Signs in a user and gives them a JWT. diff --git a/internal/webserver/controller/document/controller.go b/internal/webserver/controller/document/controller.go index 44f61be4..ca7b3d6e 100644 --- a/internal/webserver/controller/document/controller.go +++ b/internal/webserver/controller/document/controller.go @@ -2,9 +2,9 @@ package document import ( "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/metadata" + "github.com/svera/coreander/v4/internal/result" ) const relatedDocuments = 4 diff --git a/internal/webserver/controller/document/detail.go b/internal/webserver/controller/document/detail.go index e468912b..4f28019c 100644 --- a/internal/webserver/controller/document/detail.go +++ b/internal/webserver/controller/document/detail.go @@ -7,8 +7,8 @@ import ( "strings" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" ) func (d *Controller) Detail(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/document/search.go b/internal/webserver/controller/document/search.go index 64744f79..dc91b140 100644 --- a/internal/webserver/controller/document/search.go +++ b/internal/webserver/controller/document/search.go @@ -4,11 +4,11 @@ import ( "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/result" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" - "github.com/svera/coreander/v3/internal/webserver/view" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/result" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/view" ) func (d *Controller) Search(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/highlight/controller.go b/internal/webserver/controller/highlight/controller.go index 695954f9..0a75306d 100644 --- a/internal/webserver/controller/highlight/controller.go +++ b/internal/webserver/controller/highlight/controller.go @@ -1,9 +1,9 @@ package highlight import ( - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/result" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/result" + "github.com/svera/coreander/v4/internal/webserver/model" ) type highlightsRepository interface { diff --git a/internal/webserver/controller/highlight/highlight.go b/internal/webserver/controller/highlight/highlight.go index 3f16c85a..fac7e3c0 100644 --- a/internal/webserver/controller/highlight/highlight.go +++ b/internal/webserver/controller/highlight/highlight.go @@ -2,7 +2,7 @@ package highlight import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) func (h *Controller) Highlight(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/highlight/highlights.go b/internal/webserver/controller/highlight/highlights.go index 98fcc534..cb209f7f 100644 --- a/internal/webserver/controller/highlight/highlights.go +++ b/internal/webserver/controller/highlight/highlights.go @@ -5,11 +5,11 @@ import ( "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/result" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" - "github.com/svera/coreander/v3/internal/webserver/view" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/result" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/view" ) func (h *Controller) Highlights(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/highlight/remove.go b/internal/webserver/controller/highlight/remove.go index 37341f54..ee6064b5 100644 --- a/internal/webserver/controller/highlight/remove.go +++ b/internal/webserver/controller/highlight/remove.go @@ -2,7 +2,7 @@ package highlight import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) func (h *Controller) Remove(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/user/controller.go b/internal/webserver/controller/user/controller.go index c64a3372..4167836d 100644 --- a/internal/webserver/controller/user/controller.go +++ b/internal/webserver/controller/user/controller.go @@ -1,8 +1,8 @@ package user import ( - "github.com/svera/coreander/v3/internal/result" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/result" + "github.com/svera/coreander/v4/internal/webserver/model" ) type usersRepository interface { diff --git a/internal/webserver/controller/user/create.go b/internal/webserver/controller/user/create.go index c31eb68f..caf9e4f3 100644 --- a/internal/webserver/controller/user/create.go +++ b/internal/webserver/controller/user/create.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) // Create gathers information coming from the new user form and creates a new user diff --git a/internal/webserver/controller/user/delete.go b/internal/webserver/controller/user/delete.go index fe4f5279..89e9c751 100644 --- a/internal/webserver/controller/user/delete.go +++ b/internal/webserver/controller/user/delete.go @@ -2,7 +2,7 @@ package user import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) // Delete removes a user from the database diff --git a/internal/webserver/controller/user/edit.go b/internal/webserver/controller/user/edit.go index 1770a157..6bbe855c 100644 --- a/internal/webserver/controller/user/edit.go +++ b/internal/webserver/controller/user/edit.go @@ -4,7 +4,7 @@ import ( "log" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) // Edit renders the edit user form diff --git a/internal/webserver/controller/user/list.go b/internal/webserver/controller/user/list.go index 703c4f38..f4f65137 100644 --- a/internal/webserver/controller/user/list.go +++ b/internal/webserver/controller/user/list.go @@ -4,8 +4,8 @@ import ( "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/model" - "github.com/svera/coreander/v3/internal/webserver/view" + "github.com/svera/coreander/v4/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/view" ) // List list all users registered in the database diff --git a/internal/webserver/controller/user/new.go b/internal/webserver/controller/user/new.go index d1940e11..ffcb8f33 100644 --- a/internal/webserver/controller/user/new.go +++ b/internal/webserver/controller/user/new.go @@ -2,7 +2,7 @@ package user import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) // New renders the new user form diff --git a/internal/webserver/controller/user/update.go b/internal/webserver/controller/user/update.go index d048ebd9..f0d8dd60 100644 --- a/internal/webserver/controller/user/update.go +++ b/internal/webserver/controller/user/update.go @@ -7,8 +7,8 @@ import ( "time" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver/controller/auth" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/controller/auth" + "github.com/svera/coreander/v4/internal/webserver/model" ) // Update gathers information from the edit user form and updates user data diff --git a/internal/webserver/document_detail_test.go b/internal/webserver/document_detail_test.go index 02d0734f..41ff93fa 100644 --- a/internal/webserver/document_detail_test.go +++ b/internal/webserver/document_detail_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) func TestDocumentAndRead(t *testing.T) { diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index 0a11d435..10277dd2 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -9,9 +9,9 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" "gorm.io/gorm" ) diff --git a/internal/webserver/infrastructure/sqlite.go b/internal/webserver/infrastructure/sqlite.go index 3e15b81c..c04c483e 100644 --- a/internal/webserver/infrastructure/sqlite.go +++ b/internal/webserver/infrastructure/sqlite.go @@ -3,22 +3,15 @@ package infrastructure import ( "fmt" "log" - "math/rand" "os" "strings" - "time" "github.com/glebarez/sqlite" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" "gorm.io/gorm" ) -var ( - adjectives = []string{"red", "yellow", "white", "blue", "black", "brown", "green", "orange", "purple"} - animals = []string{"panda", "tiger", "lion", "lynx", "bear", "cat", "dog", "koala", "parrot", "dolphin", "shark", "whale", "hawk", "monkey", "vulture", "eagle"} -) - func Connect(path string, wordsPerMinute float64) *gorm.DB { if _, err := os.Stat(path); os.IsNotExist(err) && !strings.Contains(path, "file::memory") { if _, err = os.Create(path); err != nil { @@ -28,10 +21,7 @@ func Connect(path string, wordsPerMinute float64) *gorm.DB { } // Use the following line to connect when the temporary code block below is removed - //db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s?_pragma=foreign_keys(1)", path)), &gorm.Config{}) - db, err := gorm.Open(sqlite.Open(path), &gorm.Config{ - DisableForeignKeyConstraintWhenMigrating: true, - }) + db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s?_pragma=foreign_keys(1)", path)), &gorm.Config{}) if err != nil { log.Fatal(err) } @@ -39,43 +29,13 @@ func Connect(path string, wordsPerMinute float64) *gorm.DB { if err := db.AutoMigrate(&model.User{}, &model.Highlight{}); err != nil { log.Fatal(err) } - // The next block is temporary, used to add constraints to an en existing highlights table - // Remove when the new format is established - if !db.Migrator().HasConstraint(&model.User{}, "Highlights") { - err := db.Migrator().CreateConstraint(&model.User{}, "Highlights") - if err != nil { - log.Fatal(err) - } - err = db.Migrator().CreateConstraint(&model.User{}, "fk_users_highlights") - if err != nil { - log.Fatal(err) - } - } addDefaultAdmin(db, wordsPerMinute) - addUsernames(db) if res := db.Exec("PRAGMA foreign_keys(1)", nil); res.Error != nil { log.Fatal(err) } return db } -// addUsernames is a temporary function to fill the newly created username field -// with a random username -func addUsernames(db *gorm.DB) { - var users []model.User - db.Find(&users, "username = ?", "") - s := rand.NewSource(time.Now().Unix()) - r := rand.New(s) - for _, user := range users { - if user.ID == 1 { - user.Username = "admin" - } else { - user.Username = adjectives[r.Intn(len(adjectives))] + animals[r.Intn(len(animals))] + fmt.Sprintf("%d", rand.Intn(1000)) - } - db.Save(&user) - } -} - func addDefaultAdmin(db *gorm.DB, wordsPerMinute float64) { var result int64 db.Table("users").Count(&result) diff --git a/internal/webserver/jwtclaimsreader.go b/internal/webserver/jwtclaimsreader.go index ad3a1f9e..f523d48d 100644 --- a/internal/webserver/jwtclaimsreader.go +++ b/internal/webserver/jwtclaimsreader.go @@ -3,7 +3,7 @@ package webserver import ( "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v4" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/model" ) func sessionData(c *fiber.Ctx) model.Session { diff --git a/internal/webserver/middleware.go b/internal/webserver/middleware.go index f234c432..9c6b4250 100644 --- a/internal/webserver/middleware.go +++ b/internal/webserver/middleware.go @@ -6,8 +6,8 @@ import ( "github.com/gofiber/fiber/v2" jwtware "github.com/gofiber/jwt/v3" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" ) // RequireAdmin returns HTTP forbidden if the user requesting access diff --git a/internal/webserver/model/highlight_repository.go b/internal/webserver/model/highlight_repository.go index 00730ffa..1ad09734 100644 --- a/internal/webserver/model/highlight_repository.go +++ b/internal/webserver/model/highlight_repository.go @@ -3,8 +3,8 @@ package model import ( "log" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/result" "golang.org/x/exp/slices" "gorm.io/gorm" "gorm.io/gorm/clause" diff --git a/internal/webserver/model/user_repository.go b/internal/webserver/model/user_repository.go index ffc3b63c..6bd01439 100644 --- a/internal/webserver/model/user_repository.go +++ b/internal/webserver/model/user_repository.go @@ -6,7 +6,7 @@ import ( "fmt" "log" - "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v4/internal/result" "gorm.io/gorm" ) diff --git a/internal/webserver/remove_document_test.go b/internal/webserver/remove_document_test.go index 36f220fb..1831e4f3 100644 --- a/internal/webserver/remove_document_test.go +++ b/internal/webserver/remove_document_test.go @@ -9,9 +9,9 @@ import ( "testing" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" ) func TestRemoveDocument(t *testing.T) { diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index e9a8f65f..c2880204 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/filesystem" - "github.com/svera/coreander/v3/internal/webserver/controller" + "github.com/svera/coreander/v4/internal/webserver/controller" ) func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Sender, requireAuth bool) { diff --git a/internal/webserver/search_test.go b/internal/webserver/search_test.go index bfea3a00..b8bc94e0 100644 --- a/internal/webserver/search_test.go +++ b/internal/webserver/search_test.go @@ -6,8 +6,8 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) func TestSearch(t *testing.T) { diff --git a/internal/webserver/send_document_test.go b/internal/webserver/send_document_test.go index 9b28ac02..a726945b 100644 --- a/internal/webserver/send_document_test.go +++ b/internal/webserver/send_document_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) func TestSendDocument(t *testing.T) { diff --git a/internal/webserver/upload_test.go b/internal/webserver/upload_test.go index 11b71325..ba043ca0 100644 --- a/internal/webserver/upload_test.go +++ b/internal/webserver/upload_test.go @@ -13,9 +13,9 @@ import ( "github.com/gofiber/fiber/v2" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" ) func TestUpload(t *testing.T) { diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index 598337a4..0b8ea9d1 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -11,9 +11,9 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" "gorm.io/gorm" ) diff --git a/internal/webserver/view/paginator.go b/internal/webserver/view/paginator.go index 321480b5..1a52989f 100644 --- a/internal/webserver/view/paginator.go +++ b/internal/webserver/view/paginator.go @@ -5,7 +5,7 @@ import ( "net/url" "strings" - "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v4/internal/result" ) // Page holds the URL of a results page, and if that page is the current one being shown diff --git a/internal/webserver/webserver.go b/internal/webserver/webserver.go index 5a1433f7..4df657bf 100644 --- a/internal/webserver/webserver.go +++ b/internal/webserver/webserver.go @@ -13,10 +13,10 @@ import ( "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cache" "github.com/gofiber/fiber/v2/middleware/favicon" - "github.com/svera/coreander/v3/internal/i18n" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" - "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v4/internal/i18n" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/webserver/model" "golang.org/x/exp/slices" "golang.org/x/text/message" ) diff --git a/internal/webserver/webserver_test.go b/internal/webserver/webserver_test.go index 13ab8155..f4da6212 100644 --- a/internal/webserver/webserver_test.go +++ b/internal/webserver/webserver_test.go @@ -16,10 +16,10 @@ import ( "github.com/blevesearch/bleve/v2" "github.com/gofiber/fiber/v2" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/metadata" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" "gorm.io/gorm" ) diff --git a/main.go b/main.go index 80f5026c..597a0d63 100644 --- a/main.go +++ b/main.go @@ -13,10 +13,10 @@ import ( "gorm.io/gorm" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/webserver" - "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v4/internal/index" + "github.com/svera/coreander/v4/internal/metadata" + "github.com/svera/coreander/v4/internal/webserver" + "github.com/svera/coreander/v4/internal/webserver/infrastructure" ) var version string = "unknown" @@ -101,7 +101,6 @@ func main() { MinPasswordLength: cfg.MinPasswordLength, WordsPerMinute: cfg.WordsPerMinute, JwtSecret: cfg.JwtSecret, - Hostname: cfg.Hostname, FQDN: cfg.FQDN, Port: cfg.Port, HomeDir: homeDir, @@ -111,12 +110,6 @@ func main() { UploadDocumentMaxSize: cfg.UploadDocumentMaxSize, } - // Hack for keeping backward compatibility, remove when complete - if webserverConfig.FQDN == "localhost" && webserverConfig.Hostname != "localhost" { - fmt.Println("Warning: using deprecated environment variable 'HOSTNAME`, use 'FQDN' instead.") - webserverConfig.FQDN = webserverConfig.Hostname - } - webserverConfig.SessionTimeout, err = time.ParseDuration(fmt.Sprintf("%fh", cfg.SessionTimeout)) if err != nil { log.Fatal(fmt.Errorf("wrong value for session timeout")) @@ -130,7 +123,7 @@ func main() { controllers := webserver.SetupControllers(webserverConfig, db, metadataReaders, idx, sender, appFs) app := webserver.New(webserverConfig, controllers, sender, idx) if strings.ToLower(cfg.FQDN) == "localhost" { - fmt.Printf("Warning: using \"localhost\" as FQDN. Links using this FQDN won't be accesible outside this system.\n") + fmt.Printf("Warning: using \"localhost\" as FQDN. Links using this FQDN won't be accessible outside this system.\n") } fmt.Printf("Coreander version %s started listening on port %d\n\n", version, cfg.Port) log.Fatal(app.Listen(fmt.Sprintf(":%d", cfg.Port))) From d77e75c40b5d980161018dc0ff83baf25e3a0496 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Wed, 5 Jun 2024 10:06:03 +0200 Subject: [PATCH 03/26] FIxed db issues with foreign keys --- internal/webserver/infrastructure/sqlite.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/internal/webserver/infrastructure/sqlite.go b/internal/webserver/infrastructure/sqlite.go index c04c483e..630952ff 100644 --- a/internal/webserver/infrastructure/sqlite.go +++ b/internal/webserver/infrastructure/sqlite.go @@ -1,7 +1,6 @@ package infrastructure import ( - "fmt" "log" "os" "strings" @@ -21,18 +20,20 @@ func Connect(path string, wordsPerMinute float64) *gorm.DB { } // Use the following line to connect when the temporary code block below is removed - db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s?_pragma=foreign_keys(1)", path)), &gorm.Config{}) + //db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s?_pragma=foreign_keys(1)", path)), &gorm.Config{}) + db, err := gorm.Open(sqlite.Open(path), &gorm.Config{}) if err != nil { log.Fatal(err) } - if err := db.AutoMigrate(&model.User{}, &model.Highlight{}); err != nil { + if res := db.Exec("PRAGMA foreign_keys(1)", nil); res.Error != nil { log.Fatal(err) } - addDefaultAdmin(db, wordsPerMinute) - if res := db.Exec("PRAGMA foreign_keys(1)", nil); res.Error != nil { + + if err := db.AutoMigrate(&model.User{}, &model.Highlight{}); err != nil { log.Fatal(err) } + addDefaultAdmin(db, wordsPerMinute) return db } From 2db40fbaa68a3f8d57133c37aeadb98788961965 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Wed, 5 Jun 2024 10:30:19 +0200 Subject: [PATCH 04/26] Fixed connection string --- internal/webserver/authentication_test.go | 6 +++--- internal/webserver/document_detail_test.go | 2 +- internal/webserver/highlights_test.go | 2 +- internal/webserver/infrastructure/sqlite.go | 11 +++-------- internal/webserver/remove_document_test.go | 2 +- internal/webserver/search_test.go | 2 +- internal/webserver/send_document_test.go | 2 +- internal/webserver/upload_test.go | 2 +- internal/webserver/user_management_test.go | 2 +- internal/webserver/webserver_test.go | 2 +- 10 files changed, 14 insertions(+), 19 deletions(-) diff --git a/internal/webserver/authentication_test.go b/internal/webserver/authentication_test.go index 56cf729b..86f5ea98 100644 --- a/internal/webserver/authentication_test.go +++ b/internal/webserver/authentication_test.go @@ -19,7 +19,7 @@ import ( ) func TestAuthentication(t *testing.T) { - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) app := bootstrapApp(db, &infrastructure.SMTP{}, afero.NewMemMapFs(), webserver.Config{}) data := url.Values{ @@ -82,7 +82,7 @@ func TestAuthentication(t *testing.T) { } func TestRecoverNoEmailService(t *testing.T) { - db := infrastructure.Connect("file::memory:?cache=shared", 250) + db := infrastructure.Connect(":memory:?cache=shared", 250) app := bootstrapApp(db, &infrastructure.NoEmail{}, afero.NewMemMapFs(), webserver.Config{}) req, err := http.NewRequest(http.MethodGet, "/en/recover", nil) @@ -115,7 +115,7 @@ func TestRecover(t *testing.T) { LibraryPath: "fixtures/library", UploadDocumentMaxSize: 1, } - db = infrastructure.Connect("file::memory:?cache=shared", 250) + db = infrastructure.Connect(":memory:?cache=shared", 250) smtpMock = &infrastructure.SMTPMock{} app = bootstrapApp(db, smtpMock, afero.NewMemMapFs(), webserverConfig) diff --git a/internal/webserver/document_detail_test.go b/internal/webserver/document_detail_test.go index 41ff93fa..880950d3 100644 --- a/internal/webserver/document_detail_test.go +++ b/internal/webserver/document_detail_test.go @@ -10,7 +10,7 @@ import ( ) func TestDocumentAndRead(t *testing.T) { - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) smtpMock := &infrastructure.SMTPMock{} app := bootstrapApp(db, smtpMock, afero.NewOsFs(), webserver.Config{}) diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index 10277dd2..61629ad1 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -27,7 +27,7 @@ func TestHighlights(t *testing.T) { reset := func() { var err error - db = infrastructure.Connect("file::memory:", 250) + db = infrastructure.Connect(":memory:", 250) appFS := loadFilesInMemoryFs([]string{"fixtures/library/metadata.epub"}) app = bootstrapApp(db, &infrastructure.NoEmail{}, appFS, webserver.Config{}) adminCookie, err = login(app, "admin@example.com", "admin", t) diff --git a/internal/webserver/infrastructure/sqlite.go b/internal/webserver/infrastructure/sqlite.go index 630952ff..2d17ba5e 100644 --- a/internal/webserver/infrastructure/sqlite.go +++ b/internal/webserver/infrastructure/sqlite.go @@ -1,6 +1,7 @@ package infrastructure import ( + "fmt" "log" "os" "strings" @@ -12,24 +13,18 @@ import ( ) func Connect(path string, wordsPerMinute float64) *gorm.DB { - if _, err := os.Stat(path); os.IsNotExist(err) && !strings.Contains(path, "file::memory") { + if _, err := os.Stat(path); os.IsNotExist(err) && !strings.Contains(path, ":memory:") { if _, err = os.Create(path); err != nil { log.Fatal(err) } log.Printf("Created database at %s\n", path) } - // Use the following line to connect when the temporary code block below is removed - //db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s?_pragma=foreign_keys(1)", path)), &gorm.Config{}) - db, err := gorm.Open(sqlite.Open(path), &gorm.Config{}) + db, err := gorm.Open(sqlite.Open(fmt.Sprintf("%s?_pragma=foreign_keys(1)", path)), &gorm.Config{}) if err != nil { log.Fatal(err) } - if res := db.Exec("PRAGMA foreign_keys(1)", nil); res.Error != nil { - log.Fatal(err) - } - if err := db.AutoMigrate(&model.User{}, &model.Highlight{}); err != nil { log.Fatal(err) } diff --git a/internal/webserver/remove_document_test.go b/internal/webserver/remove_document_test.go index 1831e4f3..492cf932 100644 --- a/internal/webserver/remove_document_test.go +++ b/internal/webserver/remove_document_test.go @@ -15,7 +15,7 @@ import ( ) func TestRemoveDocument(t *testing.T) { - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) smtpMock := &infrastructure.SMTPMock{} appFS := loadDirInMemoryFs("fixtures/library") app := bootstrapApp(db, smtpMock, appFS, webserver.Config{}) diff --git a/internal/webserver/search_test.go b/internal/webserver/search_test.go index b8bc94e0..68b117fa 100644 --- a/internal/webserver/search_test.go +++ b/internal/webserver/search_test.go @@ -11,7 +11,7 @@ import ( ) func TestSearch(t *testing.T) { - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) smtpMock := &infrastructure.SMTPMock{} appFS := loadDirInMemoryFs("fixtures/library") diff --git a/internal/webserver/send_document_test.go b/internal/webserver/send_document_test.go index a726945b..e222aae6 100644 --- a/internal/webserver/send_document_test.go +++ b/internal/webserver/send_document_test.go @@ -12,7 +12,7 @@ import ( ) func TestSendDocument(t *testing.T) { - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) smtpMock := &infrastructure.SMTPMock{} app := bootstrapApp(db, smtpMock, afero.NewOsFs(), webserver.Config{}) diff --git a/internal/webserver/upload_test.go b/internal/webserver/upload_test.go index ba043ca0..a2cb089d 100644 --- a/internal/webserver/upload_test.go +++ b/internal/webserver/upload_test.go @@ -19,7 +19,7 @@ import ( ) func TestUpload(t *testing.T) { - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) appFS := loadDirInMemoryFs("fixtures/library") app := bootstrapApp(db, &infrastructure.NoEmail{}, appFS, webserver.Config{}) diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index 0b8ea9d1..d301d2a2 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -32,7 +32,7 @@ func TestUserManagement(t *testing.T) { t.Helper() var err error - db = infrastructure.Connect("file::memory:", 250) + db = infrastructure.Connect(":memory:", 250) app = bootstrapApp(db, &infrastructure.NoEmail{}, afero.NewMemMapFs(), webserver.Config{}) adminUser = model.User{} diff --git a/internal/webserver/webserver_test.go b/internal/webserver/webserver_test.go index f4da6212..8505df48 100644 --- a/internal/webserver/webserver_test.go +++ b/internal/webserver/webserver_test.go @@ -35,7 +35,7 @@ func TestGET(t *testing.T) { {"Server returns not found if the user tries to access a non-existent URL", "/xx", http.StatusNotFound}, } - db := infrastructure.Connect("file::memory:", 250) + db := infrastructure.Connect(":memory:", 250) app := bootstrapApp(db, &infrastructure.NoEmail{}, afero.NewMemMapFs(), webserver.Config{}) for _, tcase := range cases { From fbf6019ee35e091337c850004c3d5ae9b30fcb07 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Wed, 5 Jun 2024 13:21:35 +0200 Subject: [PATCH 05/26] Small tweaks --- internal/webserver/model/user.go | 2 +- main.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/webserver/model/user.go b/internal/webserver/model/user.go index df28cbc5..7692c4dc 100644 --- a/internal/webserver/model/user.go +++ b/internal/webserver/model/user.go @@ -20,7 +20,7 @@ type User struct { UpdatedAt time.Time Uuid string `gorm:"uniqueIndex; not null"` Name string `gorm:"not null"` - Username string `gorm:"type:text collate nocase; not null; default:''; unique"` + Username string `gorm:"type:text collate nocase; not null; unique"` Email string `gorm:"uniqueIndex; not null"` SendToEmail string Password string diff --git a/main.go b/main.go index 597a0d63..8894ab77 100644 --- a/main.go +++ b/main.go @@ -36,6 +36,7 @@ var ( ) func init() { + fmt.Printf("Coreander version %s\n\n", version) homeDir, err = os.UserHomeDir() if err != nil { log.Fatal("Error retrieving user home dir") @@ -125,7 +126,7 @@ func main() { if strings.ToLower(cfg.FQDN) == "localhost" { fmt.Printf("Warning: using \"localhost\" as FQDN. Links using this FQDN won't be accessible outside this system.\n") } - fmt.Printf("Coreander version %s started listening on port %d\n\n", version, cfg.Port) + fmt.Printf("Started listening on port %d\n\n", cfg.Port) log.Fatal(app.Listen(fmt.Sprintf(":%d", cfg.Port))) } From 387a0717366d54acf61d8bbd06a465d970476397 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Thu, 13 Jun 2024 14:18:41 +0200 Subject: [PATCH 06/26] replaced document path by documents --- internal/webserver/document_detail_test.go | 2 +- .../webserver/embedded/views/document.html | 8 ++++---- internal/webserver/embedded/views/index.html | 6 +++--- .../embedded/views/partials/docs-list.html | 18 +++++++++--------- .../embedded/views/partials/related.html | 2 +- .../embedded/views/partials/searchbox.html | 2 +- internal/webserver/embedded/views/reader.html | 2 +- internal/webserver/highlights_test.go | 2 +- internal/webserver/remove_document_test.go | 2 +- internal/webserver/routes.go | 5 +++-- internal/webserver/search_test.go | 4 ++-- main.go | 4 ++-- 12 files changed, 29 insertions(+), 28 deletions(-) diff --git a/internal/webserver/document_detail_test.go b/internal/webserver/document_detail_test.go index 880950d3..a8bf7ce9 100644 --- a/internal/webserver/document_detail_test.go +++ b/internal/webserver/document_detail_test.go @@ -20,7 +20,7 @@ func TestDocumentAndRead(t *testing.T) { }{ {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha-2", http.StatusOK}, - {"/en/document/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, + {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, } for _, tcase := range cases { diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index b1df3649..b503f6d6 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -123,7 +123,7 @@

{{t $lang "Unknown author"}}

{{range $i, $subject := .Document.Subjects}} {{$subjectTitle := t $lang "Search for more titles in %s" $subject}} - {{$subject}} {{end}}
@@ -143,7 +143,7 @@

{{t $lang "Unknown author"}}

{{t $lang "Other documents in collection \"%s\"" .Document.Series}}

@@ -166,7 +166,7 @@

@@ -186,7 +186,7 @@

{{t $lang "Other documents with similar subjects"}}

diff --git a/internal/webserver/embedded/views/index.html b/internal/webserver/embedded/views/index.html index c7127675..8e062223 100644 --- a/internal/webserver/embedded/views/index.html +++ b/internal/webserver/embedded/views/index.html @@ -13,7 +13,7 @@

{{t .Lang "Your highlights" }}

{{t .Lang "See all" }} - +
{{$lang := .Lang}} {{$emailSendingConfigured := .EmailSendingConfigured}} @@ -31,12 +31,12 @@

{{t .Lang "Your highlights" }}

{{ template "partials/actions" dict "Lang" $lang "Document" $doc "EmailSendingConfigured" $emailSendingConfigured "Session" $session "EmailFrom" $emailFrom "OnDehighlight" "remove"}} -
+ {{end}} - {{template "partials/delete-modal" dict "Lang" .Lang "Action" "/document" "ModalHeader" "Delete document" "ModalBody" "Are you sure you want to delete this document?" "ModalErrorMessage" "There was an error deleting the document"}} + {{template "partials/delete-modal" dict "Lang" .Lang "Action" "/documents" "ModalHeader" "Delete document" "ModalBody" "Are you sure you want to delete this document?" "ModalErrorMessage" "There was an error deleting the document"}} diff --git a/internal/webserver/embedded/views/partials/docs-list.html b/internal/webserver/embedded/views/partials/docs-list.html index f0b0ac79..e7368cf7 100644 --- a/internal/webserver/embedded/views/partials/docs-list.html +++ b/internal/webserver/embedded/views/partials/docs-list.html @@ -27,11 +27,11 @@
{{$document.Title}}
{{ if ne $document.Series "" }} {{$seriesTitle := t $lang "Search for more titles belonging to %s" $document.Series}}

{{$document.Series}}{{ if ne $document.SeriesIndex 0.0 }} {{$document.SeriesIndex}}{{end}}

{{ end }}

- {{$document.Title}} {{$document.Year}} + {{$document.Title}} {{$document.Year}}

@@ -51,27 +51,27 @@

{{range $i, $author := $document.Authors}} {{$authorTitle := t $lang "Search for more titles by %s" $author}} - {{$author}}{{if notLast $document.Authors $i}}, {{end}} + {{$author}}{{if notLast $document.Authors $i}}, {{end}} {{end}}
{{else}}
{{t $lang "Unknown author"}}
{{end}} - + {{ if gt $document.Words 0.0 }}

{{t $lang "Estimated reading time"}}: {{$document.ReadingTime $wordsPerMinute}}

{{ end }} - + {{ if $document.Pages }}

{{t $lang "%d pages" $document.Pages}}

{{ end }} - + {{ if $document.Subjects }}
{{range $i, $subject := $document.Subjects}} {{$subjectTitle := t $lang "Search for more titles in %s" $subject}} - {{$subject}} + {{$subject}} {{end}}
{{ end }} @@ -81,7 +81,7 @@
{{t $lang "Unknown author"}}
{{else}}
{{t $lang "No description available"}}
{{end}} - +
@@ -91,7 +91,7 @@
{{t $lang "Unknown author"}}
{{end}} {{end}} -{{template "partials/delete-modal" dict "Lang" $lang "Action" "/document" "ModalHeader" "Delete document" "ModalBody" "Are you sure you want to delete this document?" "ModalErrorMessage" "There was an error deleting the document"}} +{{template "partials/delete-modal" dict "Lang" $lang "Action" "/documents" "ModalHeader" "Delete document" "ModalBody" "Are you sure you want to delete this document?" "ModalErrorMessage" "There was an error deleting the document"}} diff --git a/internal/webserver/embedded/views/partials/related.html b/internal/webserver/embedded/views/partials/related.html index 7f05c3be..ce8d5d63 100644 --- a/internal/webserver/embedded/views/partials/related.html +++ b/internal/webserver/embedded/views/partials/related.html @@ -1,7 +1,7 @@
{{t .Lang "\"%s\" cover" .Document.Title}}
-
{{.Document.Title}}
+
{{.Document.Title}}
{{if .Document.Authors}}

{{join .Document.Authors ", "}} diff --git a/internal/webserver/embedded/views/partials/searchbox.html b/internal/webserver/embedded/views/partials/searchbox.html index db5a5ff0..f992407c 100644 --- a/internal/webserver/embedded/views/partials/searchbox.html +++ b/internal/webserver/embedded/views/partials/searchbox.html @@ -1,6 +1,6 @@

-
+
diff --git a/internal/webserver/embedded/views/reader.html b/internal/webserver/embedded/views/reader.html index 4d1f8b4a..23ff37bd 100644 --- a/internal/webserver/embedded/views/reader.html +++ b/internal/webserver/embedded/views/reader.html @@ -26,7 +26,7 @@ diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index 61629ad1..c95412ee 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -121,7 +121,7 @@ func TestHighlights(t *testing.T) { "id": {"john-doe-test-epub"}, } - _, err = deleteRequest(data, adminCookie, app, "/document", t) + _, err = deleteRequest(data, adminCookie, app, "/documents", t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/remove_document_test.go b/internal/webserver/remove_document_test.go index 492cf932..42ca6db3 100644 --- a/internal/webserver/remove_document_test.go +++ b/internal/webserver/remove_document_test.go @@ -65,7 +65,7 @@ func TestRemoveDocument(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - req, err := http.NewRequest(http.MethodDelete, "/document", strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodDelete, "/documents", strings.NewReader(data.Encode())) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index c2880204..539194f7 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -64,7 +64,7 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se app.Post("/highlights", alwaysRequireAuthentication, controllers.Highlights.Highlight) app.Delete("/highlights", alwaysRequireAuthentication, controllers.Highlights.Remove) - app.Delete("/document", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) + app.Delete("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) langGroup.Get("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.UploadForm) langGroup.Post("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) @@ -77,12 +77,13 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se app.Get("/cover/:slug", controllers.Documents.Cover) - langGroup.Get("/document/:slug", controllers.Documents.Detail) + langGroup.Get("/documents/:slug", controllers.Documents.Detail) app.Post("/send", controllers.Documents.Send) app.Get("/download/:slug", controllers.Documents.Download) + langGroup.Get("/documents", controllers.Documents.Search) langGroup.Get("/", controllers.Documents.Search) langGroup.Get("/read/:slug", controllers.Documents.Reader) diff --git a/internal/webserver/search_test.go b/internal/webserver/search_test.go index 68b117fa..4990cd90 100644 --- a/internal/webserver/search_test.go +++ b/internal/webserver/search_test.go @@ -22,7 +22,7 @@ func TestSearch(t *testing.T) { url string expectedResults int }{ - {"Search for documents with no metadata", "/en?search=empty", 2}, + {"Search for documents with no metadata", "/en/documents?search=empty", 2}, {"Search for documents with metadata", "/en?search=john+doe", 4}, } @@ -55,7 +55,7 @@ func TestSearch(t *testing.T) { func assertSearchResults(app *fiber.App, t *testing.T, search string, expectedResults int) { t.Helper() - req, err := http.NewRequest(http.MethodGet, "/en?search="+search, nil) + req, err := http.NewRequest(http.MethodGet, "/en/documents?search="+search, nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/main.go b/main.go index 8894ab77..c1dade71 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ var ( ) func init() { - fmt.Printf("Coreander version %s\n\n", version) + log.Printf("Coreander version %s starting\n", version) homeDir, err = os.UserHomeDir() if err != nil { log.Fatal("Error retrieving user home dir") @@ -126,7 +126,7 @@ func main() { if strings.ToLower(cfg.FQDN) == "localhost" { fmt.Printf("Warning: using \"localhost\" as FQDN. Links using this FQDN won't be accessible outside this system.\n") } - fmt.Printf("Started listening on port %d\n\n", cfg.Port) + log.Printf("Started listening on port %d\n", cfg.Port) log.Fatal(app.Listen(fmt.Sprintf(":%d", cfg.Port))) } From 3b74376d73d37e9b1a0d8fb45b7ebc91f34f554b Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Mon, 17 Jun 2024 12:45:20 +0200 Subject: [PATCH 07/26] Moved upload post route under documents --- internal/webserver/embedded/views/upload.html | 4 ++-- internal/webserver/routes.go | 2 +- internal/webserver/upload_test.go | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/webserver/embedded/views/upload.html b/internal/webserver/embedded/views/upload.html index 6ab3ea2b..afbd68c5 100644 --- a/internal/webserver/embedded/views/upload.html +++ b/internal/webserver/embedded/views/upload.html @@ -4,7 +4,7 @@

{{t .Lang "Upload document" }}

- +
-
+
diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index 539194f7..4308cfc8 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -67,7 +67,7 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se app.Delete("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) langGroup.Get("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.UploadForm) - langGroup.Post("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) + langGroup.Post("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) langGroup.Get("/logout", alwaysRequireAuthentication, controllers.Auth.SignOut) diff --git a/internal/webserver/upload_test.go b/internal/webserver/upload_test.go index a2cb089d..adea8e65 100644 --- a/internal/webserver/upload_test.go +++ b/internal/webserver/upload_test.go @@ -71,7 +71,7 @@ func TestUpload(t *testing.T) { multipartWriter := multipart.NewWriter(&buf) multipartWriter.Close() - req, err := http.NewRequest(http.MethodPost, "/en/upload", &buf) + req, err := http.NewRequest(http.MethodPost, "/en/documents", &buf) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -105,7 +105,7 @@ func TestUpload(t *testing.T) { filePart.Write([]byte("Hello, World!")) multipartWriter.Close() - req, err := http.NewRequest(http.MethodPost, "/en/upload", &buf) + req, err := http.NewRequest(http.MethodPost, "/en/documents", &buf) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -133,7 +133,7 @@ func TestUpload(t *testing.T) { part.Write([]byte(`sample`)) multipartWriter.Close() - req, err := http.NewRequest(http.MethodPost, "/en/upload", &buf) + req, err := http.NewRequest(http.MethodPost, "/en/documents", &buf) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -155,7 +155,7 @@ func TestUpload(t *testing.T) { multipartWriter := multipart.NewWriter(&buf) multipartWriter.Close() - req, err := http.NewRequest(http.MethodPost, "/en/upload", &buf) + req, err := http.NewRequest(http.MethodPost, "/en/documents", &buf) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -189,7 +189,7 @@ func TestUpload(t *testing.T) { multipartWriter.Close() - req, err := http.NewRequest(http.MethodPost, "/en/upload", &buf) + req, err := http.NewRequest(http.MethodPost, "/en/documents", &buf) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -232,7 +232,7 @@ func TestUpload(t *testing.T) { multipartWriter.Close() - req, err := http.NewRequest(http.MethodPost, "/en/upload", &buf) + req, err := http.NewRequest(http.MethodPost, "/en/documents", &buf) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } From fd607ddc20ebef96e5a7c28ddbc92399714f693d Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Wed, 19 Jun 2024 15:05:41 +0200 Subject: [PATCH 08/26] Fixed suffix number processing in slug for documents with same author and title --- internal/index/bleve_write.go | 11 ++++++++++- internal/webserver/document_detail_test.go | 3 ++- .../fixtures/library/quijote_third_edition.epub | Bin 0 -> 1804 bytes 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 internal/webserver/fixtures/library/quijote_third_edition.epub diff --git a/internal/index/bleve_write.go b/internal/index/bleve_write.go index e8810faf..5100eeaa 100644 --- a/internal/index/bleve_write.go +++ b/internal/index/bleve_write.go @@ -5,6 +5,7 @@ import ( "log" "os" "path/filepath" + "regexp" "slices" "strings" "time" @@ -134,6 +135,10 @@ func (b *BleveIndexer) createDocument(meta metadata.Metadata, fullPath string, b // processed in the current batch in memory to also compare the current doc slug against them. func (b *BleveIndexer) Slug(document DocumentWrite, batchSlugs map[string]struct{}) string { docSlug := makeSlug(document) + exp, err := regexp.Compile(`^[a-zA-Z0-9\-]+(--)\d$`) + if err != nil { + log.Fatal(err) + } i := 1 existsInBatch := false for { @@ -147,8 +152,12 @@ func (b *BleveIndexer) Slug(document DocumentWrite, batchSlugs map[string]struct if doc.Slug == "" && !existsInBatch { return docSlug } + if exp.MatchString(docSlug) { + pos := strings.LastIndex(docSlug, "--") + docSlug = docSlug[:pos] + } i++ - docSlug = fmt.Sprintf("%s-%d", docSlug, i) + docSlug = fmt.Sprintf("%s--%d", docSlug, i) } } diff --git a/internal/webserver/document_detail_test.go b/internal/webserver/document_detail_test.go index a8bf7ce9..0ac4a08e 100644 --- a/internal/webserver/document_detail_test.go +++ b/internal/webserver/document_detail_test.go @@ -19,7 +19,8 @@ func TestDocumentAndRead(t *testing.T) { expectedStatus int }{ {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, - {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha-2", http.StatusOK}, + {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--2", http.StatusOK}, + {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--3", http.StatusOK}, {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, } diff --git a/internal/webserver/fixtures/library/quijote_third_edition.epub b/internal/webserver/fixtures/library/quijote_third_edition.epub new file mode 100644 index 0000000000000000000000000000000000000000..915238c4d46178aabc073dd12e6c35a33170d083 GIT binary patch literal 1804 zcmWIWW@Zs#0D*|8xtY1CC6xuKi3J5YnaPPInfZD8sRgA;+Etka z0brd>92{Vs>K~TR+5*(U%*enX2BdvmLmYKI{oM4E^Ycm)GxJi5^eS?5V*3te9X1ef zdH;`V#CjF@m75BNl_pDKV+LwZE$C^izY$rD! zf5*Ud^5ct4vk%{@^5!SJkbXO(>ubwt`~0Pv(h=f8Ih)US_C?&*J}oj%;bWpcZY%6XSRMx%kiC>l^wmvnV}!ksJMvllR7yrt)o9 zA}4*cecH8i_1upOzct-CwDnKuvZr=S^Y<~J`tzVg$Ko46KdxtHU=RfQ)8Ex8AQ#WL@JFrpz0ZUHSo0%tXU$5YB z$iMO4L+~PEEH3JH&!aRm-;Z@%`9$ON9Ax{+h-yFzayY|Hgt`oavy^nYL0XKq9?(9En2W8v-kybl=t z48Wuy15XAasTC#q!Kulhd}3f=V5nD-QIeaJ+jo|)$v~jxeQnpRX7k-g_{Az$R&j>UyvkEHU73-@6L8&hz3jZ{ z<{f1hwlF$vnRfr&)IcAhru#X+``7KUPkXp4FMXrIvXhaXx0}?Azq=@vOjao2I%pC8 zUO(`}zP{B-O#xe~%D2z)og|Oy@8>)TdO1KpFJfX~;D!6UBtKa%FS(*O=wRL<1D?H~ zMI+y>y}i-NQFAhz%mj%LCO+%7W6fE)OxqRg8DBnbN+n2LV2Fr>5spZ=NZin(Yd$hYKf3?FrEJ8Tp>N7YR@Ytv`Zs)$?57v6WC;;1oq?Rux@ ztbZGgIeyK%%sX%QUOQj}a#}PWpT)($Wq)Hh%vDoMxuX4Qe~9oanl^_ur*wYgkPp-4E!yWK>p7Z#V1kUDdf%>(TZj?tf8>J}2g#{%mu9?YAlEXC-*o zzO3nu&i~r!ZXe*y2#f;UB{wK-A^@x$2kYV%VZc>tgLD967YM9?7<(CxZU%aZ2+|EP zLkUP>l#j^TvE?L$c1>Ur3(<}}U!j|Xo;DFCDYC#!f~M2}Z&o&tG%FC=0bRL+6~qGo D5L?sw literal 0 HcmV?d00001 From 9bc40dbe84ced93472d345c498dac2f3dc82c51f Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Wed, 19 Jun 2024 15:13:59 +0200 Subject: [PATCH 09/26] Put read route under documents --- internal/webserver/document_detail_test.go | 6 +++--- internal/webserver/embedded/views/document.html | 2 +- internal/webserver/embedded/views/partials/actions.html | 2 +- internal/webserver/routes.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/webserver/document_detail_test.go b/internal/webserver/document_detail_test.go index 0ac4a08e..ee291b18 100644 --- a/internal/webserver/document_detail_test.go +++ b/internal/webserver/document_detail_test.go @@ -18,9 +18,9 @@ func TestDocumentAndRead(t *testing.T) { url string expectedStatus int }{ - {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, - {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--2", http.StatusOK}, - {"/en/read/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--3", http.StatusOK}, + {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha/read", http.StatusOK}, + {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--2/read", http.StatusOK}, + {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--3/read", http.StatusOK}, {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, } diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index b503f6d6..6cb6f66e 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -23,7 +23,7 @@

- + diff --git a/internal/webserver/embedded/views/partials/actions.html b/internal/webserver/embedded/views/partials/actions.html index 0ee847e5..0d89b500 100644 --- a/internal/webserver/embedded/views/partials/actions.html +++ b/internal/webserver/embedded/views/partials/actions.html @@ -1,5 +1,5 @@
+
+ + + + + + + +
+
-
+ {{if eq .Session.Uuid .User.Uuid}}
@@ -73,7 +73,7 @@
{{t .Lang .Errors.oldpassword}}
- {{end}} + {{end}}
{{end}}
@@ -84,7 +84,7 @@
{{t .Lang .Errors.password .MinPasswordLength}}
- {{end}} + {{end}}
@@ -94,12 +94,13 @@
{{t .Lang .Errors.confirmpassword}}
- {{end}} + {{end}}
-
+ + diff --git a/internal/webserver/embedded/views/users/index.html b/internal/webserver/embedded/views/users/index.html index 72df9af3..4079b9b5 100644 --- a/internal/webserver/embedded/views/users/index.html +++ b/internal/webserver/embedded/views/users/index.html @@ -18,7 +18,7 @@

{{t $lang "Users"}}

{{range $i, $user := .Users}}
- {{$user.Name}} + {{$user.Name}} ({{$user.Email}}) {{if eq $user.Role 2}} diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index 75dbdfe6..fbc80709 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -50,7 +50,7 @@ func TestHighlights(t *testing.T) { "words-per-minute": {"250"}, } - response, err := postRequest(regularUserData, adminCookie, app, "/en/users/new", t) + response, err := postRequest(regularUserData, adminCookie, app, "/en/users", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index b6c75431..49b47336 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -50,14 +50,15 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se langGroup.Post("/recover", allowIfNotLoggedIn, controllers.Auth.Request) langGroup.Get("/reset-password", allowIfNotLoggedIn, controllers.Auth.EditPassword) langGroup.Post("/reset-password", allowIfNotLoggedIn, controllers.Auth.UpdatePassword) + langGroup.Get("/logout", alwaysRequireAuthentication, controllers.Auth.SignOut) usersGroup := langGroup.Group("/users", alwaysRequireAuthentication) usersGroup.Get("/", alwaysRequireAuthentication, RequireAdmin, controllers.Users.List) usersGroup.Get("/new", alwaysRequireAuthentication, RequireAdmin, controllers.Users.New) - usersGroup.Post("/new", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Create) - usersGroup.Get("/:username/edit", alwaysRequireAuthentication, controllers.Users.Edit) - usersGroup.Post("/:username/edit", alwaysRequireAuthentication, controllers.Users.Update) + usersGroup.Post("/", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Create) + usersGroup.Get("/:username", alwaysRequireAuthentication, controllers.Users.Edit) + usersGroup.Put("/:username", alwaysRequireAuthentication, controllers.Users.Update) app.Delete("/users", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Delete) langGroup.Get("/highlights/:username", alwaysRequireAuthentication, controllers.Highlights.Highlights) @@ -69,8 +70,6 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se langGroup.Get("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.UploadForm) langGroup.Post("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) - langGroup.Get("/logout", alwaysRequireAuthentication, controllers.Auth.SignOut) - // Authentication requirement is configurable for all routes below this middleware langGroup.Use(configurableAuthentication) app.Use(configurableAuthentication) diff --git a/internal/webserver/upload_test.go b/internal/webserver/upload_test.go index adea8e65..c9918550 100644 --- a/internal/webserver/upload_test.go +++ b/internal/webserver/upload_test.go @@ -38,7 +38,7 @@ func TestUpload(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - response, err := postRequest(data, adminCookie, app, "/en/users/new", t) + response, err := postRequest(data, adminCookie, app, "/en/users", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index d301d2a2..e415850a 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -53,7 +53,7 @@ func TestUserManagement(t *testing.T) { "words-per-minute": {"250"}, } - response, err := postRequest(regularUserData, adminCookie, app, "/en/users/new", t) + response, err := postRequest(regularUserData, adminCookie, app, "/en/users", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -87,7 +87,7 @@ func TestUserManagement(t *testing.T) { "words-per-minute": {"250"}, } - response, err = postRequest(newUserData, &http.Cookie{}, app, "/en/users/new", t) + response, err = postRequest(newUserData, &http.Cookie{}, app, "/en/users", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -116,7 +116,7 @@ func TestUserManagement(t *testing.T) { "words-per-minute": {"250"}, } - response, err = postRequest(newUserData, adminCookie, app, "/en/users/new", t) + response, err = postRequest(newUserData, adminCookie, app, "/en/users", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -140,7 +140,7 @@ func TestUserManagement(t *testing.T) { mustReturnStatus(response, fiber.StatusForbidden, t) - response, err = postRequest(url.Values{}, regularUserCookie, app, "/en/users/new", t) + response, err = postRequest(url.Values{}, regularUserCookie, app, "/en/users", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -151,7 +151,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to add a user with errors in form", func(t *testing.T) { reset() - response, err := postRequest(url.Values{}, adminCookie, app, "/en/users/new", t) + response, err := postRequest(url.Values{}, adminCookie, app, "/en/users", t) expectedErrorMessages := []string{ "Name cannot be empty", "Username cannot be empty", @@ -180,7 +180,7 @@ func TestUserManagement(t *testing.T) { "words-per-minute": {"250"}, } - response, err := postRequest(newUserData, adminCookie, app, "/en/users/new", t) + response, err := postRequest(newUserData, adminCookie, app, "/en/users", t) expectedErrorMessages := []string{ "A user with this username already exists", "A user with this email address already exists", @@ -195,14 +195,14 @@ func TestUserManagement(t *testing.T) { t.Run("Try to update a user without an active session", func(t *testing.T) { reset() - response, err := getRequest(&http.Cookie{}, app, fmt.Sprintf("/en/users/%s/edit", regularUser.Username), t) + response, err := getRequest(&http.Cookie{}, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } mustReturnForbiddenAndShowLogin(response, t) - response, err = postRequest(regularUserData, &http.Cookie{}, app, fmt.Sprintf("/en/users/%s/edit", regularUser.Username), t) + response, err = putRequest(regularUserData, &http.Cookie{}, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -216,14 +216,14 @@ func TestUserManagement(t *testing.T) { adminUserData := regularUserData adminUserData.Set("id", adminUser.Uuid) - response, err := getRequest(regularUserCookie, app, fmt.Sprintf("/en/users/%s/edit", adminUser.Username), t) + response, err := getRequest(regularUserCookie, app, fmt.Sprintf("/en/users/%s", adminUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } mustReturnStatus(response, fiber.StatusForbidden, t) - response, err = postRequest(adminUserData, regularUserCookie, app, fmt.Sprintf("/en/users/%s/edit", adminUser.Username), t) + response, err = putRequest(adminUserData, regularUserCookie, app, fmt.Sprintf("/en/users/%s", adminUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -237,14 +237,14 @@ func TestUserManagement(t *testing.T) { regularUserData.Set("name", "Updated regular user") regularUserData.Set("id", regularUser.Uuid) - response, err := getRequest(regularUserCookie, app, fmt.Sprintf("/en/users/%s/edit", regularUser.Username), t) + response, err := getRequest(regularUserCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } mustReturnStatus(response, fiber.StatusOK, t) - response, err = postRequest(regularUserData, regularUserCookie, app, fmt.Sprintf("/en/users/%s/edit", regularUser.Username), t) + response, err = putRequest(regularUserData, regularUserCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -261,14 +261,14 @@ func TestUserManagement(t *testing.T) { regularUserData.Set("name", "Updated regular user by an admin") regularUserData.Set("id", regularUser.Uuid) - response, err := postRequest(regularUserData, adminCookie, app, fmt.Sprintf("/en/users/%s/edit", regularUser.Username), t) + response, err := putRequest(regularUserData, adminCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } mustReturnStatus(response, fiber.StatusOK, t) - response, err = postRequest(regularUserData, adminCookie, app, fmt.Sprintf("/en/users/%s/edit", regularUser.Username), t) + response, err = putRequest(regularUserData, adminCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -282,7 +282,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to edit a non existing user with an admin session", func(t *testing.T) { reset() - response, err := getRequest(adminCookie, app, fmt.Sprintf("/en/users/%s/edit", "abcde"), t) + response, err := getRequest(adminCookie, app, fmt.Sprintf("/en/users/%s", "abcde"), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -294,7 +294,7 @@ func TestUserManagement(t *testing.T) { regularUserData.Set("name", "Updated test user by an admin") - response, err := postRequest(regularUserData, adminCookie, app, fmt.Sprintf("/en/users/%s/edit", "abcde"), t) + response, err := putRequest(regularUserData, adminCookie, app, fmt.Sprintf("/en/users/%s", "abcde"), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/webserver_test.go b/internal/webserver/webserver_test.go index 8505df48..91c441ea 100644 --- a/internal/webserver/webserver_test.go +++ b/internal/webserver/webserver_test.go @@ -124,6 +124,12 @@ func postRequest(data url.Values, cookie *http.Cookie, app *fiber.App, URL strin return formRequest(http.MethodPost, data, cookie, app, URL) } +func putRequest(data url.Values, cookie *http.Cookie, app *fiber.App, URL string, t *testing.T) (*http.Response, error) { + t.Helper() + + return formRequest(http.MethodPut, data, cookie, app, URL) +} + func deleteRequest(data url.Values, cookie *http.Cookie, app *fiber.App, URL string, t *testing.T) (*http.Response, error) { t.Helper() From bef65e9debbc6664ace0ba40c929bfcb5a4a64d5 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Mon, 15 Jul 2024 12:18:24 +0200 Subject: [PATCH 15/26] Fixed create user route in form --- internal/webserver/embedded/views/users/new.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/webserver/embedded/views/users/new.html b/internal/webserver/embedded/views/users/new.html index 63dc68e1..07a6277a 100644 --- a/internal/webserver/embedded/views/users/new.html +++ b/internal/webserver/embedded/views/users/new.html @@ -1,4 +1,4 @@ -
+
@@ -25,7 +25,7 @@ {{t .Lang .Errors.email}}
{{end}} -
+
@@ -52,7 +52,7 @@
{{t .Lang .Errors.password .MinPasswordLength}}
- {{end}} + {{end}}
@@ -62,7 +62,7 @@
{{t .Lang .Errors.confirmpassword}}
- {{end}} + {{end}}
@@ -74,7 +74,7 @@
{{t .Lang .Errors.role}}
- {{end}} + {{end}}
From dd1d7d47c1cf97efde38a331f0871dee3c694e1e Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Sun, 21 Jul 2024 21:44:04 +0200 Subject: [PATCH 16/26] Use HTTP verbs for authentication routes --- internal/webserver/authentication_test.go | 8 ++++---- internal/webserver/controller/auth/login.go | 1 + internal/webserver/controller/auth/reset-password.go | 2 +- internal/webserver/controller/auth/signout.go | 6 ++---- internal/webserver/embedded/views/auth/login.html | 2 +- internal/webserver/embedded/views/layout.html | 4 ++-- internal/webserver/routes.go | 6 +++--- internal/webserver/user_management_test.go | 2 +- 8 files changed, 15 insertions(+), 16 deletions(-) diff --git a/internal/webserver/authentication_test.go b/internal/webserver/authentication_test.go index 86f5ea98..f2b86eb5 100644 --- a/internal/webserver/authentication_test.go +++ b/internal/webserver/authentication_test.go @@ -29,7 +29,7 @@ func TestAuthentication(t *testing.T) { t.Run("Try to log in with good and bad credentials", func(t *testing.T) { // Check that login page is accessible - req, err := http.NewRequest(http.MethodGet, "/en/login", nil) + req, err := http.NewRequest(http.MethodGet, "/en/sessions/new", nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -42,7 +42,7 @@ func TestAuthentication(t *testing.T) { } // Use no credentials to log in - req, err = http.NewRequest(http.MethodPost, "/en/login", nil) + req, err = http.NewRequest(http.MethodPost, "/en/sessions", nil) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -56,7 +56,7 @@ func TestAuthentication(t *testing.T) { } // Use good credentials to log in - req, err = http.NewRequest(http.MethodPost, "/en/login", strings.NewReader(data.Encode())) + req, err = http.NewRequest(http.MethodPost, "/en/sessions", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -267,7 +267,7 @@ func TestRecover(t *testing.T) { t.Error("No location header present") return } - if expectedURL := "/en/login"; url.Path != expectedURL { + if expectedURL := "/en/sessions"; url.Path != expectedURL { t.Errorf("Expected location %s, received %s", expectedURL, url.Path) } diff --git a/internal/webserver/controller/auth/login.go b/internal/webserver/controller/auth/login.go index 30a6fac2..71dbe2c3 100644 --- a/internal/webserver/controller/auth/login.go +++ b/internal/webserver/controller/auth/login.go @@ -29,5 +29,6 @@ func (a *Controller) Login(c *fiber.Ctx) error { "Title": "Login", "Message": msg, "EmailSendingConfigured": emailSendingConfigured, + "DisableLoginLink": true, }, "layout") } diff --git a/internal/webserver/controller/auth/reset-password.go b/internal/webserver/controller/auth/reset-password.go index 033a73dd..12d90812 100644 --- a/internal/webserver/controller/auth/reset-password.go +++ b/internal/webserver/controller/auth/reset-password.go @@ -45,7 +45,7 @@ func (a *Controller) UpdatePassword(c *fiber.Ctx) error { return fiber.ErrInternalServerError } - return c.Redirect(fmt.Sprintf("/%s/login", c.Params("lang"))) + return c.Redirect(fmt.Sprintf("/%s/sessions", c.Params("lang"))) } func (a *Controller) validateRecoveryAccess(recoveryUuid string) (*model.User, error) { diff --git a/internal/webserver/controller/auth/signout.go b/internal/webserver/controller/auth/signout.go index 4b99281d..acdf654d 100644 --- a/internal/webserver/controller/auth/signout.go +++ b/internal/webserver/controller/auth/signout.go @@ -1,8 +1,6 @@ package auth import ( - "fmt" - "github.com/gofiber/fiber/v2" ) @@ -16,6 +14,6 @@ func (a *Controller) SignOut(c *fiber.Ctx) error { Secure: false, HTTPOnly: true, }) - - return c.Redirect(fmt.Sprintf("/%s", c.Params("lang"))) + c.Set("HX-Refresh", "true") + return c.SendStatus(fiber.StatusNoContent) } diff --git a/internal/webserver/embedded/views/auth/login.html b/internal/webserver/embedded/views/auth/login.html index c8987834..f45765b4 100644 --- a/internal/webserver/embedded/views/auth/login.html +++ b/internal/webserver/embedded/views/auth/login.html @@ -1,4 +1,4 @@ - +

{{t .Lang "Please sign in"}}

diff --git a/internal/webserver/embedded/views/layout.html b/internal/webserver/embedded/views/layout.html index c684dc83..007bd0eb 100644 --- a/internal/webserver/embedded/views/layout.html +++ b/internal/webserver/embedded/views/layout.html @@ -94,7 +94,7 @@
Coreander
  • - + Coreander
  • {{else if not .DisableLoginLink}}
  • - {{t .Lang "Login"}} + {{t .Lang "Login"}}
  • {{end}}
    diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index 49b47336..b328cd4f 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -44,13 +44,13 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se return c.Next() }) - langGroup.Get("/login", allowIfNotLoggedIn, controllers.Auth.Login) - langGroup.Post("login", allowIfNotLoggedIn, controllers.Auth.SignIn) + langGroup.Get("/sessions/new", allowIfNotLoggedIn, controllers.Auth.Login) + langGroup.Post("/sessions", allowIfNotLoggedIn, controllers.Auth.SignIn) langGroup.Get("/recover", allowIfNotLoggedIn, controllers.Auth.Recover) langGroup.Post("/recover", allowIfNotLoggedIn, controllers.Auth.Request) langGroup.Get("/reset-password", allowIfNotLoggedIn, controllers.Auth.EditPassword) langGroup.Post("/reset-password", allowIfNotLoggedIn, controllers.Auth.UpdatePassword) - langGroup.Get("/logout", alwaysRequireAuthentication, controllers.Auth.SignOut) + langGroup.Delete("/sessions", alwaysRequireAuthentication, controllers.Auth.SignOut) usersGroup := langGroup.Group("/users", alwaysRequireAuthentication) diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index e415850a..0394a3a2 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -428,7 +428,7 @@ func login(app *fiber.App, email, password string, t *testing.T) (*http.Cookie, "password": {password}, } - req, err := http.NewRequest(http.MethodPost, "/en/login", strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodPost, "/en/sessions", strings.NewReader(data.Encode())) if err != nil { return nil, err } From b3529c61bb205d71eebcee343324e6d9410c79be Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Thu, 25 Jul 2024 10:17:14 +0200 Subject: [PATCH 17/26] Use HTTP delete verb for deleting docs and users --- .../webserver/controller/document/delete.go | 8 +---- .../highlight/{highlight.go => create.go} | 2 +- .../highlight/{remove.go => delete.go} | 2 +- .../highlight/{highlights.go => list.go} | 2 +- internal/webserver/controller/user/delete.go | 4 +-- internal/webserver/embedded/js/delete.js | 16 +++------- .../embedded/views/partials/delete-modal.html | 1 - .../webserver/embedded/views/users/index.html | 2 +- internal/webserver/highlights_test.go | 16 ++-------- internal/webserver/remove_document_test.go | 15 ++------- internal/webserver/routes.go | 10 +++--- internal/webserver/user_management_test.go | 31 +++---------------- 12 files changed, 26 insertions(+), 83 deletions(-) rename internal/webserver/controller/highlight/{highlight.go => create.go} (86%) rename internal/webserver/controller/highlight/{remove.go => delete.go} (86%) rename internal/webserver/controller/highlight/{highlights.go => list.go} (97%) diff --git a/internal/webserver/controller/document/delete.go b/internal/webserver/controller/document/delete.go index f5f88760..eee18d7d 100644 --- a/internal/webserver/controller/document/delete.go +++ b/internal/webserver/controller/document/delete.go @@ -1,7 +1,6 @@ package document import ( - "fmt" "log" "path/filepath" @@ -9,13 +8,8 @@ import ( ) func (d *Controller) Delete(c *fiber.Ctx) error { - if c.FormValue("id") == "" { - return fiber.ErrBadRequest - } - - document, err := d.idx.Document(c.FormValue("id")) + document, err := d.idx.Document(c.Params("slug")) if err != nil { - fmt.Println(err) return fiber.ErrBadRequest } diff --git a/internal/webserver/controller/highlight/highlight.go b/internal/webserver/controller/highlight/create.go similarity index 86% rename from internal/webserver/controller/highlight/highlight.go rename to internal/webserver/controller/highlight/create.go index 829dd58e..f201d4ad 100644 --- a/internal/webserver/controller/highlight/highlight.go +++ b/internal/webserver/controller/highlight/create.go @@ -5,7 +5,7 @@ import ( "github.com/svera/coreander/v4/internal/webserver/model" ) -func (h *Controller) Highlight(c *fiber.Ctx) error { +func (h *Controller) Create(c *fiber.Ctx) error { user := c.Locals("Session").(model.Session) document, err := h.idx.Document(c.Params("slug")) diff --git a/internal/webserver/controller/highlight/remove.go b/internal/webserver/controller/highlight/delete.go similarity index 86% rename from internal/webserver/controller/highlight/remove.go rename to internal/webserver/controller/highlight/delete.go index 7813525b..3c4f419a 100644 --- a/internal/webserver/controller/highlight/remove.go +++ b/internal/webserver/controller/highlight/delete.go @@ -5,7 +5,7 @@ import ( "github.com/svera/coreander/v4/internal/webserver/model" ) -func (h *Controller) Remove(c *fiber.Ctx) error { +func (h *Controller) Delete(c *fiber.Ctx) error { user := c.Locals("Session").(model.Session) document, err := h.idx.Document(c.Params("slug")) diff --git a/internal/webserver/controller/highlight/highlights.go b/internal/webserver/controller/highlight/list.go similarity index 97% rename from internal/webserver/controller/highlight/highlights.go rename to internal/webserver/controller/highlight/list.go index cb209f7f..10fcd73f 100644 --- a/internal/webserver/controller/highlight/highlights.go +++ b/internal/webserver/controller/highlight/list.go @@ -12,7 +12,7 @@ import ( "github.com/svera/coreander/v4/internal/webserver/view" ) -func (h *Controller) Highlights(c *fiber.Ctx) error { +func (h *Controller) List(c *fiber.Ctx) error { emailSendingConfigured := true if _, ok := h.sender.(*infrastructure.NoEmail); ok { emailSendingConfigured = false diff --git a/internal/webserver/controller/user/delete.go b/internal/webserver/controller/user/delete.go index 89e9c751..6ac9d520 100644 --- a/internal/webserver/controller/user/delete.go +++ b/internal/webserver/controller/user/delete.go @@ -7,7 +7,7 @@ import ( // Delete removes a user from the database func (u *Controller) Delete(c *fiber.Ctx) error { - user, err := u.repository.FindByUuid(c.FormValue("id")) + user, err := u.repository.FindByUsername(c.Params("username")) if err != nil { return fiber.ErrInternalServerError } @@ -20,7 +20,7 @@ func (u *Controller) Delete(c *fiber.Ctx) error { return fiber.ErrForbidden } - if err = u.repository.Delete(c.FormValue("id")); err != nil { + if err = u.repository.Delete(user.Uuid); err != nil { return fiber.ErrInternalServerError } diff --git a/internal/webserver/embedded/js/delete.js b/internal/webserver/embedded/js/delete.js index 788eb769..78a94bad 100644 --- a/internal/webserver/embedded/js/delete.js +++ b/internal/webserver/embedded/js/delete.js @@ -9,13 +9,11 @@ const deleteModal = document.getElementById('delete-modal'); const deleteForm = document.getElementById('delete-form'); +let id deleteModal.addEventListener('show.bs.modal', event => { const link = event.relatedTarget - const id = link.getAttribute('data-id') - const modalInput = deleteModal.querySelector('.id') - - modalInput.value = id; + id = link.getAttribute('data-id') }) deleteModal.addEventListener('hidden.bs.modal', event => { @@ -25,14 +23,8 @@ deleteModal.addEventListener('hidden.bs.modal', event => { deleteForm.addEventListener('submit', event => { event.preventDefault(); - fetch(deleteForm.getAttribute("action"), { - method: "DELETE", - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: new URLSearchParams({ - 'id': deleteForm.elements['id'].value, - }) + fetch(deleteForm.getAttribute("action") + '/' + id, { + method: "DELETE" }) .then((response) => { if (response.ok || response.status == "403") { diff --git a/internal/webserver/embedded/views/partials/delete-modal.html b/internal/webserver/embedded/views/partials/delete-modal.html index cb4f2f93..ebed992b 100644 --- a/internal/webserver/embedded/views/partials/delete-modal.html +++ b/internal/webserver/embedded/views/partials/delete-modal.html @@ -17,7 +17,6 @@

    {{t .Lang .ModalHeader}}

    - diff --git a/internal/webserver/embedded/views/users/index.html b/internal/webserver/embedded/views/users/index.html index 4079b9b5..c463bbeb 100644 --- a/internal/webserver/embedded/views/users/index.html +++ b/internal/webserver/embedded/views/users/index.html @@ -26,7 +26,7 @@

    {{t $lang "Users"}}

    {{end}} {{ if not (and (eq $admins 1) (eq $user.Role 2)) }} - + diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index fbc80709..f8f55d70 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -19,7 +19,6 @@ func TestHighlights(t *testing.T) { db *gorm.DB app *fiber.App adminCookie *http.Cookie - data url.Values adminUser model.User ) @@ -34,9 +33,6 @@ func TestHighlights(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - data = url.Values{ - "slug": {"john-doe-test-epub"}, - } adminUser = model.User{} db.Where("email = ?", "admin@example.com").First(&adminUser) @@ -116,11 +112,7 @@ func TestHighlights(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - data = url.Values{ - "id": {"john-doe-test-epub"}, - } - - _, err = deleteRequest(data, adminCookie, app, "/documents", t) + _, err = deleteRequest(url.Values{}, adminCookie, app, "/documents/john-doe-test-epub", t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -158,11 +150,7 @@ func TestHighlights(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - data := url.Values{ - "id": {regularUser.Uuid}, - } - - _, err = deleteRequest(data, adminCookie, app, "/users", t) + _, err = deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/users/%s", regularUser.Username), t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/remove_document_test.go b/internal/webserver/remove_document_test.go index 42ca6db3..7e29b8e8 100644 --- a/internal/webserver/remove_document_test.go +++ b/internal/webserver/remove_document_test.go @@ -1,11 +1,11 @@ package webserver_test import ( + "fmt" "log" "net/http" "net/url" "os" - "strings" "testing" "github.com/google/uuid" @@ -43,8 +43,7 @@ func TestRemoveDocument(t *testing.T) { slug string expectedHTTPStatus int }{ - {"Remove no document slug", "admin@example.com", "admin", "", "", http.StatusBadRequest}, - {"Remove non existing document slug", "admin@example.com", "admin", "wrong.epub", "", http.StatusBadRequest}, + {"Remove non existing document slug", "admin@example.com", "admin", "wrong.epub", "wrong-epub", http.StatusBadRequest}, {"Remove document with a regular user", "regular@example.com", "regular", "metadata.epub", "john-doe-test-epub", http.StatusForbidden}, {"Remove document with an admin user", "admin@example.com", "admin", "metadata.epub", "john-doe-test-epub", http.StatusOK}, } @@ -56,23 +55,15 @@ func TestRemoveDocument(t *testing.T) { err error ) - data := url.Values{ - "id": {tcase.slug}, - } - cookie, err := login(app, tcase.email, tcase.password, t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } - req, err := http.NewRequest(http.MethodDelete, "/documents", strings.NewReader(data.Encode())) + response, err = deleteRequest(url.Values{}, cookie, app, fmt.Sprintf("/documents/%s", tcase.slug), t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - req.AddCookie(cookie) - - response, err = app.Test(req) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index b328cd4f..44c4026e 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -59,13 +59,13 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se usersGroup.Post("/", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Create) usersGroup.Get("/:username", alwaysRequireAuthentication, controllers.Users.Edit) usersGroup.Put("/:username", alwaysRequireAuthentication, controllers.Users.Update) - app.Delete("/users", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Delete) + app.Delete("/users/:username", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Delete) - langGroup.Get("/highlights/:username", alwaysRequireAuthentication, controllers.Highlights.Highlights) - app.Post("/documents/:slug/highlight", alwaysRequireAuthentication, controllers.Highlights.Highlight) - app.Delete("/documents/:slug/highlight", alwaysRequireAuthentication, controllers.Highlights.Remove) + langGroup.Get("/highlights/:username", alwaysRequireAuthentication, controllers.Highlights.List) + app.Post("/documents/:slug/highlight", alwaysRequireAuthentication, controllers.Highlights.Create) + app.Delete("/documents/:slug/highlight", alwaysRequireAuthentication, controllers.Highlights.Delete) - app.Delete("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) + app.Delete("/documents/:slug", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) langGroup.Get("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.UploadForm) langGroup.Post("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index 0394a3a2..d8144057 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -304,11 +304,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a user without an active session", func(t *testing.T) { reset() - regularUserData = url.Values{ - "id": {regularUser.Uuid}, - } - - response, err := deleteRequest(regularUserData, &http.Cookie{}, app, "/users", t) + response, err := deleteRequest(url.Values{}, &http.Cookie{}, app, fmt.Sprintf("/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -319,13 +315,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a user with a regular user's session", func(t *testing.T) { reset() - regularUserData = url.Values{ - "id": {regularUser.Uuid}, - } - - regularUserData.Set("name", "Updated test user") - - response, err := deleteRequest(regularUserData, regularUserCookie, app, "/users", t) + response, err := deleteRequest(url.Values{}, regularUserCookie, app, fmt.Sprintf("/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -336,11 +326,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a user with an admin session", func(t *testing.T) { reset() - regularUserData = url.Values{ - "id": {regularUser.Uuid}, - } - - response, err := deleteRequest(regularUserData, adminCookie, app, "/users", t) + response, err := deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -355,10 +341,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete the only existing admin user", func(t *testing.T) { reset() - regularUserData = url.Values{ - "id": {adminUser.Uuid}, - } - response, err := deleteRequest(regularUserData, adminCookie, app, "/users", t) + response, err := deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/users/%s", adminUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -369,11 +352,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a non existing user with an admin session", func(t *testing.T) { reset() - regularUserData = url.Values{ - "id": {"abcde"}, - } - - response, err := deleteRequest(regularUserData, adminCookie, app, "/users", t) + response, err := deleteRequest(url.Values{}, adminCookie, app, "/users/wrong", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } From ee52204a046b6623c0932fbffef394f8a7891d19 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Sun, 28 Jul 2024 12:25:47 +0200 Subject: [PATCH 18/26] REST highlights --- internal/webserver/controller/auth/signin.go | 2 +- internal/webserver/controller/auth/signout.go | 2 +- .../webserver/controller/highlight/list.go | 2 +- .../webserver/embedded/views/document.html | 4 ++-- internal/webserver/embedded/views/index.html | 2 +- internal/webserver/embedded/views/layout.html | 2 +- .../embedded/views/partials/actions.html | 4 ++-- internal/webserver/highlights_test.go | 21 +++++++++---------- internal/webserver/middleware.go | 2 +- internal/webserver/routes.go | 6 +++--- 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/internal/webserver/controller/auth/signin.go b/internal/webserver/controller/auth/signin.go index 6d4a6205..9be3d3df 100644 --- a/internal/webserver/controller/auth/signin.go +++ b/internal/webserver/controller/auth/signin.go @@ -41,7 +41,7 @@ func (a *Controller) SignIn(c *fiber.Ctx) error { Name: "coreander", Value: signedToken, Path: "/", - MaxAge: int(a.config.SessionTimeout.Seconds()), + MaxAge: 34560000, // 400 days which is the life limit imposed by Chrome Secure: false, HTTPOnly: true, }) diff --git a/internal/webserver/controller/auth/signout.go b/internal/webserver/controller/auth/signout.go index acdf654d..7daec00c 100644 --- a/internal/webserver/controller/auth/signout.go +++ b/internal/webserver/controller/auth/signout.go @@ -8,7 +8,7 @@ import ( func (a *Controller) SignOut(c *fiber.Ctx) error { c.Cookie(&fiber.Cookie{ Name: "coreander", - Value: "void", + Value: "", Path: "/", MaxAge: -1, Secure: false, diff --git a/internal/webserver/controller/highlight/list.go b/internal/webserver/controller/highlight/list.go index 10fcd73f..d3a8d162 100644 --- a/internal/webserver/controller/highlight/list.go +++ b/internal/webserver/controller/highlight/list.go @@ -32,7 +32,7 @@ func (h *Controller) List(c *fiber.Ctx) error { h.wordsPerMinute = session.WordsPerMinute } - user, err := h.usrRepository.FindByUsername(c.Params("username")) + user, err := h.usrRepository.FindByUsername(session.Username) if err != nil { log.Println(err.Error()) return fiber.ErrInternalServerError diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index 1ce56b84..2f17f35f 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -41,14 +41,14 @@

    {{if and (.Session) (ne .Session.Name "")}} - +   {{t .Lang "Highlight"}} - + diff --git a/internal/webserver/embedded/views/index.html b/internal/webserver/embedded/views/index.html index 8e062223..a427de5a 100644 --- a/internal/webserver/embedded/views/index.html +++ b/internal/webserver/embedded/views/index.html @@ -9,7 +9,7 @@

    {{t .Lang "Your highlights" }}

    {{if gt (len .Highlights) 0}}
    diff --git a/internal/webserver/embedded/views/layout.html b/internal/webserver/embedded/views/layout.html index 007bd0eb..6d3831ef 100644 --- a/internal/webserver/embedded/views/layout.html +++ b/internal/webserver/embedded/views/layout.html @@ -73,7 +73,7 @@

    Coreander
    {{end}}
  • - + diff --git a/internal/webserver/embedded/views/partials/actions.html b/internal/webserver/embedded/views/partials/actions.html index e1d10551..c5f24fe5 100644 --- a/internal/webserver/embedded/views/partials/actions.html +++ b/internal/webserver/embedded/views/partials/actions.html @@ -18,7 +18,7 @@
  • {{if and (.Session) (ne .Session.Name "")}}
  • - + @@ -27,7 +27,7 @@
  • - + diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index f8f55d70..140ca844 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -75,7 +75,7 @@ func TestHighlights(t *testing.T) { mustReturnStatus(response, fiber.StatusOK, t) - assertHighlights(app, t, adminCookie, adminUser.Username, 1) + assertHighlights(app, t, adminCookie, 1) response, err = highlight(adminCookie, app, "john-doe-test-epub", fiber.MethodDelete, t) if err != nil { @@ -84,7 +84,7 @@ func TestHighlights(t *testing.T) { mustReturnStatus(response, fiber.StatusOK, t) - assertHighlights(app, t, adminCookie, adminUser.Username, 0) + assertHighlights(app, t, adminCookie, 0) }) t.Run("Deleting a document also removes it from the highlights of all users", func(t *testing.T) { @@ -105,7 +105,7 @@ func TestHighlights(t *testing.T) { mustReturnStatus(response, fiber.StatusOK, t) - assertHighlights(app, t, regularUserCookie, regularUser.Username, 1) + assertHighlights(app, t, regularUserCookie, 1) adminCookie, err = login(app, "admin@example.com", "admin", t) if err != nil { @@ -122,7 +122,7 @@ func TestHighlights(t *testing.T) { if total != 0 { t.Errorf("Expected no highlights in DB for user, got %d", total) } - assertHighlights(app, t, adminCookie, regularUser.Username, 0) + assertHighlights(app, t, adminCookie, 0) }) t.Run("Deleting a user also remove his/her highlights", func(t *testing.T) { @@ -143,7 +143,7 @@ func TestHighlights(t *testing.T) { mustReturnStatus(response, fiber.StatusOK, t) - assertHighlights(app, t, regularUserCookie, regularUser.Username, 1) + assertHighlights(app, t, regularUserCookie, 1) adminCookie, err = login(app, "admin@example.com", "admin", t) if err != nil { @@ -160,13 +160,12 @@ func TestHighlights(t *testing.T) { if total != 0 { t.Errorf("Expected no highlights in DB for deleted user, got %d", total) } - assertNoHighlights(app, t, adminCookie, regularUser.Username) }) } func highlight(cookie *http.Cookie, app *fiber.App, slug string, method string, t *testing.T) (*http.Response, error) { t.Helper() - req, err := http.NewRequest(method, fmt.Sprintf("/documents/%s/highlight", slug), nil) + req, err := http.NewRequest(method, fmt.Sprintf("/highlights/%s", slug), nil) if err != nil { return nil, err } @@ -176,10 +175,10 @@ func highlight(cookie *http.Cookie, app *fiber.App, slug string, method string, return app.Test(req) } -func assertHighlights(app *fiber.App, t *testing.T, cookie *http.Cookie, username string, expectedResults int) { +func assertHighlights(app *fiber.App, t *testing.T, cookie *http.Cookie, expectedResults int) { t.Helper() - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/en/highlights/%s", username), nil) + req, err := http.NewRequest(http.MethodGet, "/en/highlights", nil) req.AddCookie(cookie) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -202,10 +201,10 @@ func assertHighlights(app *fiber.App, t *testing.T, cookie *http.Cookie, usernam } } -func assertNoHighlights(app *fiber.App, t *testing.T, cookie *http.Cookie, username string) { +func assertNoHighlights(app *fiber.App, t *testing.T, cookie *http.Cookie) { t.Helper() - req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/en/highlights/%s", username), nil) + req, err := http.NewRequest(http.MethodGet, "/en/highlights", nil) req.AddCookie(cookie) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) diff --git a/internal/webserver/middleware.go b/internal/webserver/middleware.go index 9c6b4250..31ff9bdb 100644 --- a/internal/webserver/middleware.go +++ b/internal/webserver/middleware.go @@ -111,7 +111,7 @@ func forbidden(c *fiber.Ctx, sender Sender, err error) error { emailSendingConfigured = false } message := "" - if err.Error() != "missing or malformed JWT" && c.Cookies("coreander") != "void" { + if err.Error() != "missing or malformed JWT" && c.Cookies("coreander") != "" { message = "Session expired, please log in again." } return c.Status(fiber.StatusForbidden).Render("auth/login", fiber.Map{ diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index 44c4026e..ac80a582 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -61,9 +61,9 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se usersGroup.Put("/:username", alwaysRequireAuthentication, controllers.Users.Update) app.Delete("/users/:username", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Delete) - langGroup.Get("/highlights/:username", alwaysRequireAuthentication, controllers.Highlights.List) - app.Post("/documents/:slug/highlight", alwaysRequireAuthentication, controllers.Highlights.Create) - app.Delete("/documents/:slug/highlight", alwaysRequireAuthentication, controllers.Highlights.Delete) + langGroup.Get("/highlights", alwaysRequireAuthentication, controllers.Highlights.List) + app.Post("/highlights/:slug", alwaysRequireAuthentication, controllers.Highlights.Create) + app.Delete("/highlights/:slug", alwaysRequireAuthentication, controllers.Highlights.Delete) app.Delete("/documents/:slug", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) From c1e4768c13a9d95d228eb65d37785cffb716279d Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Sat, 3 Aug 2024 21:21:34 +0100 Subject: [PATCH 19/26] Open document reader from last position --- .../embedded/js/{foliate-js => }/reader.js | 24 ++++++++++++------- internal/webserver/embedded/views/reader.html | 3 ++- 2 files changed, 18 insertions(+), 9 deletions(-) rename internal/webserver/embedded/js/{foliate-js => }/reader.js (92%) diff --git a/internal/webserver/embedded/js/foliate-js/reader.js b/internal/webserver/embedded/js/reader.js similarity index 92% rename from internal/webserver/embedded/js/foliate-js/reader.js rename to internal/webserver/embedded/js/reader.js index 9dc4ee68..d292dad1 100644 --- a/internal/webserver/embedded/js/foliate-js/reader.js +++ b/internal/webserver/embedded/js/reader.js @@ -1,7 +1,7 @@ -import './view.js' -import { createTOCView } from './ui/tree.js' -import { createMenu } from './ui/menu.js' -import { Overlayer } from './overlayer.js' +import './foliate-js/view.js' +import { createTOCView } from './foliate-js/ui/tree.js' +import { createMenu } from './foliate-js/ui/menu.js' +import { Overlayer } from './foliate-js/overlayer.js' const isZip = async file => { const arr = new Uint8Array(await file.slice(0, 4).arrayBuffer()) @@ -17,7 +17,7 @@ const isPDF = async file => { const makeZipLoader = async file => { const { configure, ZipReader, BlobReader, TextWriter, BlobWriter } = - await import('./vendor/zip.js') + await import('./foliate-js/vendor/zip.js') configure({ useWebWorkers: false }) const reader = new ZipReader(new BlobReader(file)) const entries = await reader.getEntries() @@ -35,17 +35,21 @@ const getView = async file => { if (!file.size) throw new Error('File not found') else if (await isZip(file)) { const loader = await makeZipLoader(file) - const { EPUB } = await import('./epub.js') + const { EPUB } = await import('./foliate-js/epub.js') book = await new EPUB(loader).init() } else if (await isPDF(file)) { - const { makePDF } = await import('./pdf.js') + const { makePDF } = await import('./foliate-js/pdf.js') book = await makePDF(file) } if (!book) throw new Error('File type not supported') const view = document.createElement('foliate-view') + const storage = window.localStorage + const slug = document.getElementById('slug').value + document.body.append(view) await view.open(book) + await view.init({lastLocation: storage.getItem(slug)}) return view } @@ -183,7 +187,7 @@ class Reader { // load and show highlights embedded in the file by Calibre const bookmarks = await book.getCalibreBookmarks?.() if (bookmarks) { - const { fromCalibreHighlight } = await import('./epubcfi.js') + const { fromCalibreHighlight } = await import('./foliate-js/epubcfi.js') for (const obj of bookmarks) { if (obj.type === 'highlight') { const value = fromCalibreHighlight(obj) @@ -222,6 +226,10 @@ class Reader { doc.addEventListener('keydown', this.#handleKeydown.bind(this)) } #onRelocate({ detail }) { + const storage = window.localStorage + const slug = document.getElementById('slug').value + + storage.setItem(slug, detail.cfi) const { fraction, location, tocItem, pageItem } = detail const percent = percentFormat.format(fraction) const loc = pageItem diff --git a/internal/webserver/embedded/views/reader.html b/internal/webserver/embedded/views/reader.html index 1746b0a9..67d5daf0 100644 --- a/internal/webserver/embedded/views/reader.html +++ b/internal/webserver/embedded/views/reader.html @@ -14,6 +14,7 @@ +
    @@ -79,4 +80,4 @@ }} - + From d0ab900bb21622fdc9bfde58f9388c16455fe257 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Thu, 8 Aug 2024 17:10:40 +0100 Subject: [PATCH 20/26] Use epub lib method for metadata --- internal/index/bleve_write.go | 2 +- internal/metadata/epub.go | 122 ++++++++++++++++------------------ internal/metadata/metadata.go | 1 - 3 files changed, 57 insertions(+), 68 deletions(-) diff --git a/internal/index/bleve_write.go b/internal/index/bleve_write.go index 5100eeaa..1cee28eb 100644 --- a/internal/index/bleve_write.go +++ b/internal/index/bleve_write.go @@ -135,7 +135,7 @@ func (b *BleveIndexer) createDocument(meta metadata.Metadata, fullPath string, b // processed in the current batch in memory to also compare the current doc slug against them. func (b *BleveIndexer) Slug(document DocumentWrite, batchSlugs map[string]struct{}) string { docSlug := makeSlug(document) - exp, err := regexp.Compile(`^[a-zA-Z0-9\-]+(--)\d$`) + exp, err := regexp.Compile(`^[a-zA-Z0-9\-]+(--)[0-9]+$`) if err != nil { log.Fatal(err) } diff --git a/internal/metadata/epub.go b/internal/metadata/epub.go index 562b85cf..ace34881 100644 --- a/internal/metadata/epub.go +++ b/internal/metadata/epub.go @@ -22,26 +22,24 @@ type EpubReader struct{} func (e EpubReader) Metadata(file string) (Metadata, error) { bk := Metadata{} - opf, err := epub.GetPackageFromFile(file) + meta, err := epub.GetMetadataFromFile(file) if err != nil { return bk, err } title := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) - if len(opf.Metadata.Title) > 0 && len(opf.Metadata.Title[0].Value) > 0 { - title = opf.Metadata.Title[0].Value + if len(meta.Title) > 0 && len(meta.Title[0]) > 0 { + title = meta.Title[0] } var authors []string - if len(opf.Metadata.Creator) > 0 { - for _, creator := range opf.Metadata.Creator { - if creator.Role == "aut" || creator.Role == "" { - // Some epub files mistakenly put all authors in a single field instead of using a field for each one. - // We want to identify those cases looking for specific separators and then indexing each author properly. - names := strings.Split(creator.Value, "&") - for i := range names { - names[i] = strings.TrimSpace(names[i]) - } - authors = append(authors, names...) + for _, creator := range meta.Creator { + if creator.Role == "aut" || creator.Role == "" { + // Some epub files mistakenly put all authors in a single field instead of using a field for each one. + // We want to identify those cases looking for specific separators and then indexing each author properly. + names := strings.Split(creator.FullName, "&") + for i := range names { + names[i] = strings.TrimSpace(names[i]) } + authors = append(authors, names...) } } @@ -50,72 +48,51 @@ func (e EpubReader) Metadata(file string) (Metadata, error) { } var subjects []string - if len(opf.Metadata.Subject) > 0 { - for _, subject := range opf.Metadata.Subject { - subject.Value = strings.TrimSpace(subject.Value) - if subject.Value == "" { - continue - } - // Some epub files mistakenly put all subjects in a single field instead of using a field for each one. - // We want to identify those cases looking for specific separators and then indexing each subject properly. - names := strings.Split(subject.Value, ",") - for i := range names { - names[i] = strings.TrimSpace(names[i]) - } - subjects = append(subjects, names...) + for _, subject := range meta.Subject { + subject = strings.TrimSpace(subject) + if subject == "" { + continue + } + // Some epub files mistakenly put all subjects in a single field instead of using a field for each one. + // We want to identify those cases looking for specific separators and then indexing each subject properly. + names := strings.Split(subject, ",") + for i := range names { + names[i] = strings.TrimSpace(names[i]) } + subjects = append(subjects, names...) } description := "" - if len(opf.Metadata.Description) > 0 { + if len(meta.Description) > 0 { strict := bluemonday.StrictPolicy() - noHTMLDescription := strict.Sanitize(opf.Metadata.Description[0].Value) - if noHTMLDescription == opf.Metadata.Description[0].Value { - paragraphs := strings.Split(opf.Metadata.Description[0].Value, "\n") + noHTMLDescription := strict.Sanitize(meta.Description[0]) + if noHTMLDescription == meta.Description[0] { + paragraphs := strings.Split(meta.Description[0], "\n") description = "

    " + strings.Join(paragraphs, "

    ") + "

    " } else { p := bluemonday.UGCPolicy() - description = p.Sanitize(opf.Metadata.Description[0].Value) + description = p.Sanitize(meta.Description[0]) } } lang := "" - if len(opf.Metadata.Language) > 0 { - lang = opf.Metadata.Language[0].Value + if len(meta.Language) > 0 { + lang = meta.Language[0] } year := "" - if len(opf.Metadata.Date) > 0 { - for _, date := range opf.Metadata.Date { - if date.Event == "publication" || date.Event == "" { - t, err := time.Parse("2006-01-02", date.Value) - if err == nil { - year = strings.TrimLeft(t.Format("2006"), "0") - break - } + for _, date := range meta.Date { + if date.Event == "publication" || date.Event == "" { + t, err := time.Parse("2006-01-02", date.Stamp) + if err == nil { + year = strings.TrimLeft(t.Format("2006"), "0") + break } } } - cover := "" - series := "" var seriesIndex float64 = 0 - for _, val := range opf.Metadata.Meta { - if val.Name == "cover" { - id := val.Content - for _, item := range opf.Manifest.Items { - if item.ID == id { - cover = item.Href - break - } - } - } - if val.Name == "calibre:series" { - series = val.Content - } - if val.Name == "calibre:series_index" { - seriesIndex, _ = strconv.ParseFloat(val.Content, 64) - } - } + + seriesIndex, _ = strconv.ParseFloat(meta.SeriesIndex, 64) bk = Metadata{ Title: title, @@ -123,8 +100,7 @@ func (e EpubReader) Metadata(file string) (Metadata, error) { Description: template.HTML(description), Language: lang, Year: year, - Cover: cover, - Series: series, + Series: meta.Series, SeriesIndex: seriesIndex, Type: "EPUB", Subjects: subjects, @@ -141,12 +117,26 @@ func (e EpubReader) Metadata(file string) (Metadata, error) { func (e EpubReader) Cover(documentFullPath string, coverMaxWidth int) ([]byte, error) { var cover []byte - reader := EpubReader{} - meta, err := reader.Metadata(documentFullPath) + coverFileName := "" + + opf, err := epub.GetPackageFromFile(documentFullPath) if err != nil { return nil, err } - if meta.Cover == "" { + + for _, val := range opf.Metadata.Meta { + if val.Name != "cover" { + continue + } + for _, item := range opf.Manifest.Items { + if item.ID == val.Content { + coverFileName = item.Href + break + } + } + } + + if coverFileName == "" { return nil, fmt.Errorf("no cover image set in %s", documentFullPath) } @@ -155,7 +145,7 @@ func (e EpubReader) Cover(documentFullPath string, coverMaxWidth int) ([]byte, e return nil, err } defer r.Close() - cover, err = extractCover(r, meta.Cover, coverMaxWidth) + cover, err = extractCover(r, coverFileName, coverMaxWidth) if err != nil { return nil, err } diff --git a/internal/metadata/metadata.go b/internal/metadata/metadata.go index 55a4f865..aac25193 100644 --- a/internal/metadata/metadata.go +++ b/internal/metadata/metadata.go @@ -13,7 +13,6 @@ type Metadata struct { Language string Year string Words float64 - Cover string Series string SeriesIndex float64 Pages int From a0d363d67988a2891c2a46c2740ef5e31dfa23bf Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Sun, 8 Sep 2024 17:22:08 +0200 Subject: [PATCH 21/26] Fixed not retrieving results for Uppercase accented words using lowercase non-accented counterparts --- internal/index/bleve.go | 12 ++++++------ internal/index/bleve_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/internal/index/bleve.go b/internal/index/bleve.go index 2de52b57..48459f92 100644 --- a/internal/index/bleve.go +++ b/internal/index/bleve.go @@ -24,7 +24,7 @@ import ( // Version identifies the mapping used for indexing. Any changes in the mapping requires an increase // of version, to signal that a new index needs to be created. -const Version = "v2" +const Version = "v3" // Metadata fields var ( @@ -33,11 +33,11 @@ var ( ) var noStopWordsFilters = map[string][]string{ - es.AnalyzerName: {es.NormalizeName, lowercase.Name, es.LightStemmerName}, - en.AnalyzerName: {en.PossessiveName, lowercase.Name, porter.Name}, - de.AnalyzerName: {de.NormalizeName, lowercase.Name, de.LightStemmerName}, - fr.AnalyzerName: {fr.ElisionName, lowercase.Name, fr.LightStemmerName}, - it.AnalyzerName: {it.ElisionName, lowercase.Name, it.LightStemmerName}, + es.AnalyzerName: {lowercase.Name, es.NormalizeName, es.LightStemmerName}, + en.AnalyzerName: {lowercase.Name, en.PossessiveName, porter.Name}, + de.AnalyzerName: {lowercase.Name, de.NormalizeName, de.LightStemmerName}, + fr.AnalyzerName: {lowercase.Name, fr.ElisionName, fr.LightStemmerName}, + it.AnalyzerName: {lowercase.Name, it.ElisionName, it.LightStemmerName}, pt.AnalyzerName: {lowercase.Name, pt.LightStemmerName}, } diff --git a/internal/index/bleve_test.go b/internal/index/bleve_test.go index 26b71843..20fa63dc 100644 --- a/internal/index/bleve_test.go +++ b/internal/index/bleve_test.go @@ -290,5 +290,34 @@ func testCases() []testCase { }, ), }, + { + "Test spanish stemmer returning accented word while using unaccented word in search", + "lib/book9.epub", + metadata.Metadata{ + Title: "Últimos días en Colditz", + Authors: []string{"Patrick R. Reid"}, + Description: "Just test metadata", + Language: "es", + Subjects: []string{"History", "WWII"}, + }, + "ultimos", + result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + 1, + 1, + []index.Document{ + { + ID: "book9.epub", + Slug: "patrick-r-reid-ultimos-dias-en-colditz", + Metadata: metadata.Metadata{ + Title: "Últimos días en Colditz", + Authors: []string{"Patrick R. Reid"}, + Description: "Just test metadata", + Subjects: []string{"History", "WWII"}, + }, + }, + }, + ), + }, } } From fc2cc2a606e7fd4f0af276177d41eb568b6e15ef Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Wed, 11 Sep 2024 11:59:31 +0200 Subject: [PATCH 22/26] Kepub support and skip indexing already indexed docs --- README.md | 3 +- config.go | 4 +- go.mod | 14 ++++- go.sum | 24 ++++++++ internal/index/bleve_test.go | 2 +- internal/index/bleve_write.go | 39 +++++++++++-- .../webserver/controller/document/download.go | 56 ++++++++++++++++--- .../webserver/controller/document/search.go | 2 + .../webserver/controller/document/send.go | 9 ++- internal/webserver/embedded/js/send-email.js | 5 +- .../webserver/embedded/views/document.html | 17 ++++-- .../embedded/views/partials/actions.html | 14 ++++- .../embedded/views/partials/delete-modal.html | 2 +- .../embedded/views/partials/docs-list.html | 4 +- .../embedded/views/partials/related.html | 2 +- internal/webserver/embedded/views/reader.html | 2 +- .../webserver/embedded/views/users/index.html | 2 +- internal/webserver/highlights_test.go | 6 +- internal/webserver/remove_document_test.go | 2 +- internal/webserver/routes.go | 48 ++++++++-------- internal/webserver/send_document_test.go | 4 +- internal/webserver/user_management_test.go | 10 ++-- internal/webserver/webserver_test.go | 2 +- main.go | 10 +--- 24 files changed, 200 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 312403d9..a517b2b7 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ A personal documents server, Coreander indexes the documents (EPUBs and PDFs wit * Read indexed epubs and PDFs from Coreander's interface thanks to [foliate-js](https://github.com/johnfactotum/foliate-js). * Restrictable access only to registered users. * Upload documents through the web interface. +* Download as kepub (epub for Kobo devices) converted on the fly thanks to [Kepubify](https://github.com/pgaskin/kepubify). ## Installation @@ -97,7 +98,7 @@ On first run, Coreander creates an admin user with the following credentials: * `PORT`: Port number in which the webserver listens for requests. Defaults to 3000. * `BATCH_SIZE`: Number of documents persisted by the indexer in one write operation. Defaults to 100. * `COVER_MAX_WIDTH`: Maximum horizontal size for documents cover thumbnails in pixels. Defaults to 600. -* `SKIP_INDEXING`: Whether to bypass the indexing process or not. +* `FORCE_INDEXING`: Whether to force indexing already indexed documents or not. Defaults to false. * `SMTP_SERVER`: Address of the send mail server. * `SMTP_PORT`: Port number of the send mail server. Defaults to 587. * `SMTP_USER`: User to authenticate against the SMTP server. diff --git a/config.go b/config.go index 83ba03f0..b27a385f 100644 --- a/config.go +++ b/config.go @@ -12,8 +12,8 @@ type Config struct { BatchSize int `env:"BATCH_SIZE" env-default:"100"` // CoverMaxWidth sets the maximum horizontal size for documents cover thumbnails in pixels CoverMaxWidth int `env:"COVER_MAX_WIDTH" env-default:"600"` - // SkipIndexing signals whether to bypass the indexing process or not - SkipIndexing bool `env:"SKIP_INDEXING" env-default:"false"` + // ForceIndexing signals whether to force indexing already indexed documents or not + ForceIndexing bool `env:"FORCE_INDEXING" env-default:"false"` // SmtpServer points to the address of the send mail server SmtpServer string `env:"SMTP_SERVER"` // SmtpPort defines the port in which the mail server listens for requests diff --git a/go.mod b/go.mod index 88e040d5..83621b6a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/svera/coreander/v4 -go 1.21 +go 1.21.0 + +toolchain go1.21.1 require ( github.com/blevesearch/bleve/v2 v2.4.0 @@ -27,9 +29,11 @@ require ( require ( github.com/andybalholm/cascadia v1.3.2 // indirect + github.com/beevik/etree v1.4.1 // indirect github.com/blevesearch/go-faiss v1.0.13 // indirect github.com/blevesearch/zapx/v16 v16.0.12 // indirect github.com/dustin/go-humanize v1.0.1 // indirect + github.com/geek1011/kepubify v2.3.2+incompatible // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect @@ -37,14 +41,21 @@ require ( github.com/hhrutter/lzw v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect + github.com/kr/smartypants v0.1.0 // indirect github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-zglob v0.0.5 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect + github.com/pgaskin/kepubify v2.3.2+incompatible // indirect + github.com/pgaskin/kepubify/_/go116-zip.go117 v0.0.0-20210611152744-2d89b3182523 // indirect + github.com/pgaskin/kepubify/_/html v0.0.0-20211223234002-6ee2cc632cdc // indirect github.com/philhofer/fwd v1.1.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/pflag v1.0.5 // indirect github.com/tinylib/msgp v1.1.9 // indirect + golang.org/x/sync v0.7.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect modernc.org/libc v1.49.3 // indirect @@ -90,6 +101,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mschoch/smat v0.2.0 // indirect + github.com/pgaskin/kepubify/v4 v4.0.4 github.com/pirmd/epub v0.3.0 github.com/rivo/uniseg v0.4.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/go.sum b/go.sum index 2be4c03b..113c2ae5 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,10 @@ github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0 h1:R/qAiUxFT3mNgQ github.com/antzucaro/matchr v0.0.0-20210222213004-b04723ef80f0/go.mod h1:v3ZDlfVAL1OrkKHbGSFFK60k0/7hruHPDq2XMs9Gu6U= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bamiaux/rez v0.0.0-20170731184118-29f4463c688b/go.mod h1:obBQGGIFbbv9KWg92Qu9UHeD94JXmHD1jovY/z6I3O8= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= +github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= +github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bits-and-blooms/bitset v1.13.0 h1:bAQ9OPNFYbGHV6Nez0tmNI0RiEu7/hxlYJRUA0wFAVE= github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= @@ -67,6 +71,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/flotzilla/pdf_parser v0.1.96 h1:SlgvO7NZqFzhBO+o6X1u7rUYjhv+81V3dYQF+LTfGOE= github.com/flotzilla/pdf_parser v0.1.96/go.mod h1:/CPB1OWEeFqRbtnFWXgArmOnA3u7smVHxr5dFy4U6Nk= +github.com/geek1011/kepubify v2.3.2+incompatible h1:G1dAwpTpSHN79/bOqQ64SjkllYPhpp25kUkJ95WNFnA= +github.com/geek1011/kepubify v2.3.2+incompatible/go.mod h1:xMWLgn5FQSh6oNcq//MwSwPf2WgyQq6T+fY9nSn9+Cs= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= @@ -104,6 +110,7 @@ github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es= github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= +github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/hhrutter/lzw v1.0.0 h1:laL89Llp86W3rRs83LvKbwYRx6INE8gDn0XNb1oXtm0= github.com/hhrutter/lzw v1.0.0/go.mod h1:2HC6DJSn/n6iAZfgM3Pg+cP1KxeWc3ezG8bBqW5+WEo= github.com/hhrutter/tiff v1.0.1 h1:MIus8caHU5U6823gx7C6jrfoEvfSTGtEFRiM8/LOzC0= @@ -122,6 +129,8 @@ github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQs github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/smartypants v0.1.0 h1:Sn8hn5XrY+uXrxSWUdcr621Gfpk11mOGGVs4XX06kEw= +github.com/kr/smartypants v0.1.0/go.mod h1:EcTX9ge+SWNaGwbQvHwNICsMGavh98FLUqyOWFr+j9c= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= @@ -138,6 +147,9 @@ github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-zglob v0.0.5 h1:LKgpZXHg94zoBPDbb6aeiCs4SiQSS8otal4JnzkIvMc= +github.com/mattn/go-zglob v0.0.5/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -153,6 +165,15 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pdfcpu/pdfcpu v0.7.0 h1:cd7/z7hAyyDuzdciKfNZyQ3TYreJza2DsuPdIHYURcA= github.com/pdfcpu/pdfcpu v0.7.0/go.mod h1:kmpD0rk8YnZj0l3qSeGBlAB+XszHUgNv//ORH/E7EYo= +github.com/pgaskin/kepubify v2.3.2+incompatible h1:0wWWxop5T5O+p6tlZ59saC9hZYtkrXaKWLiTXmUNHHg= +github.com/pgaskin/kepubify v2.3.2+incompatible/go.mod h1:vQR3SJUwNyKStXpUPsVcCjBZtjZ1TgYtgKb8jHMyEDg= +github.com/pgaskin/kepubify/_/go116-zip.go117 v0.0.0-20210611152744-2d89b3182523 h1:pYGj3rKTy+TDs5Z707kT+ztjoIDCy76lc2UPkZocAFM= +github.com/pgaskin/kepubify/_/go116-zip.go117 v0.0.0-20210611152744-2d89b3182523/go.mod h1:FNMbV/TSSnhqyzjq8jsS+VD0o/gwpuCH0dh8G1uQ/fw= +github.com/pgaskin/kepubify/_/html v0.0.0-20211223234002-6ee2cc632cdc h1:mJk4TIXTO+JmxgHJ5iyil42PLQJWkyaKB/qNcjJU6h4= +github.com/pgaskin/kepubify/_/html v0.0.0-20211223234002-6ee2cc632cdc/go.mod h1:fxzoIpMFAReNKunZ+ttVbf3hNVrJGtrSZMI4olZizbs= +github.com/pgaskin/kepubify/v4 v4.0.4 h1:8ePyepo4eRNSmeDs5MdJLJtit+zxmK36wILmGcvpccU= +github.com/pgaskin/kepubify/v4 v4.0.4/go.mod h1:wzUdFNYW2uZh2xfHDuzNRRUO4WqV+y99UBxVd3rBTus= +github.com/pgaskin/koboutils/v2 v2.1.2-0.20220306004009-a07e72ebae42/go.mod h1:wTzkDIlsxmUyfwfspGcm0Ap+HOxSUYV0S8kMYrf+0gM= github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.1.2 h1:bnDivRJ1EWPjUIRXV5KfORO897HTbpFAQddBdE8t7Gw= github.com/philhofer/fwd v1.1.2/go.mod h1:qkPdfjR2SIEbspLqpe1tO4n5yICnr2DY7mqEx2tUTP0= @@ -181,6 +202,8 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -235,6 +258,7 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= diff --git a/internal/index/bleve_test.go b/internal/index/bleve_test.go index 20fa63dc..eedc5318 100644 --- a/internal/index/bleve_test.go +++ b/internal/index/bleve_test.go @@ -35,7 +35,7 @@ func TestIndexAndSearch(t *testing.T) { appFS.MkdirAll("lib", 0755) afero.WriteFile(appFS, tcase.filename, []byte(""), 0644) - err = idx.AddLibrary(1) + err = idx.AddLibrary(1, true) if err != nil { t.Errorf("Error indexing: %s", err.Error()) } diff --git a/internal/index/bleve_write.go b/internal/index/bleve_write.go index 1cee28eb..030ff15a 100644 --- a/internal/index/bleve_write.go +++ b/internal/index/bleve_write.go @@ -10,6 +10,7 @@ import ( "strings" "time" + index "github.com/blevesearch/bleve_index_api" "github.com/gosimple/slug" "github.com/spf13/afero" "github.com/svera/coreander/v4/internal/metadata" @@ -45,13 +46,19 @@ func (b *BleveIndexer) RemoveFile(file string) error { return nil } -// AddLibrary scans for documents and adds them to the index in batches of -func (b *BleveIndexer) AddLibrary(batchSize int) error { +// AddLibrary scans for documents and adds them to the index in batches of if they +// haven't been previously indexed or if is true +func (b *BleveIndexer) AddLibrary(batchSize int, forceIndexing bool) error { batch := b.idx.NewBatch() batchSlugs := make(map[string]struct{}, batchSize) languages := []string{} b.indexStartTime = float64(time.Now().UnixNano()) e := afero.Walk(b.fs, b.libraryPath, func(fullPath string, f os.FileInfo, err error) error { + if indexed, lang := b.isAlreadyIndexed(fullPath); indexed && !forceIndexing { + b.indexedDocuments += 1 + languages = addLanguage(lang, languages) + return nil + } ext := strings.ToLower(filepath.Ext(fullPath)) if _, ok := b.reader[ext]; !ok { return nil @@ -80,13 +87,33 @@ func (b *BleveIndexer) AddLibrary(batchSize int) error { } return nil }) + if len(languages) > 0 { + batch.SetInternal(internalLanguages, []byte(strings.Join(languages, ","))) + } + b.idx.Batch(batch) b.indexStartTime = 0 b.indexedDocuments = 0 - batch.SetInternal(internalLanguages, []byte(strings.Join(languages, ","))) - b.idx.Batch(batch) return e } +func (b *BleveIndexer) isAlreadyIndexed(fullPath string) (bool, string) { + doc, err := b.idx.Document(b.id(fullPath)) + if err != nil { + log.Fatalln(err) + } + if doc == nil { + return false, "" + } + lang := "" + doc.VisitFields(func(f index.Field) { + if f.Name() == "Language" { + lang = string(f.Value()) + return + } + }) + return true, lang +} + func addLanguage(lang string, languages []string) []string { if !slices.Contains(languages, defaultAnalyzer) && lang == "" { return append(languages, defaultAnalyzer) @@ -117,7 +144,7 @@ func (b *BleveIndexer) createDocument(meta metadata.Metadata, fullPath string, b SubjectsEq: make([]string, len(meta.Subjects)), } - document.ID = b.ID(document, fullPath) + document.ID = b.id(fullPath) document.Slug = b.Slug(document, batchSlugs) copy(document.AuthorsEq, meta.Authors) for i := range document.AuthorsEq { @@ -161,7 +188,7 @@ func (b *BleveIndexer) Slug(document DocumentWrite, batchSlugs map[string]struct } } -func (b *BleveIndexer) ID(meta DocumentWrite, file string) string { +func (b *BleveIndexer) id(file string) string { ID := strings.ReplaceAll(file, b.libraryPath, "") return strings.TrimPrefix(ID, string(filepath.Separator)) } diff --git a/internal/webserver/controller/document/download.go b/internal/webserver/controller/document/download.go index 7c997900..abc4b305 100644 --- a/internal/webserver/controller/document/download.go +++ b/internal/webserver/controller/document/download.go @@ -1,16 +1,27 @@ package document import ( + "archive/zip" + "bytes" + "context" "fmt" "io" + "log" "os" "path/filepath" "strings" "github.com/gofiber/fiber/v2" + "github.com/pgaskin/kepubify/v4/kepub" ) func (d *Controller) Download(c *fiber.Ctx) error { + var ( + output []byte + err error + fileName string + ) + document, err := d.idx.Document(c.Params("slug")) if err != nil { return fiber.ErrBadRequest @@ -22,13 +33,25 @@ func (d *Controller) Download(c *fiber.Ctx) error { return fiber.ErrNotFound } - file, err := os.Open(fullPath) - if err != nil { - return fiber.ErrInternalServerError - } - contents, err := io.ReadAll(file) - if err != nil { - return fiber.ErrInternalServerError + if c.Query("format") == "kepub" { + output, err = kepubify(fullPath) + if err != nil { + log.Println(err) + return fiber.ErrInternalServerError + } + fileName = strings.TrimSuffix(filepath.Base(fullPath), filepath.Ext(fullPath)) + fileName = fileName + ".kepub.epub" + } else { + file, err := os.Open(fullPath) + if err != nil { + log.Println(err) + return fiber.ErrInternalServerError + } + if output, err = io.ReadAll(file); err != nil { + log.Println(err) + return fiber.ErrInternalServerError + } + fileName = filepath.Base(document.ID) } ext := strings.ToLower(filepath.Ext(document.ID)) @@ -39,7 +62,22 @@ func (d *Controller) Download(c *fiber.Ctx) error { c.Response().Header.Set(fiber.HeaderContentType, "application/pdf") } - c.Response().Header.Set(fiber.HeaderContentDisposition, fmt.Sprintf("inline; filename=\"%s\"", filepath.Base(document.ID))) - c.Response().BodyWriter().Write(contents) + c.Response().Header.Set(fiber.HeaderContentDisposition, fmt.Sprintf("inline; filename=\"%s\"", fileName)) + c.Response().BodyWriter().Write(output) return nil } + +func kepubify(fullPath string) ([]byte, error) { + output := bytes.NewBuffer(nil) + r, err := zip.OpenReader(fullPath) + if err != nil { + return nil, fiber.ErrInternalServerError + } + defer r.Close() + + if err = kepub.NewConverter().Convert(context.Background(), output, r); err != nil { + return nil, fiber.ErrInternalServerError + } + + return output.Bytes(), nil +} diff --git a/internal/webserver/controller/document/search.go b/internal/webserver/controller/document/search.go index dc91b140..a21b8f41 100644 --- a/internal/webserver/controller/document/search.go +++ b/internal/webserver/controller/document/search.go @@ -1,6 +1,7 @@ package document import ( + "log" "strconv" "github.com/gofiber/fiber/v2" @@ -35,6 +36,7 @@ func (d *Controller) Search(c *fiber.Ctx) error { if keywords := c.Query("search"); keywords != "" { if searchResults, err = d.idx.Search(keywords, page, model.ResultsPerPage); err != nil { + log.Println(err) return fiber.ErrInternalServerError } diff --git a/internal/webserver/controller/document/send.go b/internal/webserver/controller/document/send.go index f8898882..ba6b6c14 100644 --- a/internal/webserver/controller/document/send.go +++ b/internal/webserver/controller/document/send.go @@ -1,6 +1,7 @@ package document import ( + "log" "net/mail" "os" "path/filepath" @@ -10,7 +11,8 @@ import ( ) func (d *Controller) Send(c *fiber.Ctx) error { - if strings.Trim(c.FormValue("slug"), " ") == "" { + slug := "" + if slug = strings.Trim(c.Params("slug"), " "); slug == "" { return fiber.ErrBadRequest } @@ -18,13 +20,14 @@ func (d *Controller) Send(c *fiber.Ctx) error { return fiber.ErrBadRequest } - document, err := d.idx.Document(c.FormValue("slug")) + document, err := d.idx.Document(slug) if err != nil { return fiber.ErrBadRequest } if _, err := os.Stat(filepath.Join(d.config.LibraryPath, document.ID)); err != nil { - return fiber.ErrBadRequest + log.Println(err) + return fiber.ErrInternalServerError } return d.sender.SendDocument(c.FormValue("email"), d.config.LibraryPath, document.ID) diff --git a/internal/webserver/embedded/js/send-email.js b/internal/webserver/embedded/js/send-email.js index 73dd487e..60860bb0 100644 --- a/internal/webserver/embedded/js/send-email.js +++ b/internal/webserver/embedded/js/send-email.js @@ -13,14 +13,13 @@ Array.from(forms).forEach(form => { submit.setAttribute("disabled", true); spinner.classList.remove("visually-hidden"); sendIcon.classList.add("visually-hidden"); - fetch('/send', { + fetch(form.getAttribute("action"), { method: "POST", headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ - 'email': form.elements[0].value, - 'slug': form.elements[1].value, + 'email': form.elements[0].value }) }) .then((response) => { diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index 2f17f35f..9c5978cf 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -7,7 +7,7 @@
  • -
  • +
  • @@ -16,6 +16,15 @@ {{.Document.Type}}
  • +
  • + + + + +   {{t .Lang "Download"}} + KEPUB + +
  • {{if and (.Session) (ne .Session.Name "")}}
  • @@ -40,7 +49,7 @@
  • {{else}}
  • - +
    - diff --git a/internal/webserver/embedded/views/partials/docs-list.html b/internal/webserver/embedded/views/partials/docs-list.html index 51860bd3..ee93a525 100644 --- a/internal/webserver/embedded/views/partials/docs-list.html +++ b/internal/webserver/embedded/views/partials/docs-list.html @@ -9,7 +9,7 @@
    - {{t $lang "\"%s\" cover" $document.Title}} + {{t $lang "\"%s\" cover" $document.Title}}
    {{$document.Title}}

    @@ -91,7 +91,7 @@

    {{t $lang "Unknown author"}}
    {{end}} {{end}}
    -{{template "partials/delete-modal" dict "Lang" $lang "Action" "/documents" "ModalHeader" "Delete document" "ModalBody" "Are you sure you want to delete this document?" "ModalErrorMessage" "There was an error deleting the document"}} +{{template "partials/delete-modal" dict "Lang" $lang "Action" "documents" "ModalHeader" "Delete document" "ModalBody" "Are you sure you want to delete this document?" "ModalErrorMessage" "There was an error deleting the document"}} diff --git a/internal/webserver/embedded/views/partials/related.html b/internal/webserver/embedded/views/partials/related.html index 93c895f5..f3a952ab 100644 --- a/internal/webserver/embedded/views/partials/related.html +++ b/internal/webserver/embedded/views/partials/related.html @@ -1,5 +1,5 @@
    - {{t .Lang "\"%s\" cover" .Document.Title}} + {{t .Lang "\"%s\" cover" .Document.Title}}
    {{.Document.Title}}
    {{if .Document.Authors}} diff --git a/internal/webserver/embedded/views/reader.html b/internal/webserver/embedded/views/reader.html index 67d5daf0..25f8cf63 100644 --- a/internal/webserver/embedded/views/reader.html +++ b/internal/webserver/embedded/views/reader.html @@ -13,7 +13,7 @@ - +
    diff --git a/internal/webserver/embedded/views/users/index.html b/internal/webserver/embedded/views/users/index.html index c463bbeb..8022a889 100644 --- a/internal/webserver/embedded/views/users/index.html +++ b/internal/webserver/embedded/views/users/index.html @@ -40,6 +40,6 @@

    {{t $lang "Users"}}

    {{template "partials/pagination" .}} {{end}} -{{template "partials/delete-modal" dict "Lang" $lang "Action" "/users" "ModalHeader" "Delete user" "ModalBody" "Are you sure you want to delete this user?" "ModalErrorMessage" "There was an error deleting the user, try again later"}} +{{template "partials/delete-modal" dict "Lang" $lang "Action" "users" "ModalHeader" "Delete user" "ModalBody" "Are you sure you want to delete this user?" "ModalErrorMessage" "There was an error deleting the user, try again later"}} diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index 140ca844..0ec178fe 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -112,7 +112,7 @@ func TestHighlights(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - _, err = deleteRequest(url.Values{}, adminCookie, app, "/documents/john-doe-test-epub", t) + _, err = deleteRequest(url.Values{}, adminCookie, app, "/en/documents/john-doe-test-epub", t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -150,7 +150,7 @@ func TestHighlights(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - _, err = deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/users/%s", regularUser.Username), t) + _, err = deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -165,7 +165,7 @@ func TestHighlights(t *testing.T) { func highlight(cookie *http.Cookie, app *fiber.App, slug string, method string, t *testing.T) (*http.Response, error) { t.Helper() - req, err := http.NewRequest(method, fmt.Sprintf("/highlights/%s", slug), nil) + req, err := http.NewRequest(method, fmt.Sprintf("/en/highlights/%s", slug), nil) if err != nil { return nil, err } diff --git a/internal/webserver/remove_document_test.go b/internal/webserver/remove_document_test.go index 7e29b8e8..3983f849 100644 --- a/internal/webserver/remove_document_test.go +++ b/internal/webserver/remove_document_test.go @@ -60,7 +60,7 @@ func TestRemoveDocument(t *testing.T) { t.Fatalf("Unexpected error: %v", err.Error()) } - response, err = deleteRequest(url.Values{}, cookie, app, fmt.Sprintf("/documents/%s", tcase.slug), t) + response, err = deleteRequest(url.Values{}, cookie, app, fmt.Sprintf("/en/documents/%s", tcase.slug), t) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index ac80a582..fcd0a7ad 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -11,9 +11,12 @@ import ( ) func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Sender, requireAuth bool) { - var allowIfNotLoggedIn = AllowIfNotLoggedIn(jwtSecret) - var alwaysRequireAuthentication = AlwaysRequireAuthentication(jwtSecret, sender) - var configurableAuthentication = ConfigurableAuthentication(jwtSecret, sender, requireAuth) + // Middlewares + var ( + allowIfNotLoggedIn = AllowIfNotLoggedIn(jwtSecret) + alwaysRequireAuthentication = AlwaysRequireAuthentication(jwtSecret, sender) + configurableAuthentication = ConfigurableAuthentication(jwtSecret, sender, requireAuth) + ) app.Use("/css", filesystem.New(filesystem.Config{ Root: http.FS(cssFS), @@ -54,35 +57,34 @@ func routes(app *fiber.App, controllers Controllers, jwtSecret []byte, sender Se usersGroup := langGroup.Group("/users", alwaysRequireAuthentication) - usersGroup.Get("/", alwaysRequireAuthentication, RequireAdmin, controllers.Users.List) - usersGroup.Get("/new", alwaysRequireAuthentication, RequireAdmin, controllers.Users.New) - usersGroup.Post("/", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Create) - usersGroup.Get("/:username", alwaysRequireAuthentication, controllers.Users.Edit) - usersGroup.Put("/:username", alwaysRequireAuthentication, controllers.Users.Update) - app.Delete("/users/:username", alwaysRequireAuthentication, RequireAdmin, controllers.Users.Delete) + usersGroup.Get("/", RequireAdmin, controllers.Users.List) + usersGroup.Get("/new", RequireAdmin, controllers.Users.New) + usersGroup.Post("/", RequireAdmin, controllers.Users.Create) + usersGroup.Get("/:username", controllers.Users.Edit) + usersGroup.Put("/:username", controllers.Users.Update) + usersGroup.Delete("/:username", RequireAdmin, controllers.Users.Delete) - langGroup.Get("/highlights", alwaysRequireAuthentication, controllers.Highlights.List) - app.Post("/highlights/:slug", alwaysRequireAuthentication, controllers.Highlights.Create) - app.Delete("/highlights/:slug", alwaysRequireAuthentication, controllers.Highlights.Delete) - - app.Delete("/documents/:slug", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) + highlightsGroup := langGroup.Group("/highlights", alwaysRequireAuthentication) + highlightsGroup.Get("/", controllers.Highlights.List) + highlightsGroup.Post("/:slug", controllers.Highlights.Create) + highlightsGroup.Delete("/:slug", controllers.Highlights.Delete) + docsGroup := langGroup.Group("/documents") langGroup.Get("/upload", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.UploadForm) - langGroup.Post("/documents", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) + docsGroup.Post("/", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Upload) + docsGroup.Delete("/:slug", alwaysRequireAuthentication, RequireAdmin, controllers.Documents.Delete) // Authentication requirement is configurable for all routes below this middleware langGroup.Use(configurableAuthentication) app.Use(configurableAuthentication) - app.Get("/documents/:slug/cover", controllers.Documents.Cover) - langGroup.Get("/documents/:slug/read", controllers.Documents.Reader) - app.Get("/documents/:slug/download", controllers.Documents.Download) - - langGroup.Get("/documents/:slug", controllers.Documents.Detail) - - app.Post("/send", controllers.Documents.Send) + docsGroup.Get("/:slug/cover", controllers.Documents.Cover) + docsGroup.Get("/:slug/read", controllers.Documents.Reader) + docsGroup.Get("/:slug/download", controllers.Documents.Download) + docsGroup.Post("/:slug/send", controllers.Documents.Send) + docsGroup.Get("/:slug", controllers.Documents.Detail) + docsGroup.Get("/", controllers.Documents.Search) - langGroup.Get("/documents", controllers.Documents.Search) langGroup.Get("/", controllers.Documents.Search) app.Get("/", func(c *fiber.Ctx) error { diff --git a/internal/webserver/send_document_test.go b/internal/webserver/send_document_test.go index e222aae6..a1abd02a 100644 --- a/internal/webserver/send_document_test.go +++ b/internal/webserver/send_document_test.go @@ -22,7 +22,6 @@ func TestSendDocument(t *testing.T) { slug string expectedHTTPStatus int }{ - {"Send no document slug", "admin@example.com", "", http.StatusBadRequest}, {"Send no email address", "", "empty", http.StatusBadRequest}, {"Send non existing document slug", "admin@example.com", "wrong", http.StatusBadRequest}, {"Send document slug and email address", "admin@example.com", "john-doe-test-epub", http.StatusOK}, @@ -37,10 +36,9 @@ func TestSendDocument(t *testing.T) { data := url.Values{ "email": {tcase.email}, - "slug": {tcase.slug}, } - req, err := http.NewRequest(http.MethodPost, "/send", strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodPost, "/en/documents/"+tcase.slug+"/send", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index d8144057..fb76539c 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -304,7 +304,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a user without an active session", func(t *testing.T) { reset() - response, err := deleteRequest(url.Values{}, &http.Cookie{}, app, fmt.Sprintf("/users/%s", regularUser.Username), t) + response, err := deleteRequest(url.Values{}, &http.Cookie{}, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -315,7 +315,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a user with a regular user's session", func(t *testing.T) { reset() - response, err := deleteRequest(url.Values{}, regularUserCookie, app, fmt.Sprintf("/users/%s", regularUser.Username), t) + response, err := deleteRequest(url.Values{}, regularUserCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -326,7 +326,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a user with an admin session", func(t *testing.T) { reset() - response, err := deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/users/%s", regularUser.Username), t) + response, err := deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/en/users/%s", regularUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -341,7 +341,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete the only existing admin user", func(t *testing.T) { reset() - response, err := deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/users/%s", adminUser.Username), t) + response, err := deleteRequest(url.Values{}, adminCookie, app, fmt.Sprintf("/en/users/%s", adminUser.Username), t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -352,7 +352,7 @@ func TestUserManagement(t *testing.T) { t.Run("Try to delete a non existing user with an admin session", func(t *testing.T) { reset() - response, err := deleteRequest(url.Values{}, adminCookie, app, "/users/wrong", t) + response, err := deleteRequest(url.Values{}, adminCookie, app, "/en/users/wrong", t) if response == nil { t.Fatalf("Unexpected error: %v", err.Error()) } diff --git a/internal/webserver/webserver_test.go b/internal/webserver/webserver_test.go index 91c441ea..e5cf53e5 100644 --- a/internal/webserver/webserver_test.go +++ b/internal/webserver/webserver_test.go @@ -77,7 +77,7 @@ func bootstrapApp(db *gorm.DB, sender webserver.Sender, appFs afero.Fs, webserve idx = index.NewBleve(indexFile, appFs, webserverConfig.LibraryPath, metadataReaders) } - err = idx.AddLibrary(100) + err = idx.AddLibrary(100, true) if err != nil { log.Fatal(err) } diff --git a/main.go b/main.go index c1dade71..18abdc10 100644 --- a/main.go +++ b/main.go @@ -81,11 +81,7 @@ func migrateDir() { func main() { defer idx.Close() - if !cfg.SkipIndexing { - go startIndex(idx, appFs, cfg.BatchSize, cfg.LibPath) - } else { - go fileWatcher(idx, cfg.LibPath) - } + go startIndex(idx, appFs, cfg.BatchSize, cfg.LibPath) sender = &infrastructure.NoEmail{} if cfg.SmtpServer != "" && cfg.SmtpUser != "" && cfg.SmtpPassword != "" { @@ -133,7 +129,7 @@ func main() { func startIndex(idx *index.BleveIndexer, appFs afero.Fs, batchSize int, libPath string) { start := time.Now().Unix() log.Printf("Indexing documents at %s, this can take a while depending on the size of your library.", libPath) - err := idx.AddLibrary(batchSize) + err := idx.AddLibrary(batchSize, cfg.ForceIndexing) if err != nil { log.Fatal(err) } @@ -147,7 +143,6 @@ func getIndexFile(fs afero.Fs) bleve.Index { indexFile, err := bleve.Open(homeDir + indexPath) if err == bleve.ErrorIndexPathDoesNotExist { log.Println("No index found, creating a new one.") - cfg.SkipIndexing = false indexFile = index.Create(homeDir + indexPath) } version, err := indexFile.GetInternal([]byte("version")) @@ -159,7 +154,6 @@ func getIndexFile(fs afero.Fs) bleve.Index { if err = fs.RemoveAll(homeDir + indexPath); err != nil { log.Fatal(err) } - cfg.SkipIndexing = false indexFile = index.Create(homeDir + indexPath) } return indexFile From 4669be8781e5c330780c7bbe7548b3b2909bd759 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Sat, 14 Sep 2024 13:23:54 +0200 Subject: [PATCH 23/26] Fixed missing language in highlight links --- internal/webserver/embedded/views/document.html | 4 ++-- internal/webserver/embedded/views/partials/actions.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index 9c5978cf..6282637c 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -50,14 +50,14 @@

    {{if and (.Session) (ne .Session.Name "")}} - +   {{t .Lang "Highlight"}} - + diff --git a/internal/webserver/embedded/views/partials/actions.html b/internal/webserver/embedded/views/partials/actions.html index df3ad3a5..a7825b7a 100644 --- a/internal/webserver/embedded/views/partials/actions.html +++ b/internal/webserver/embedded/views/partials/actions.html @@ -27,7 +27,7 @@

  • {{if and (.Session) (ne .Session.Name "")}}
  • - + @@ -36,7 +36,7 @@
  • - + From b095997dc13c5fff263a38982c4b788f147a5502 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Tue, 24 Sep 2024 19:02:23 +0200 Subject: [PATCH 24/26] Redirect to doc detail after upload --- filewatcher_linux.go | 2 +- internal/index/bleve_write.go | 10 +++++----- .../webserver/controller/document/controller.go | 2 +- internal/webserver/controller/document/detail.go | 6 ++++++ internal/webserver/controller/document/upload.go | 15 +++++++-------- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/filewatcher_linux.go b/filewatcher_linux.go index 9cdb6685..b4acae9c 100644 --- a/filewatcher_linux.go +++ b/filewatcher_linux.go @@ -20,7 +20,7 @@ func fileWatcher(idx *index.BleveIndexer, libPath string) { select { case ei := <-c: if ei.Event() == notify.InCloseWrite || ei.Event() == notify.InMovedFrom { - if err := idx.AddFile(ei.Path()); err != nil { + if _, err := idx.AddFile(ei.Path()); err != nil { log.Printf("Error indexing new file: %s\n", ei.Path()) } } diff --git a/internal/index/bleve_write.go b/internal/index/bleve_write.go index 030ff15a..fb848b99 100644 --- a/internal/index/bleve_write.go +++ b/internal/index/bleve_write.go @@ -17,23 +17,23 @@ import ( ) // AddFile adds a file to the index -func (b *BleveIndexer) AddFile(file string) error { +func (b *BleveIndexer) AddFile(file string) (string, error) { ext := strings.ToLower(filepath.Ext(file)) if _, ok := b.reader[ext]; !ok { - return fmt.Errorf("file extension %s not supported", ext) + return "", fmt.Errorf("file extension %s not supported", ext) } meta, err := b.reader[ext].Metadata(file) if err != nil { - return fmt.Errorf("error extracting metadata from file %s: %s", file, err) + return "", fmt.Errorf("error extracting metadata from file %s: %s", file, err) } document := b.createDocument(meta, file, nil) err = b.idx.Index(document.ID, document) if err != nil { - return fmt.Errorf("error indexing file %s: %s", file, err) + return "", fmt.Errorf("error indexing file %s: %s", file, err) } - return nil + return document.Slug, nil } // RemoveFile removes a file from the index diff --git a/internal/webserver/controller/document/controller.go b/internal/webserver/controller/document/controller.go index ca7b3d6e..82bcf617 100644 --- a/internal/webserver/controller/document/controller.go +++ b/internal/webserver/controller/document/controller.go @@ -23,7 +23,7 @@ type IdxReaderWriter interface { SameSubjects(slug string, quantity int) ([]index.Document, error) SameAuthors(slug string, quantity int) ([]index.Document, error) SameSeries(slug string, quantity int) ([]index.Document, error) - AddFile(file string) error + AddFile(file string) (string, error) RemoveFile(file string) error Documents(IDs []string) (map[string]index.Document, error) } diff --git a/internal/webserver/controller/document/detail.go b/internal/webserver/controller/document/detail.go index 4f28019c..7ab8e037 100644 --- a/internal/webserver/controller/document/detail.go +++ b/internal/webserver/controller/document/detail.go @@ -60,6 +60,11 @@ func (d *Controller) Detail(c *fiber.Ctx) error { document = d.hlRepository.Highlighted(int(session.ID), document) } + msg := "" + if c.Query("success") != "" { + msg = "Document uploaded successfully." + } + return c.Render("document", fiber.Map{ "Title": title, "Document": document, @@ -69,5 +74,6 @@ func (d *Controller) Detail(c *fiber.Ctx) error { "SameAuthors": sameAuthors, "SameSubjects": sameSubjects, "WordsPerMinute": d.config.WordsPerMinute, + "Message": msg, }, "layout") } diff --git a/internal/webserver/controller/document/upload.go b/internal/webserver/controller/document/upload.go index 53f1fc62..d09cb2c1 100644 --- a/internal/webserver/controller/document/upload.go +++ b/internal/webserver/controller/document/upload.go @@ -11,18 +11,13 @@ import ( "slices" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/log" "github.com/valyala/fasthttp" ) func (d *Controller) UploadForm(c *fiber.Ctx) error { - msg := "" - if c.Query("success") != "" { - msg = "Document uploaded successfully." - } - return c.Render("upload", fiber.Map{ "Title": "Coreander", - "Message": msg, "MaxSize": d.config.UploadDocumentMaxSize, }, "layout") } @@ -61,11 +56,13 @@ func (d *Controller) Upload(c *fiber.Ctx) error { bytes, err := fileToBytes(file) if err != nil { + log.Error() return internalServerErrorStatus } destFile, err := d.appFs.Create(destination) if err != nil { + log.Error(err) return internalServerErrorStatus } @@ -74,12 +71,14 @@ func (d *Controller) Upload(c *fiber.Ctx) error { } destFile.Close() - if err := d.idx.AddFile(destination); err != nil { + slug, err := d.idx.AddFile(destination) + if err != nil { + log.Error(err) os.Remove(destination) return internalServerErrorStatus } - return c.Redirect(fmt.Sprintf("/%s/upload?success=1", c.Params("lang"))) + return c.Redirect(fmt.Sprintf("/%s/documents/%s?success=1", c.Params("lang"), slug)) } func fileToBytes(fileHeader *multipart.FileHeader) ([]byte, error) { From 903b78106a3e4cd9ee4ca1a6ea91d3ec6c04d020 Mon Sep 17 00:00:00 2001 From: Sergio Vera Date: Fri, 11 Oct 2024 10:31:52 +0200 Subject: [PATCH 25/26] Removed lang path parameter from routes. Use cookie for locale setting instead --- internal/webserver/authentication_test.go | 34 ++++++------ internal/webserver/controller/auth/login.go | 3 +- internal/webserver/controller/auth/request.go | 6 +-- .../controller/auth/reset-password.go | 3 +- internal/webserver/controller/auth/signin.go | 3 +- .../webserver/controller/document/upload.go | 2 +- internal/webserver/controller/user/create.go | 3 +- internal/webserver/document_detail_test.go | 8 +-- .../embedded/views/auth/edit-password.html | 2 +- .../webserver/embedded/views/auth/login.html | 4 +- .../embedded/views/auth/recover.html | 2 +- .../webserver/embedded/views/document.html | 28 +++++----- internal/webserver/embedded/views/index.html | 4 +- internal/webserver/embedded/views/layout.html | 21 ++++---- .../embedded/views/partials/actions.html | 12 ++--- .../embedded/views/partials/delete-modal.html | 2 +- .../embedded/views/partials/docs-list.html | 10 ++-- .../embedded/views/partials/related.html | 4 +- .../embedded/views/partials/searchbox.html | 2 +- internal/webserver/embedded/views/reader.html | 4 +- internal/webserver/embedded/views/upload.html | 2 +- .../webserver/embedded/views/users/edit.html | 4 +- .../webserver/embedded/views/users/index.html | 4 +- .../webserver/embedded/views/users/new.html | 2 +- internal/webserver/highlights_test.go | 12 ++--- internal/webserver/remove_document_test.go | 2 +- internal/webserver/routes.go | 50 +++++++---------- internal/webserver/search_test.go | 6 +-- internal/webserver/send_document_test.go | 2 +- internal/webserver/upload_test.go | 20 +++---- internal/webserver/user_management_test.go | 54 +++++++++---------- internal/webserver/view/paginator.go | 19 ++----- internal/webserver/view/to_query_string.go | 19 +++++++ internal/webserver/webserver.go | 14 ++++- internal/webserver/webserver_test.go | 6 +-- 35 files changed, 188 insertions(+), 185 deletions(-) create mode 100644 internal/webserver/view/to_query_string.go diff --git a/internal/webserver/authentication_test.go b/internal/webserver/authentication_test.go index f2b86eb5..b9f24e78 100644 --- a/internal/webserver/authentication_test.go +++ b/internal/webserver/authentication_test.go @@ -29,7 +29,7 @@ func TestAuthentication(t *testing.T) { t.Run("Try to log in with good and bad credentials", func(t *testing.T) { // Check that login page is accessible - req, err := http.NewRequest(http.MethodGet, "/en/sessions/new", nil) + req, err := http.NewRequest(http.MethodGet, "/sessions/new", nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -42,7 +42,7 @@ func TestAuthentication(t *testing.T) { } // Use no credentials to log in - req, err = http.NewRequest(http.MethodPost, "/en/sessions", nil) + req, err = http.NewRequest(http.MethodPost, "/sessions", nil) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -56,7 +56,7 @@ func TestAuthentication(t *testing.T) { } // Use good credentials to log in - req, err = http.NewRequest(http.MethodPost, "/en/sessions", strings.NewReader(data.Encode())) + req, err = http.NewRequest(http.MethodPost, "/sessions", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -75,8 +75,8 @@ func TestAuthentication(t *testing.T) { t.Error("No location header present") return } - if url.Path != "/en" { - t.Errorf("Expected location %s, received %s", "/en", url.Path) + if url.Path != "/" { + t.Errorf("Expected location %s, received %s", "/", url.Path) } }) } @@ -85,7 +85,7 @@ func TestRecoverNoEmailService(t *testing.T) { db := infrastructure.Connect(":memory:?cache=shared", 250) app := bootstrapApp(db, &infrastructure.NoEmail{}, afero.NewMemMapFs(), webserver.Config{}) - req, err := http.NewRequest(http.MethodGet, "/en/recover", nil) + req, err := http.NewRequest(http.MethodGet, "/recover", nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -127,7 +127,7 @@ func TestRecover(t *testing.T) { t.Run("Check that recover page is accessible", func(t *testing.T) { reset(2 * time.Hour) - req, err := http.NewRequest(http.MethodGet, "/en/recover", nil) + req, err := http.NewRequest(http.MethodGet, "/recover", nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -143,7 +143,7 @@ func TestRecover(t *testing.T) { t.Run("Check that not posting an email returns an error", func(t *testing.T) { reset(2 * time.Hour) - req, err := http.NewRequest(http.MethodPost, "/en/recover", nil) + req, err := http.NewRequest(http.MethodPost, "/recover", nil) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -180,7 +180,7 @@ func TestRecover(t *testing.T) { "email": {"unknown@example.com"}, } - req, err := http.NewRequest(http.MethodPost, "/en/recover", strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodPost, "/recover", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -202,7 +202,7 @@ func TestRecover(t *testing.T) { t.Run("Try to access the update password without the recovery ID", func(t *testing.T) { reset(2 * time.Hour) - req, err := http.NewRequest(http.MethodGet, "/en/reset-password", nil) + req, err := http.NewRequest(http.MethodGet, "/reset-password", nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -218,7 +218,7 @@ func TestRecover(t *testing.T) { t.Run("Check that posting an existing email sends a recovery email and resetting the password successfully redirects to the login page", func(t *testing.T) { reset(2 * time.Hour) - req, err := http.NewRequest(http.MethodPost, "/en/recover", strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodPost, "/recover", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -248,7 +248,7 @@ func TestRecover(t *testing.T) { "id": {adminUser.RecoveryUUID}, } - req, err = http.NewRequest(http.MethodPost, "/en/reset-password", strings.NewReader(data.Encode())) + req, err = http.NewRequest(http.MethodPost, "/reset-password", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -267,14 +267,14 @@ func TestRecover(t *testing.T) { t.Error("No location header present") return } - if expectedURL := "/en/sessions"; url.Path != expectedURL { + if expectedURL := "/sessions"; url.Path != expectedURL { t.Errorf("Expected location %s, received %s", expectedURL, url.Path) } // Try to access again to the reset password page with the same recovery ID leads to an error db.Where("email = ?", "admin@example.com").First(&adminUser) - req, err = http.NewRequest(http.MethodGet, "/en/reset-password", nil) + req, err = http.NewRequest(http.MethodGet, "/reset-password", nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -295,7 +295,7 @@ func TestRecover(t *testing.T) { t.Run("Check that using a timed out link returns an error", func(t *testing.T) { reset(0 * time.Hour) - req, err := http.NewRequest(http.MethodPost, "/en/recover", strings.NewReader(data.Encode())) + req, err := http.NewRequest(http.MethodPost, "/recover", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) @@ -315,7 +315,7 @@ func TestRecover(t *testing.T) { adminUser := model.User{} db.Where("email = ?", "admin@example.com").First(&adminUser) - req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/en/reset-password?id=%s", adminUser.RecoveryUUID), nil) + req, err = http.NewRequest(http.MethodGet, fmt.Sprintf("/reset-password?id=%s", adminUser.RecoveryUUID), nil) if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) } @@ -336,7 +336,7 @@ func TestRecover(t *testing.T) { "id": {adminUser.RecoveryUUID}, } - req, err = http.NewRequest(http.MethodPost, "/en/reset-password", strings.NewReader(data.Encode())) + req, err = http.NewRequest(http.MethodPost, "/reset-password", strings.NewReader(data.Encode())) req.Header.Add("Content-Type", "application/x-www-form-urlencoded") if err != nil { t.Fatalf("Unexpected error: %v", err.Error()) diff --git a/internal/webserver/controller/auth/login.go b/internal/webserver/controller/auth/login.go index 71dbe2c3..2dc9a6c0 100644 --- a/internal/webserver/controller/auth/login.go +++ b/internal/webserver/controller/auth/login.go @@ -10,9 +10,8 @@ import ( func (a *Controller) Login(c *fiber.Ctx) error { resetPassword := fmt.Sprintf( - "%s/%s/reset-password", + "%s/reset-password", c.Locals("fqdn").(string), - c.Params("lang"), ) msg := "" diff --git a/internal/webserver/controller/auth/request.go b/internal/webserver/controller/auth/request.go index 589d16cf..874c4c9c 100644 --- a/internal/webserver/controller/auth/request.go +++ b/internal/webserver/controller/auth/request.go @@ -36,20 +36,18 @@ func (a *Controller) Request(c *fiber.Ctx) error { } recoveryLink := fmt.Sprintf( - "%s/%s/reset-password?id=%s", + "%s/reset-password?id=%s", c.Locals("fqdn"), - c.Params("lang"), user.RecoveryUUID, ) c.Render("auth/email", fiber.Map{ - "Lang": c.Params("lang"), "RecoveryLink": recoveryLink, "RecoveryTimeout": strconv.FormatFloat(a.config.RecoveryTimeout.Hours(), 'f', -1, 64), }) return a.sender.Send( c.FormValue("email"), - a.printers[c.Params("lang")].Sprintf("Password recovery request"), + a.printers[c.Locals("Lang").(string)].Sprintf("Password recovery request"), string(c.Response().Body()), ) } diff --git a/internal/webserver/controller/auth/reset-password.go b/internal/webserver/controller/auth/reset-password.go index 12d90812..33a32005 100644 --- a/internal/webserver/controller/auth/reset-password.go +++ b/internal/webserver/controller/auth/reset-password.go @@ -1,7 +1,6 @@ package auth import ( - "fmt" "time" "github.com/gofiber/fiber/v2" @@ -45,7 +44,7 @@ func (a *Controller) UpdatePassword(c *fiber.Ctx) error { return fiber.ErrInternalServerError } - return c.Redirect(fmt.Sprintf("/%s/sessions", c.Params("lang"))) + return c.Redirect("/sessions") } func (a *Controller) validateRecoveryAccess(recoveryUuid string) (*model.User, error) { diff --git a/internal/webserver/controller/auth/signin.go b/internal/webserver/controller/auth/signin.go index 9be3d3df..c3453ddd 100644 --- a/internal/webserver/controller/auth/signin.go +++ b/internal/webserver/controller/auth/signin.go @@ -1,7 +1,6 @@ package auth import ( - "fmt" "strings" "time" @@ -51,7 +50,7 @@ func (a *Controller) SignIn(c *fiber.Ctx) error { return c.Redirect(referer) } - return c.Redirect(fmt.Sprintf("/%s", c.Params("lang"))) + return c.Redirect("/") } func GenerateToken(c *fiber.Ctx, user *model.User, expiration time.Time, secret []byte) (string, error) { diff --git a/internal/webserver/controller/document/upload.go b/internal/webserver/controller/document/upload.go index d09cb2c1..5bda91bd 100644 --- a/internal/webserver/controller/document/upload.go +++ b/internal/webserver/controller/document/upload.go @@ -78,7 +78,7 @@ func (d *Controller) Upload(c *fiber.Ctx) error { return internalServerErrorStatus } - return c.Redirect(fmt.Sprintf("/%s/documents/%s?success=1", c.Params("lang"), slug)) + return c.Redirect(fmt.Sprintf("/documents/%s?success=1", slug)) } func fileToBytes(fileHeader *multipart.FileHeader) ([]byte, error) { diff --git a/internal/webserver/controller/user/create.go b/internal/webserver/controller/user/create.go index caf9e4f3..b639567b 100644 --- a/internal/webserver/controller/user/create.go +++ b/internal/webserver/controller/user/create.go @@ -1,7 +1,6 @@ package user import ( - "fmt" "strconv" "strings" @@ -47,5 +46,5 @@ func (u *Controller) Create(c *fiber.Ctx) error { return fiber.ErrInternalServerError } - return c.Redirect(fmt.Sprintf("/%s/users", c.Params("lang"))) + return c.Redirect("/users") } diff --git a/internal/webserver/document_detail_test.go b/internal/webserver/document_detail_test.go index ee291b18..fbfbf417 100644 --- a/internal/webserver/document_detail_test.go +++ b/internal/webserver/document_detail_test.go @@ -18,10 +18,10 @@ func TestDocumentAndRead(t *testing.T) { url string expectedStatus int }{ - {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha/read", http.StatusOK}, - {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--2/read", http.StatusOK}, - {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--3/read", http.StatusOK}, - {"/en/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, + {"/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha/read", http.StatusOK}, + {"/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--2/read", http.StatusOK}, + {"/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha--3/read", http.StatusOK}, + {"/documents/miguel-de-cervantes-y-saavedra-don-quijote-de-la-mancha", http.StatusOK}, } for _, tcase := range cases { diff --git a/internal/webserver/embedded/views/auth/edit-password.html b/internal/webserver/embedded/views/auth/edit-password.html index e46db62f..2788fb46 100644 --- a/internal/webserver/embedded/views/auth/edit-password.html +++ b/internal/webserver/embedded/views/auth/edit-password.html @@ -1,5 +1,5 @@

    {{t .Lang "Set new password"}}

    - +
    +

    {{t .Lang "Please sign in"}}

    diff --git a/internal/webserver/embedded/views/auth/recover.html b/internal/webserver/embedded/views/auth/recover.html index 072fe42d..b6aad61a 100644 --- a/internal/webserver/embedded/views/auth/recover.html +++ b/internal/webserver/embedded/views/auth/recover.html @@ -1,6 +1,6 @@

    {{t .Lang "Recover password"}}

    - +
    diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index 6282637c..aaed4398 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -7,7 +7,7 @@
    - {{t $lang "\"%s\" cover" .Document.Title}} @@ -23,7 +23,7 @@

    - + @@ -31,7 +31,7 @@

      {{t .Lang "Read"}} - + @@ -40,7 +40,7 @@

    {{.Document.Type}} - + @@ -50,14 +50,14 @@

    {{if and (.Session) (ne .Session.Name "")}} - +   {{t .Lang "Highlight"}} - + @@ -69,7 +69,7 @@

    {{t .Lang "Send to email unavailable, no email service configured"}}

    {{else}}
    - + @@ -152,11 +152,11 @@

    {{t $lang "Unknown author"}}

    {{t $lang "Other documents in collection \"%s\"" .Document.Series}}

    -
    +
    {{range $i, $doc := .SameSeries}}
    {{template "partials/related" dict "Lang" $lang "Document" $doc}} @@ -175,11 +175,11 @@

    -
    +
    {{range $i, $doc := .SameAuthors}}
    {{template "partials/related" dict "Lang" $lang "Document" $doc}} @@ -195,11 +195,11 @@

    {{t $lang "Other documents with similar subjects"}}

    -
    +
    {{range $i, $doc := .SameSubjects}}
    {{template "partials/related" dict "Lang" $lang "Document" $doc}} diff --git a/internal/webserver/embedded/views/index.html b/internal/webserver/embedded/views/index.html index a427de5a..c0c2522c 100644 --- a/internal/webserver/embedded/views/index.html +++ b/internal/webserver/embedded/views/index.html @@ -9,12 +9,12 @@

    {{t .Lang "Your highlights" }}

    {{if gt (len .Highlights) 0}} -
    +
    {{$lang := .Lang}} {{$emailSendingConfigured := .EmailSendingConfigured}} {{$session := .Session}} diff --git a/internal/webserver/embedded/views/layout.html b/internal/webserver/embedded/views/layout.html index 6d3831ef..fe26dbc0 100644 --- a/internal/webserver/embedded/views/layout.html +++ b/internal/webserver/embedded/views/layout.html @@ -27,7 +27,7 @@

    {{t .Lang "Users"}} {{else}} - + Coreander

    {{end}}

  • - + @@ -82,7 +82,7 @@
    Coreander
  • {{if eq .Session.Role 2}}
  • - + @@ -94,7 +94,7 @@
    Coreander
  • - + Coreander
  • {{else if not .DisableLoginLink}}
  • - {{t .Lang "Login"}} + {{t .Lang "Login"}}
  • {{end}}
    {{$lang := .Lang}} - {{$pathMinusLang := .PathMinusLang}} + {{$URLPath := .URLPath}} + {{$queryString := .QueryString}} {{range $i, $currentLang := .SupportedLanguages}} {{if eq $lang $currentLang}}
  • {{uppercase $currentLang}}
  • + {{else if eq $queryString ""}} +
  • {{uppercase $currentLang}}
  • {{else}} -
  • {{uppercase $currentLang}}
  • +
  • {{uppercase $currentLang}}
  • {{end}} {{end}}
    diff --git a/internal/webserver/embedded/views/partials/actions.html b/internal/webserver/embedded/views/partials/actions.html index a7825b7a..cf8b5f8d 100644 --- a/internal/webserver/embedded/views/partials/actions.html +++ b/internal/webserver/embedded/views/partials/actions.html @@ -1,5 +1,5 @@