diff --git a/internal/index/bleve_read.go b/internal/index/bleve_read.go index 3c704c1..53c7a99 100644 --- a/internal/index/bleve_read.go +++ b/internal/index/bleve_read.go @@ -10,11 +10,11 @@ import ( "github.com/blevesearch/bleve/v2/search/query" "github.com/gosimple/slug" "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/search" + "github.com/svera/coreander/v3/internal/result" ) // Search look for documents which match with the passed keywords. Returns a maximum documents, offset by -func (b *BleveIndexer) Search(keywords string, page, resultsPerPage int) (*search.PaginatedResult, error) { +func (b *BleveIndexer) Search(keywords string, page, resultsPerPage int) (result.Paginated[[]Document], error) { for _, prefix := range []string{"Authors:", "Series:", "Title:", "Subjects:", "\""} { if strings.HasPrefix(strings.Trim(keywords, " "), prefix) { query := bleve.NewQueryStringQuery(keywords) @@ -88,16 +88,17 @@ func (b *BleveIndexer) Search(keywords string, page, resultsPerPage int) (*searc return b.runPaginatedQuery(compound, page, resultsPerPage) } -func (b *BleveIndexer) runQuery(query query.Query, results int) ([]search.Document, error) { +func (b *BleveIndexer) runQuery(query query.Query, results int) ([]Document, error) { res, err := b.runPaginatedQuery(query, 0, results) if err != nil { return nil, err } - return res.Hits, nil + return res.Hits(), nil } -func (b *BleveIndexer) runPaginatedQuery(query query.Query, page, resultsPerPage int) (*search.PaginatedResult, error) { - var result search.PaginatedResult +func (b *BleveIndexer) runPaginatedQuery(query query.Query, page, resultsPerPage int) (result.Paginated[[]Document], error) { + var res result.Paginated[[]Document] + if page < 1 { page = 1 } @@ -107,31 +108,16 @@ func (b *BleveIndexer) runPaginatedQuery(query query.Query, page, resultsPerPage searchOptions.Fields = []string{"ID", "Slug", "Title", "Authors", "Description", "Year", "Words", "Series", "SeriesIndex", "Pages", "Type", "Subjects"} searchResult, err := b.idx.Search(searchOptions) if err != nil { - return nil, err + return result.Paginated[[]Document]{}, err } if searchResult.Total == 0 { - return &result, nil - } - totalPages := search.CalculateTotalPages(searchResult.Total, uint64(resultsPerPage)) - if totalPages < page { - page = totalPages - if page == 0 { - page = 1 - } - searchResult, err = b.idx.Search(searchOptions) - if err != nil { - return nil, err - } - } - result = search.PaginatedResult{ - Page: page, - TotalPages: totalPages, - TotalHits: int(searchResult.Total), - Hits: make([]search.Document, 0, len(searchResult.Hits)), + return res, nil } + docs := make([]Document, 0, len(searchResult.Hits)) + for _, val := range searchResult.Hits { - doc := search.Document{ + doc := Document{ ID: val.ID, Slug: val.Fields["Slug"].(string), Metadata: metadata.Metadata{ @@ -147,9 +133,15 @@ func (b *BleveIndexer) runPaginatedQuery(query query.Query, page, resultsPerPage Subjects: slicer(val.Fields["Subjects"]), }, } - result.Hits = append(result.Hits, doc) + docs = append(docs, doc) } - return &result, nil + + return result.NewPaginated[[]Document]( + resultsPerPage, + page, + int(searchResult.Total), + docs, + ), nil } // Count returns the number of indexed documents @@ -157,21 +149,20 @@ func (b *BleveIndexer) Count() (uint64, error) { return b.idx.DocCount() } -func (b *BleveIndexer) Document(slug string) (search.Document, error) { - doc := search.Document{} +func (b *BleveIndexer) Document(slug string) (Document, error) { query := bleve.NewTermQuery(slug) query.SetField("Slug") searchOptions := bleve.NewSearchRequest(query) searchOptions.Fields = []string{"ID", "Slug", "Title", "Authors", "Description", "Year", "Words", "Series", "SeriesIndex", "Pages", "Type", "Subjects"} searchResult, err := b.idx.Search(searchOptions) if err != nil { - return doc, err + return Document{}, err } if searchResult.Total == 0 { - return doc, fmt.Errorf("Document with slug %s not found", slug) + return Document{}, fmt.Errorf("Document with slug %s not found", slug) } - doc = search.Document{ + return Document{ ID: searchResult.Hits[0].ID, Slug: searchResult.Hits[0].Fields["Slug"].(string), Metadata: metadata.Metadata{ @@ -186,13 +177,11 @@ func (b *BleveIndexer) Document(slug string) (search.Document, error) { Type: searchResult.Hits[0].Fields["Type"].(string), Subjects: slicer(searchResult.Hits[0].Fields["Subjects"]), }, - } - - return doc, nil + }, nil } -func (b *BleveIndexer) Documents(IDs []string) ([]search.Document, error) { - docs := make([]search.Document, 0, len(IDs)) +func (b *BleveIndexer) Documents(IDs []string) (map[string]Document, error) { + docs := make(map[string]Document, len(IDs)) query := bleve.NewDocIDQuery(IDs) searchOptions := bleve.NewSearchRequest(query) searchOptions.Fields = []string{"ID", "Slug", "Title", "Authors", "Description", "Year", "Words", "Series", "SeriesIndex", "Pages", "Type", "Subjects"} @@ -202,9 +191,8 @@ func (b *BleveIndexer) Documents(IDs []string) ([]search.Document, error) { } for _, hit := range searchResult.Hits { - docs = append( - docs, - search.Document{ + docs[hit.ID] = + Document{ ID: hit.ID, Slug: hit.Fields["Slug"].(string), Metadata: metadata.Metadata{ @@ -219,8 +207,7 @@ func (b *BleveIndexer) Documents(IDs []string) ([]search.Document, error) { Type: hit.Fields["Type"].(string), Subjects: slicer(hit.Fields["Subjects"]), }, - }, - ) + } } return docs, nil @@ -228,10 +215,10 @@ func (b *BleveIndexer) Documents(IDs []string) ([]search.Document, error) { // SameSubjects returns an array of metadata of documents by other authors, different between each other, // which have similar subjects as the passed one and does not belong to the same collection -func (b *BleveIndexer) SameSubjects(slug string, quantity int) ([]search.Document, error) { +func (b *BleveIndexer) SameSubjects(slug string, quantity int) ([]Document, error) { doc, err := b.Document(slug) if err != nil { - return []search.Document{}, err + return []Document{}, err } subjectsCompoundQuery := bleve.NewDisjunctionQuery() @@ -254,7 +241,7 @@ func (b *BleveIndexer) SameSubjects(slug string, quantity int) ([]search.Documen } bq.AddMustNot(authorsCompoundQuery) - res := make([]search.Document, 0, quantity) + res := make([]Document, 0, quantity) for i := 0; i < quantity; i++ { doc, err := b.runQuery(bq, 1) if err != nil { @@ -277,10 +264,10 @@ func (b *BleveIndexer) SameSubjects(slug string, quantity int) ([]search.Documen // SameAuthors returns an array of metadata of documents by the same authors which // does not belong to the same collection -func (b *BleveIndexer) SameAuthors(slug string, quantity int) ([]search.Document, error) { +func (b *BleveIndexer) SameAuthors(slug string, quantity int) ([]Document, error) { doc, err := b.Document(slug) if err != nil { - return []search.Document{}, err + return []Document{}, err } authorsCompoundQuery := bleve.NewDisjunctionQuery() @@ -300,10 +287,10 @@ func (b *BleveIndexer) SameAuthors(slug string, quantity int) ([]search.Document } // SameSeries returns an array of metadata of documents in the same series -func (b *BleveIndexer) SameSeries(slug string, quantity int) ([]search.Document, error) { +func (b *BleveIndexer) SameSeries(slug string, quantity int) ([]Document, error) { doc, err := b.Document(slug) if err != nil { - return []search.Document{}, err + return []Document{}, err } bq := bleve.NewBooleanQuery() diff --git a/internal/index/bleve_test.go b/internal/index/bleve_test.go index 45bec47..f735705 100644 --- a/internal/index/bleve_test.go +++ b/internal/index/bleve_test.go @@ -8,7 +8,8 @@ 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/search" + "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v3/internal/webserver/model" ) func TestIndexAndSearch(t *testing.T) { @@ -42,7 +43,7 @@ func TestIndexAndSearch(t *testing.T) { if err != nil { t.Errorf("Error searching: %s", err.Error()) } - if !reflect.DeepEqual(*res, tcase.expectedResult) { + if !reflect.DeepEqual(res, tcase.expectedResult) { t.Errorf("Wrong result returned, expected %#v, got %#v", tcase.expectedResult, res) } }) @@ -54,7 +55,7 @@ type testCase struct { filename string mockedMeta metadata.Metadata search string - expectedResult search.PaginatedResult + expectedResult result.Paginated[[]index.Document] } func testCases() []testCase { @@ -70,11 +71,11 @@ func testCases() []testCase { Subjects: []string{"History", "Middle age"}, }, "perez", - search.PaginatedResult{ - Page: 1, - TotalPages: 1, - TotalHits: 1, - Hits: []search.Document{ + result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + 1, + 1, + []index.Document{ { ID: "book1.epub", Slug: "perez-test-a", @@ -86,7 +87,7 @@ func testCases() []testCase { }, }, }, - }, + ), }, { "Look for a term without circumflex accent must return circumflexed results", @@ -99,11 +100,11 @@ func testCases() []testCase { Subjects: []string{""}, }, "benoit", - search.PaginatedResult{ - Page: 1, - TotalPages: 1, - TotalHits: 1, - Hits: []search.Document{ + result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + 1, + 1, + []index.Document{ { ID: "book2.epub", Slug: "benoit-test-b", @@ -115,7 +116,7 @@ func testCases() []testCase { }, }, }, - }, + ), }, { "Look for several, not exact terms must return a result with all those terms, even if there is something in between", @@ -128,11 +129,11 @@ func testCases() []testCase { Subjects: []string{""}, }, "clifford simak", - search.PaginatedResult{ - Page: 1, - TotalPages: 1, - TotalHits: 1, - Hits: []search.Document{ + result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + 1, + 1, + []index.Document{ { ID: "book3.epub", Slug: "clifford-d-simak-test-c", @@ -144,7 +145,7 @@ func testCases() []testCase { }, }, }, - }, + ), }, { "Look for several, not exact terms must return a result with all those terms, even if there is something in between", @@ -157,11 +158,11 @@ func testCases() []testCase { Subjects: []string{""}, }, "james ellroy", - search.PaginatedResult{ - Page: 1, - TotalPages: 1, - TotalHits: 1, - Hits: []search.Document{ + result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + 1, + 1, + []index.Document{ { ID: "book4.epub", Slug: "james-ellroy-test-d", @@ -172,7 +173,7 @@ func testCases() []testCase { }, }, }, - }, + ), }, { "Look for several, not exact terms with multiple leading, trailing and in-between spaces must return a result with all those terms, even if there is something in between", @@ -185,11 +186,11 @@ func testCases() []testCase { Subjects: []string{""}, }, " james ellroy ", - search.PaginatedResult{ - Page: 1, - TotalPages: 1, - TotalHits: 1, - Hits: []search.Document{ + result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + 1, + 1, + []index.Document{ { ID: "book5.epub", Slug: "james-ellroy-test-e", @@ -200,7 +201,7 @@ func testCases() []testCase { }, }, }, - }, + ), }, } } diff --git a/internal/index/bleve_write.go b/internal/index/bleve_write.go index 0be64e5..2d06766 100644 --- a/internal/index/bleve_write.go +++ b/internal/index/bleve_write.go @@ -10,7 +10,6 @@ import ( "github.com/gosimple/slug" "github.com/spf13/afero" "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/search" ) // AddFile adds a file to the index @@ -82,7 +81,7 @@ func (b *BleveIndexer) AddLibrary(fs afero.Fs, batchSize int) error { func (b *BleveIndexer) createDocument(meta metadata.Metadata, fullPath string, batchSlugs map[string]struct{}) DocumentWrite { document := DocumentWrite{ - Document: search.Document{ + Document: Document{ Metadata: meta, }, SeriesEq: strings.ReplaceAll(slug.Make(meta.Series), "-", ""), diff --git a/internal/index/document.go b/internal/index/document.go index ed5d314..9ce74e1 100644 --- a/internal/index/document.go +++ b/internal/index/document.go @@ -1,11 +1,18 @@ package index -import "github.com/svera/coreander/v3/internal/search" +import "github.com/svera/coreander/v3/internal/metadata" + +type Document struct { + metadata.Metadata + ID string + Slug string + Highlighted bool +} // DocumentWrite is an extension to Document that is used only when writing to the index, // as some of its fields are only used to perform searches and not returned type DocumentWrite struct { - search.Document + Document AuthorsEq []string SeriesEq string SubjectsEq []string diff --git a/internal/model/highlight_repository.go b/internal/model/highlight_repository.go deleted file mode 100644 index 4381f09..0000000 --- a/internal/model/highlight_repository.go +++ /dev/null @@ -1,72 +0,0 @@ -package model - -import ( - "log" - - "github.com/svera/coreander/v3/internal/search" - "golang.org/x/exp/slices" - "gorm.io/gorm" - "gorm.io/gorm/clause" -) - -type HighlightRepository struct { - DB *gorm.DB -} - -func (u *HighlightRepository) Highlights(userID int, page int, resultsPerPage int) (search.PaginatedResult, error) { - highlights := []string{} - var total int64 - - result := u.DB.Scopes(Paginate(page, resultsPerPage)).Table("highlights").Select("path").Where("user_id = ?", userID).Order("created_at DESC").Pluck("path", &highlights) - if result.Error != nil { - log.Printf("error listing highlights: %s\n", result.Error) - } - u.DB.Table("highlights").Where("user_id = ?", userID).Count(&total) - - paginatedResult := search.PaginatedResult{ - Page: page, - Hits: make([]search.Document, len(highlights)), - TotalHits: int(total), - TotalPages: search.CalculateTotalPages(uint64(total), ResultsPerPage), - } - - for i, path := range highlights { - paginatedResult.Hits[i].ID = path - } - - return paginatedResult, result.Error -} - -func (u *HighlightRepository) Highlighted(userID int, documents []search.Document) []search.Document { - highlights := make([]string, 0, len(documents)) - paths := make([]string, 0, len(documents)) - for _, path := range documents { - paths = append(paths, path.ID) - } - u.DB.Table("highlights").Where( - "user_id = ? AND path IN (?)", - userID, - paths, - ).Pluck("path", &highlights) - for i, doc := range documents { - documents[i].Highlighted = slices.Contains(highlights, doc.ID) - } - - return documents -} - -func (u *HighlightRepository) Highlight(userID int, documentPath string) error { - highlight := Highlight{ - UserID: userID, - Path: documentPath, - } - return u.DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&highlight).Error -} - -func (u *HighlightRepository) Remove(userID int, documentPath string) error { - highlight := Highlight{ - UserID: userID, - Path: documentPath, - } - return u.DB.Delete(&highlight).Error -} diff --git a/internal/result/paginated.go b/internal/result/paginated.go new file mode 100644 index 0000000..be09197 --- /dev/null +++ b/internal/result/paginated.go @@ -0,0 +1,52 @@ +package result + +import ( + "math" +) + +// Paginated holds the result of a search request, as well as some related metadata +type Paginated[T any] struct { + maxResultsPerPage int + page int + totalPages int + hits T + totalHits int +} + +func NewPaginated[T any](maxResultsPerPage, page, totalHits int, hits T) Paginated[T] { + return Paginated[T]{ + maxResultsPerPage: maxResultsPerPage, + page: page, + totalHits: totalHits, + hits: hits, + } +} + +func (P Paginated[T]) MaxResultsPerPage() int { + return P.maxResultsPerPage +} + +func (P Paginated[T]) Page() int { + return P.page +} + +func (P Paginated[T]) Hits() T { + return P.hits +} + +func (P Paginated[T]) TotalHits() int { + return P.totalHits +} + +func (P Paginated[T]) TotalPages() int { + if P.totalPages != 0 { + return P.totalPages + } + + if P.maxResultsPerPage == 0 { + return 0 + } + + P.totalPages = int(math.Ceil(float64(P.totalHits) / float64(P.maxResultsPerPage))) + return P.totalPages +} diff --git a/internal/search/result.go b/internal/search/result.go deleted file mode 100644 index 21d3854..0000000 --- a/internal/search/result.go +++ /dev/null @@ -1,26 +0,0 @@ -package search - -import ( - "math" - - "github.com/svera/coreander/v3/internal/metadata" -) - -type Document struct { - metadata.Metadata - ID string - Slug string - Highlighted bool -} - -// PaginatedResult holds the result of a search request, as well as some related metadata -type PaginatedResult struct { - Page int - TotalPages int - Hits []Document - TotalHits int -} - -func CalculateTotalPages(total, resultsPerPage uint64) int { - return int(math.Ceil(float64(total) / float64(resultsPerPage))) -} diff --git a/internal/webserver/authentication_test.go b/internal/webserver/authentication_test.go index eab7fe8..0d3927e 100644 --- a/internal/webserver/authentication_test.go +++ b/internal/webserver/authentication_test.go @@ -9,8 +9,8 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/model" ) func TestAuthentication(t *testing.T) { diff --git a/internal/webserver/controller.go b/internal/webserver/controller.go index 4213297..09587f3 100644 --- a/internal/webserver/controller.go +++ b/internal/webserver/controller.go @@ -9,14 +9,14 @@ import ( jwtware "github.com/gofiber/jwt/v3" "github.com/spf13/afero" "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/infrastructure" "github.com/svera/coreander/v3/internal/metadata" - "github.com/svera/coreander/v3/internal/model" "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/infrastructure" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" "gorm.io/gorm" ) diff --git a/internal/webserver/controller/auth/controller.go b/internal/webserver/controller/auth/controller.go index 0c8de93..7ff3529 100644 --- a/internal/webserver/controller/auth/controller.go +++ b/internal/webserver/controller/auth/controller.go @@ -5,7 +5,7 @@ import ( "time" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/model" "golang.org/x/text/message" ) diff --git a/internal/webserver/controller/auth/login.go b/internal/webserver/controller/auth/login.go index 27084b4..a1ae5e2 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/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" ) func (a *Controller) Login(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/auth/recover.go b/internal/webserver/controller/auth/recover.go index 58b573b..9f17c79 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/infrastructure" + "github.com/svera/coreander/v3/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 b85fbd2..c60a08d 100644 --- a/internal/webserver/controller/auth/request.go +++ b/internal/webserver/controller/auth/request.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" ) func (a *Controller) Request(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/auth/signin.go b/internal/webserver/controller/auth/signin.go index 83bae58..ae77d61 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/model" + "github.com/svera/coreander/v3/internal/webserver/model" ) // Signs in a user and gives them a JWT. diff --git a/internal/webserver/controller/auth/update-password.go b/internal/webserver/controller/auth/update-password.go index 165f6a9..c533d2b 100644 --- a/internal/webserver/controller/auth/update-password.go +++ b/internal/webserver/controller/auth/update-password.go @@ -5,8 +5,8 @@ import ( "time" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/model" ) func (a *Controller) UpdatePassword(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/document/controller.go b/internal/webserver/controller/document/controller.go index b506ee3..2475f55 100644 --- a/internal/webserver/controller/document/controller.go +++ b/internal/webserver/controller/document/controller.go @@ -2,8 +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/search" + "github.com/svera/coreander/v3/internal/result" ) const relatedDocuments = 4 @@ -15,19 +16,19 @@ type Sender interface { // IdxReaderWriter defines a set of reading and writing operations over an index type IdxReaderWriter interface { - Search(keywords string, page, resultsPerPage int) (*search.PaginatedResult, error) + Search(keywords string, page, resultsPerPage int) (result.Paginated[[]index.Document], error) Count() (uint64, error) Close() error - Document(Slug string) (search.Document, error) - Documents(IDs []string) ([]search.Document, error) - SameSubjects(slug string, quantity int) ([]search.Document, error) - SameAuthors(slug string, quantity int) ([]search.Document, error) - SameSeries(slug string, quantity int) ([]search.Document, error) + Document(Slug string) (index.Document, error) + SameSubjects(slug string, quantity int) ([]index.Document, error) + SameAuthors(slug string, quantity int) ([]index.Document, error) + SameSeries(slug string, quantity int) ([]index.Document, error) RemoveFile(file string) error } type highlightsRepository interface { - Highlighted(userID int, documents []search.Document) []search.Document + Highlighted(userID int, doc index.Document) index.Document + HighlightedPaginatedResult(userID int, results result.Paginated[[]index.Document]) result.Paginated[[]index.Document] } type Config struct { diff --git a/internal/webserver/controller/document/delete.go b/internal/webserver/controller/document/delete.go index 2335f62..9a4be8b 100644 --- a/internal/webserver/controller/document/delete.go +++ b/internal/webserver/controller/document/delete.go @@ -6,8 +6,8 @@ import ( "path/filepath" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" ) func (d *Controller) Delete(c *fiber.Ctx) error { diff --git a/internal/webserver/controller/document/detail.go b/internal/webserver/controller/document/detail.go index 082e64c..c340823 100644 --- a/internal/webserver/controller/document/detail.go +++ b/internal/webserver/controller/document/detail.go @@ -7,8 +7,7 @@ import ( "strings" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/search" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" ) @@ -55,7 +54,7 @@ func (d *Controller) Detail(c *fiber.Ctx) error { } if session.ID > 0 { - document = d.hlRepository.Highlighted(int(session.ID), []search.Document{document})[0] + document = d.hlRepository.Highlighted(int(session.ID), document) } return c.Render("document", fiber.Map{ diff --git a/internal/webserver/controller/document/search.go b/internal/webserver/controller/document/search.go index 8890784..99effda 100644 --- a/internal/webserver/controller/document/search.go +++ b/internal/webserver/controller/document/search.go @@ -4,11 +4,12 @@ import ( "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" - "github.com/svera/coreander/v3/internal/search" - "github.com/svera/coreander/v3/internal/webserver/controller" + "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/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v3/internal/webserver/view" ) func (d *Controller) Search(c *fiber.Ctx) error { @@ -27,7 +28,7 @@ func (d *Controller) Search(c *fiber.Ctx) error { d.config.WordsPerMinute = session.WordsPerMinute } - var searchResults *search.PaginatedResult + var searchResults result.Paginated[[]index.Document] if keywords := c.Query("search"); keywords != "" { if searchResults, err = d.idx.Search(keywords, page, model.ResultsPerPage); err != nil { @@ -35,14 +36,13 @@ func (d *Controller) Search(c *fiber.Ctx) error { } if session.ID > 0 { - searchResults.Hits = d.hlRepository.Highlighted(int(session.ID), searchResults.Hits) + searchResults = d.hlRepository.HighlightedPaginatedResult(int(session.ID), searchResults) } return c.Render("results", fiber.Map{ "Keywords": keywords, - "Results": searchResults.Hits, - "Total": searchResults.TotalHits, - "Paginator": controller.Pagination(model.MaxPagesNavigator, searchResults.TotalPages, searchResults.Page, map[string]string{"search": keywords}), + "Results": searchResults, + "Paginator": view.Pagination(model.MaxPagesNavigator, searchResults, map[string]string{"search": keywords}), "Title": "Search results", "EmailSendingConfigured": emailSendingConfigured, "EmailFrom": d.sender.From(), diff --git a/internal/webserver/controller/highlight/controller.go b/internal/webserver/controller/highlight/controller.go index 4fcf10a..d93b947 100644 --- a/internal/webserver/controller/highlight/controller.go +++ b/internal/webserver/controller/highlight/controller.go @@ -1,28 +1,22 @@ package highlight import ( - "github.com/svera/coreander/v3/internal/model" - "github.com/svera/coreander/v3/internal/search" + "github.com/svera/coreander/v3/internal/index" + "github.com/svera/coreander/v3/internal/result" + "github.com/svera/coreander/v3/internal/webserver/model" ) type highlightsRepository interface { - Highlights(userID int, page int, resultsPerPage int) (search.PaginatedResult, error) + Highlights(userID int, page int, resultsPerPage int) (result.Paginated[[]string], error) Highlight(userID int, documentPath string) error Remove(userID int, documentPath string) error - Highlighted(userID int, documents []search.Document) []search.Document + Highlighted(userID int, documents index.Document) index.Document } // IdxReaderWriter defines a set of reading and writing operations over an index type IdxReaderWriter interface { - Search(keywords string, page, resultsPerPage int) (*search.PaginatedResult, error) - Count() (uint64, error) - Close() error - Document(Slug string) (search.Document, error) - Documents(IDs []string) ([]search.Document, error) - SameSubjects(slug string, quantity int) ([]search.Document, error) - SameAuthors(slug string, quantity int) ([]search.Document, error) - SameSeries(slug string, quantity int) ([]search.Document, error) - RemoveFile(file string) error + Document(Slug string) (index.Document, error) + Documents(IDs []string) (map[string]index.Document, error) } type usersRepository interface { diff --git a/internal/webserver/controller/highlight/highlights.go b/internal/webserver/controller/highlight/highlights.go index 7c9cf6c..1a958a7 100644 --- a/internal/webserver/controller/highlight/highlights.go +++ b/internal/webserver/controller/highlight/highlights.go @@ -4,10 +4,12 @@ import ( "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" - "github.com/svera/coreander/v3/internal/webserver/controller" + "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/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v3/internal/webserver/view" ) func (h *Controller) Highlights(c *fiber.Ctx) error { @@ -39,19 +41,28 @@ func (h *Controller) Highlights(c *fiber.Ctx) error { if err != nil { return fiber.ErrInternalServerError } - for i, highlight := range highlights.Hits { - docs, err := h.idx.Documents([]string{highlight.ID}) - if err != nil { - return fiber.ErrInternalServerError - } - highlights.Hits[i] = docs[0] - highlights.Hits[i].Highlighted = true + + docs, err := h.idx.Documents(highlights.Hits()) + if err != nil { + return fiber.ErrInternalServerError } + docsSortedByHighlightedDate := make([]index.Document, len(docs)) + for i, path := range highlights.Hits() { + docsSortedByHighlightedDate[i] = docs[path] + docsSortedByHighlightedDate[i].Highlighted = true + } + + paginatedResults := result.NewPaginated[[]index.Document]( + model.ResultsPerPage, + page, + highlights.TotalHits(), + docsSortedByHighlightedDate, + ) + return c.Render("highlights", fiber.Map{ - "Results": highlights.Hits, - "Total": highlights.TotalHits, - "Paginator": controller.Pagination(model.MaxPagesNavigator, highlights.TotalPages, page, nil), + "Results": paginatedResults, + "Paginator": view.Pagination(model.MaxPagesNavigator, paginatedResults, nil), "Title": "Highlights", "EmailSendingConfigured": emailSendingConfigured, "EmailFrom": h.sender.From(), diff --git a/internal/webserver/controller/root.go b/internal/webserver/controller/root.go index dda6611..c1062a5 100644 --- a/internal/webserver/controller/root.go +++ b/internal/webserver/controller/root.go @@ -7,12 +7,13 @@ import ( "golang.org/x/text/language" ) -func Root(c *fiber.Ctx) error { +func Root(c *fiber.Ctx, supportedLanguages []string) error { acceptHeader := c.Get(fiber.HeaderAcceptLanguage) - languageMatcher := language.NewMatcher([]language.Tag{ - language.English, - language.Spanish, - }) + tags := make([]language.Tag, len(supportedLanguages)) + for i, lang := range supportedLanguages { + tags[i] = language.Make(lang) + } + languageMatcher := language.NewMatcher(tags) t, _, _ := language.ParseAcceptLanguage(acceptHeader) tag, _, _ := languageMatcher.Match(t...) diff --git a/internal/webserver/controller/user/controller.go b/internal/webserver/controller/user/controller.go index c7cf308..4456c3b 100644 --- a/internal/webserver/controller/user/controller.go +++ b/internal/webserver/controller/user/controller.go @@ -2,12 +2,13 @@ package user import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/result" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" ) type usersRepository interface { - List(page int, resultsPerPage int) ([]model.User, error) + List(page int, resultsPerPage int) (result.Paginated[[]model.User], error) Total() int64 FindByUuid(uuid string) (*model.User, error) Create(user *model.User) error diff --git a/internal/webserver/controller/user/create.go b/internal/webserver/controller/user/create.go index 320ecb8..a105db1 100644 --- a/internal/webserver/controller/user/create.go +++ b/internal/webserver/controller/user/create.go @@ -6,8 +6,8 @@ import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/model" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/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 1779ed7..f469110 100644 --- a/internal/webserver/controller/user/delete.go +++ b/internal/webserver/controller/user/delete.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/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 df20cab..da9dbc3 100644 --- a/internal/webserver/controller/user/edit.go +++ b/internal/webserver/controller/user/edit.go @@ -2,8 +2,8 @@ package user import ( "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/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 38f7bf1..2718226 100644 --- a/internal/webserver/controller/user/list.go +++ b/internal/webserver/controller/user/list.go @@ -1,13 +1,12 @@ package user import ( - "math" "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" - "github.com/svera/coreander/v3/internal/webserver/controller" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" + "github.com/svera/coreander/v3/internal/webserver/view" ) // List list all users registered in the database @@ -22,14 +21,12 @@ func (u *Controller) List(c *fiber.Ctx) error { if err != nil { page = 1 } - totalRows := u.repository.Total() - totalPages := int(math.Ceil(float64(totalRows) / model.ResultsPerPage)) users, _ := u.repository.List(page, model.ResultsPerPage) return c.Render("users/index", fiber.Map{ "Title": "Users", - "Users": users, - "Paginator": controller.Pagination(model.MaxPagesNavigator, totalPages, page, map[string]string{}), + "Users": users.Hits(), + "Paginator": view.Pagination(model.MaxPagesNavigator, users, map[string]string{}), "Session": session, "Admins": u.repository.Admins(), }, "layout") diff --git a/internal/webserver/controller/user/update.go b/internal/webserver/controller/user/update.go index 3978e0d..4a5c30a 100644 --- a/internal/webserver/controller/user/update.go +++ b/internal/webserver/controller/user/update.go @@ -4,8 +4,8 @@ import ( "strconv" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/model" "github.com/svera/coreander/v3/internal/webserver/jwtclaimsreader" + "github.com/svera/coreander/v3/internal/webserver/model" ) // Update gathers information from the edit user form and updates user data diff --git a/internal/webserver/embedded/views/document.html b/internal/webserver/embedded/views/document.html index 66aa15a..0d9ae9a 100644 --- a/internal/webserver/embedded/views/document.html +++ b/internal/webserver/embedded/views/document.html @@ -111,14 +111,14 @@

{{t $lang "Unknown author"}}

{{range $i, $subject := .Document.Subjects}} {{$subjectTitle := t $lang "Search for more titles in %s" $subject}} - {{$subject}} {{end}}
{{ end }} {{if .Document.Description}} -
{{.Document.Description}}
+
{{.Document.Description}}
{{else}}
{{t $lang "No description available"}}
{{end}} diff --git a/internal/webserver/embedded/views/highlights.html b/internal/webserver/embedded/views/highlights.html index 82770cf..2bc53bc 100644 --- a/internal/webserver/embedded/views/highlights.html +++ b/internal/webserver/embedded/views/highlights.html @@ -1,8 +1,8 @@

{{t .Lang "%s highlights" .Session.Name}}

- {{if gt .Total 0}} -

{{t .Lang "%d highlighted documents" .Total }}

+ {{if gt .Results.TotalHits 0}} +

{{t .Lang "%d highlighted documents" .Results.TotalHits }}

{{else}}

{{t .Lang "No highlighted documents" }}

{{end}} diff --git a/internal/webserver/embedded/views/partials/docs-list.html b/internal/webserver/embedded/views/partials/docs-list.html index a62f82d..04bbe92 100644 --- a/internal/webserver/embedded/views/partials/docs-list.html +++ b/internal/webserver/embedded/views/partials/docs-list.html @@ -5,7 +5,7 @@ {{$wordsPerMinute := .WordsPerMinute}}
- {{if .Results}} {{range $i, $document := .Results}} + {{if .Results}} {{range $i, $document := .Results.Hits}}
@@ -55,15 +55,15 @@
{{t $lang "Unknown author"}}
{{range $i, $subject := $document.Subjects}} {{$subjectTitle := t $lang "Search for more titles in %s" $subject}} - {{$subject}} + {{$subject}} {{end}}
{{ end }} {{if $document.Description}} -
{{$document.Description}}
+
{{$document.Description}}
{{else}} -
{{t $lang "No description available"}}
+
{{t $lang "No description available"}}
{{end}} diff --git a/internal/webserver/embedded/views/results.html b/internal/webserver/embedded/views/results.html index 3e29ebd..24901ad 100644 --- a/internal/webserver/embedded/views/results.html +++ b/internal/webserver/embedded/views/results.html @@ -1,6 +1,6 @@
- {{template "partials/searchbox" .}} {{if gt .Total 0}} -

{{t .Lang "%d matches found" .Total }}

+ {{template "partials/searchbox" .}} {{if gt .Results.TotalHits 0}} +

{{t .Lang "%d matches found" .Results.TotalHits }}

{{else}}

{{t .Lang "No matches found" }}

{{end}} diff --git a/internal/webserver/highlights_test.go b/internal/webserver/highlights_test.go index aa0c5df..7f594d0 100644 --- a/internal/webserver/highlights_test.go +++ b/internal/webserver/highlights_test.go @@ -9,8 +9,8 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/model" ) func TestHighlights(t *testing.T) { diff --git a/internal/infrastructure/noemail.go b/internal/webserver/infrastructure/noemail.go similarity index 100% rename from internal/infrastructure/noemail.go rename to internal/webserver/infrastructure/noemail.go diff --git a/internal/infrastructure/smtp.go b/internal/webserver/infrastructure/smtp.go similarity index 100% rename from internal/infrastructure/smtp.go rename to internal/webserver/infrastructure/smtp.go diff --git a/internal/infrastructure/sqlite.go b/internal/webserver/infrastructure/sqlite.go similarity index 96% rename from internal/infrastructure/sqlite.go rename to internal/webserver/infrastructure/sqlite.go index dee0172..cc86d88 100644 --- a/internal/infrastructure/sqlite.go +++ b/internal/webserver/infrastructure/sqlite.go @@ -7,7 +7,7 @@ import ( "github.com/glebarez/sqlite" "github.com/google/uuid" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/model" "gorm.io/gorm" ) diff --git a/internal/infrastructure/template_engine.go b/internal/webserver/infrastructure/template_engine.go similarity index 100% rename from internal/infrastructure/template_engine.go rename to internal/webserver/infrastructure/template_engine.go diff --git a/internal/webserver/jwtclaimsreader/jwtclaimsreader.go b/internal/webserver/jwtclaimsreader/jwtclaimsreader.go index 701d3c0..caeee84 100644 --- a/internal/webserver/jwtclaimsreader/jwtclaimsreader.go +++ b/internal/webserver/jwtclaimsreader/jwtclaimsreader.go @@ -3,7 +3,7 @@ package jwtclaimsreader import ( "github.com/gofiber/fiber/v2" "github.com/golang-jwt/jwt/v4" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/model" ) func SessionData(c *fiber.Ctx) model.User { diff --git a/internal/model/highlight.go b/internal/webserver/model/highlight.go similarity index 100% rename from internal/model/highlight.go rename to internal/webserver/model/highlight.go diff --git a/internal/webserver/model/highlight_repository.go b/internal/webserver/model/highlight_repository.go new file mode 100644 index 0000000..a85d53d --- /dev/null +++ b/internal/webserver/model/highlight_repository.go @@ -0,0 +1,91 @@ +package model + +import ( + "log" + + "github.com/svera/coreander/v3/internal/index" + "github.com/svera/coreander/v3/internal/result" + "golang.org/x/exp/slices" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type HighlightRepository struct { + DB *gorm.DB +} + +func (u *HighlightRepository) Highlights(userID int, page int, resultsPerPage int) (result.Paginated[[]string], error) { + highlights := []string{} + var total int64 + + res := u.DB.Scopes(Paginate(page, resultsPerPage)).Table("highlights").Select("path").Where("user_id = ?", userID).Order("created_at DESC").Pluck("path", &highlights) + if res.Error != nil { + log.Printf("error listing highlights: %s\n", res.Error) + } + u.DB.Table("highlights").Where("user_id = ?", userID).Count(&total) + + return result.NewPaginated[[]string]( + resultsPerPage, + page, + int(total), + highlights, + ), res.Error +} + +func (u *HighlightRepository) HighlightedPaginatedResult(userID int, results result.Paginated[[]index.Document]) result.Paginated[[]index.Document] { + highlights := make([]string, 0, len(results.Hits())) + paths := make([]string, 0, len(results.Hits())) + documents := make([]index.Document, len(results.Hits())) + + for _, path := range results.Hits() { + paths = append(paths, path.ID) + } + u.DB.Table("highlights").Where( + "user_id = ? AND path IN (?)", + userID, + paths, + ).Pluck("path", &highlights) + + for i, doc := range results.Hits() { + documents[i] = doc + documents[i].Highlighted = slices.Contains(highlights, doc.ID) + } + + return result.NewPaginated[[]index.Document]( + ResultsPerPage, + results.Page(), + results.TotalHits(), + documents, + ) +} + +func (u *HighlightRepository) Highlighted(userID int, doc index.Document) index.Document { + var count int64 + + u.DB.Table("highlights").Where( + "user_id = ? AND path = ?", + userID, + doc.ID, + ).Count(&count) + + if count == 1 { + doc.Highlighted = true + } + return doc +} + +func (u *HighlightRepository) Highlight(userID int, documentPath string) error { + highlight := Highlight{ + UserID: userID, + Path: documentPath, + } + return u.DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&highlight).Error +} + +func (u *HighlightRepository) Remove(userID int, documentPath string) error { + highlight := Highlight{ + UserID: userID, + Path: documentPath, + } + return u.DB.Delete(&highlight).Error +} diff --git a/internal/model/paginate.go b/internal/webserver/model/paginate.go similarity index 100% rename from internal/model/paginate.go rename to internal/webserver/model/paginate.go diff --git a/internal/model/user.go b/internal/webserver/model/user.go similarity index 100% rename from internal/model/user.go rename to internal/webserver/model/user.go diff --git a/internal/model/user_repository.go b/internal/webserver/model/user_repository.go similarity index 78% rename from internal/model/user_repository.go rename to internal/webserver/model/user_repository.go index ca76496..d101738 100644 --- a/internal/model/user_repository.go +++ b/internal/webserver/model/user_repository.go @@ -6,6 +6,7 @@ import ( "fmt" "log" + "github.com/svera/coreander/v3/internal/result" "gorm.io/gorm" ) @@ -13,18 +14,28 @@ type UserRepository struct { DB *gorm.DB } -func (u *UserRepository) List(page int, resultsPerPage int) ([]User, error) { - users := []User{} - result := u.DB.Scopes(Paginate(page, resultsPerPage)).Order("email ASC").Find(&users) - if result.Error != nil { - log.Printf("error listing users: %s\n", result.Error) +func (u *UserRepository) List(page int, resultsPerPage int) (result.Paginated[[]User], error) { + var users []User + + res := u.DB.Scopes(Paginate(page, resultsPerPage)).Order("email ASC").Find(&users) + if res.Error != nil { + log.Printf("error listing users: %s\n", res.Error) } - return users, result.Error + + return result.NewPaginated[[]User]( + resultsPerPage, + page, + int(u.Total()), + users, + ), res.Error } func (u *UserRepository) Total() int64 { - var totalRows int64 - users := []User{} + var ( + totalRows int64 + users []User + ) + u.DB.Model(&users).Count(&totalRows) return totalRows } @@ -65,6 +76,7 @@ func (u *UserRepository) Admins() int64 { func (u *UserRepository) Delete(uuid string) error { var user User + result := u.DB.Where("uuid = ?", uuid).Delete(&user) if result.Error != nil && !errors.Is(result.Error, gorm.ErrRecordNotFound) { log.Printf("error deleting user: %s\n", result.Error) @@ -79,9 +91,8 @@ func Hash(s string) string { } func (u *UserRepository) find(field, value string) (*User, error) { - var ( - user User - ) + var user User + result := u.DB.Where(fmt.Sprintf("%s = ?", field), value).First(&user) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return nil, nil diff --git a/internal/webserver/routes.go b/internal/webserver/routes.go index ee1405c..2119fb5 100644 --- a/internal/webserver/routes.go +++ b/internal/webserver/routes.go @@ -76,6 +76,6 @@ func routes(app *fiber.App, controllers Controllers, supportedLanguages []string langGroup.Get("/read/:slug", controllers.Documents.Reader) app.Get("/", func(c *fiber.Ctx) error { - return controller.Root(c) + return controller.Root(c, supportedLanguages) }) } diff --git a/internal/webserver/search_test.go b/internal/webserver/search_test.go index 1ffaaab..b0185b4 100644 --- a/internal/webserver/search_test.go +++ b/internal/webserver/search_test.go @@ -12,8 +12,8 @@ import ( "github.com/gofiber/fiber/v2" "github.com/google/uuid" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/model" ) func TestSearch(t *testing.T) { diff --git a/internal/webserver/user_management_test.go b/internal/webserver/user_management_test.go index 29d9d19..69c47fd 100644 --- a/internal/webserver/user_management_test.go +++ b/internal/webserver/user_management_test.go @@ -11,8 +11,8 @@ import ( "github.com/PuerkitoBio/goquery" "github.com/gofiber/fiber/v2" "github.com/spf13/afero" - "github.com/svera/coreander/v3/internal/infrastructure" - "github.com/svera/coreander/v3/internal/model" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/model" ) func TestUserManagement(t *testing.T) { diff --git a/internal/webserver/controller/paginator.go b/internal/webserver/view/paginator.go similarity index 67% rename from internal/webserver/controller/paginator.go rename to internal/webserver/view/paginator.go index 11131e6..321480b 100644 --- a/internal/webserver/controller/paginator.go +++ b/internal/webserver/view/paginator.go @@ -1,9 +1,11 @@ -package controller +package view import ( "fmt" "net/url" "strings" + + "github.com/svera/coreander/v3/internal/result" ) // Page holds the URL of a results page, and if that page is the current one being shown @@ -19,38 +21,38 @@ type PagesNavigator struct { NextLink string } -func Pagination(size int, totalPages int, current int, params map[string]string) PagesNavigator { +func Pagination[T any](size int, results result.Paginated[T], params map[string]string) PagesNavigator { var nav PagesNavigator start := 1 end := size - if totalPages > size { + if results.TotalPages() > size { nav = PagesNavigator{ Pages: make(map[int]Page, size), } - if current > size/2 { - start = current - size/2 - end = (current + size/2) - if end > totalPages { - start = totalPages - size - end = totalPages + if results.Page() > size/2 { + start = results.Page() - size/2 + end = (results.Page() + size/2) + if end > results.TotalPages() { + start = results.TotalPages() - size + end = results.TotalPages() } } } else { nav = PagesNavigator{ - Pages: make(map[int]Page, totalPages), + Pages: make(map[int]Page, results.TotalPages()), } - end = totalPages + end = results.TotalPages() } for i := start; i <= end; i++ { p := Page{ Link: fmt.Sprintf("?%spage=%d", toQueryString(params), i), } - if i == current { + if i == results.Page() { p.IsCurrent = true if i > 1 { nav.PreviousLink = fmt.Sprintf("?%spage=%d", toQueryString(params), i-1) } - if i < totalPages { + if i < results.TotalPages() { nav.NextLink = fmt.Sprintf("?%spage=%d", toQueryString(params), i+1) } } diff --git a/internal/webserver/webserver.go b/internal/webserver/webserver.go index 0f9ae62..c7d2bc7 100644 --- a/internal/webserver/webserver.go +++ b/internal/webserver/webserver.go @@ -12,7 +12,7 @@ import ( "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/infrastructure" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" "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 e3f31e9..729a5f3 100644 --- a/internal/webserver/webserver_test.go +++ b/internal/webserver/webserver_test.go @@ -11,9 +11,9 @@ import ( "github.com/gofiber/fiber/v2" "github.com/spf13/afero" "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/infrastructure" "github.com/svera/coreander/v3/internal/metadata" "github.com/svera/coreander/v3/internal/webserver" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" "gorm.io/gorm" ) diff --git a/main.go b/main.go index 02873d8..aa2d657 100644 --- a/main.go +++ b/main.go @@ -12,9 +12,9 @@ import ( "github.com/spf13/afero" "github.com/svera/coreander/v3/internal/index" - "github.com/svera/coreander/v3/internal/infrastructure" "github.com/svera/coreander/v3/internal/metadata" "github.com/svera/coreander/v3/internal/webserver" + "github.com/svera/coreander/v3/internal/webserver/infrastructure" ) var version string = "unknown"