From b1236140b675b561a46a83455a0b0b15ea1045b7 Mon Sep 17 00:00:00 2001 From: Elias Kohout Date: Mon, 20 Jan 2025 21:56:58 +0100 Subject: [PATCH] clean up repositories; move viewmodel repo into own file; --- src/internal/app/app.go | 6 +- src/internal/app/index.go | 2 +- src/internal/app/upsearch.go | 2 +- src/internal/model/article.go | 14 +-- .../model/database/articlerepository.go | 116 ++---------------- .../database/articleviewmodelrepository.go | 114 +++++++++++++++++ .../model/database/documentrepository.go | 4 +- 7 files changed, 138 insertions(+), 120 deletions(-) create mode 100644 src/internal/model/database/articleviewmodelrepository.go diff --git a/src/internal/app/app.go b/src/internal/app/app.go index dbac387..5f4edc3 100644 --- a/src/internal/app/app.go +++ b/src/internal/app/app.go @@ -7,12 +7,14 @@ import ( ) type App struct { - articles *database.ArticleRepository + articles *database.ArticleRepository + articleVMs *database.ArticleViewModelRepository } func NewApp(db *sql.DB) *App { return &App{ - articles: &database.ArticleRepository{DB: db}, + articles: &database.ArticleRepository{DB: db}, + articleVMs: &database.ArticleViewModelRepository{DB: db}, } } diff --git a/src/internal/app/index.go b/src/internal/app/index.go index 777a074..f22d302 100644 --- a/src/internal/app/index.go +++ b/src/internal/app/index.go @@ -20,7 +20,7 @@ func (app *App) Index(w http.ResponseWriter, req *http.Request) { } // get articles - articleVMs, err := app.articles.AllArticleViewModels(int(limit), int(offset)) + articleVMs, err := app.articleVMs.All(int(limit), int(offset)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/src/internal/app/upsearch.go b/src/internal/app/upsearch.go index d6a21ed..603c92b 100644 --- a/src/internal/app/upsearch.go +++ b/src/internal/app/upsearch.go @@ -16,7 +16,7 @@ func (app *App) UpSearch(w http.ResponseWriter, req *http.Request) { } // get articles - articleVMs, err := app.articles.SearchArticleViewModel(searchTerms) + articleVMs, err := app.articleVMs.Search(searchTerms) if err != nil { // treat as no result http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/src/internal/model/article.go b/src/internal/model/article.go index 88bbf5e..1095bbd 100644 --- a/src/internal/model/article.go +++ b/src/internal/model/article.go @@ -45,7 +45,7 @@ type ArticlePageViewModel struct { // TODO docstring func (a *Article) ViewModel() *ArticleViewModel { - var summary string + var summary string if len(a.Content) > 200 { summary = a.Content[:200] } else { @@ -59,16 +59,16 @@ func (a *Article) ViewModel() *ArticleViewModel { } return &ArticleViewModel{ - Id: a.Id, - Title: a.Title, - PublishDate: a.PublishDate.Local().Format("02.01.2006"), - ShortSource: short_url, - Summary: summary, + Id: a.Id, + Title: a.Title, + PublishDate: a.PublishDate.Local().Format("02.01.2006"), + ShortSource: short_url, + Summary: summary, } } func (a *Article) PageViewModel() *ArticlePageViewModel { - summary := "N/A" + summary := "N/A" short_url := "" parsedURL, err := url.Parse(a.SourceUrl) diff --git a/src/internal/model/database/articlerepository.go b/src/internal/model/database/articlerepository.go index c2ee7a2..73dc70a 100644 --- a/src/internal/model/database/articlerepository.go +++ b/src/internal/model/database/articlerepository.go @@ -3,7 +3,6 @@ package database import ( "crowsnest/internal/model" "database/sql" - "net/url" "strings" ) @@ -13,7 +12,7 @@ type ArticleRepository struct { // Gets all the article objects from the database. This may throw an error if // the connection to the database fails. -func (m *ArticleRepository) All(limit int, offset int) ([]model.Article, error) { +func (m *ArticleRepository) All(limit int, offset int) ([]*model.Article, error) { stmt := ` SELECT id, title, sourceUrl, content, publishDate, fetchDate FROM articles @@ -25,9 +24,9 @@ func (m *ArticleRepository) All(limit int, offset int) ([]model.Article, error) return nil, err } - articles := []model.Article{} + articles := []*model.Article{} for rows.Next() { - a := model.Article{} + a := &model.Article{} err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate) if err != nil { return nil, err @@ -43,55 +42,6 @@ func (m *ArticleRepository) All(limit int, offset int) ([]model.Article, error) return articles, nil } -func (m *ArticleRepository) AllArticleViewModels(limit int, offset int) ([]*model.ArticleViewModel, error) { - stmt := ` - SELECT a.id, a.title, a.sourceUrl, a.publishDate, d.summary - FROM articles a JOIN documents d ON a.document_id = d.id - ORDER BY a.publishDate DESC - LIMIT $1 OFFSET $2 - ` - rows, err := m.DB.Query(stmt, limit, offset) - if err != nil { - return nil, err - } - - articleVMs := []*model.ArticleViewModel{} - var sourceUrl string - for rows.Next() { - a := model.ArticleViewModel{} - - err := rows.Scan(&a.Id, &a.Title, &sourceUrl, &a.PublishDate, &a.Summary) - if err != nil { - return nil, err - } - - // summary - if a.Summary == "" { - a.Summary = "N/A" - } - - // short url - parsedURL, err := url.Parse(sourceUrl) - if err == nil { - a.ShortSource = parsedURL.Hostname() - } else { - a.ShortSource = "" - } - - // ai summary always false - a.AiSummarized = false - - articleVMs = append(articleVMs, &a) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - return articleVMs, nil - -} - // Counts all articles in the database. This may throw an error if the // connection to the database fails. func (m *ArticleRepository) CountAll() (uint, error) { @@ -110,58 +60,7 @@ func (m *ArticleRepository) CountAll() (uint, error) { // Will use the full-text search features of the underlying database to search // articles for a given search query. This may fail if the connection to the // database fails. -func (m *ArticleRepository) SearchArticleViewModel(query string) ([]*model.ArticleViewModel, error) { - stmt := ` - SELECT a.id, a.title, a.sourceUrl, a.publishDate, d.summary - FROM articles a JOIN documents d ON a.document_id = d.id - WHERE to_tsvector('german', d.content) @@ to_tsquery('german', $1) - ORDER BY ts_rank(to_tsvector('german', d.content), to_tsquery('german', $1)) DESC - LIMIT 10 - ` - - query = strings.Join(strings.Split(strings.TrimSpace(query), " "), " | ") - rows, err := m.DB.Query(stmt, query) - if err != nil { - return nil, err - } - - articleVMs := []*model.ArticleViewModel{} - for rows.Next() { - a := &model.ArticleViewModel{} - var sourceUrl string - err := rows.Scan(&a.Id, &a.Title, &sourceUrl, &a.PublishDate, &a.Summary) - if err != nil { - return nil, err - } - // summary - if a.Summary == "" { - a.Summary = "N/A" - } - - // short url - parsedURL, err := url.Parse(sourceUrl) - if err == nil { - a.ShortSource = parsedURL.Hostname() - } else { - a.ShortSource = "" - } - - // ai summary always false - a.AiSummarized = false - - articleVMs = append(articleVMs, a) - } - - if err = rows.Err(); err != nil { - return nil, err - } - return articleVMs, nil -} - -// Will use the full-text search features of the underlying database to search -// articles for a given search query. This may fail if the connection to the -// database fails. -func (m *ArticleRepository) Search(query string) ([]model.Article, error) { +func (m *ArticleRepository) Search(query string) ([]*model.Article, error) { stmt := ` SELECT a.id, a.title, a.sourceurl, a.content, a.publishdate, a.fetchDate FROM articles a JOIN documents d ON a.document_id = d.id @@ -176,9 +75,9 @@ func (m *ArticleRepository) Search(query string) ([]model.Article, error) { return nil, err } - articles := []model.Article{} + articles := []*model.Article{} for rows.Next() { - a := model.Article{} + a := &model.Article{} err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate) if err != nil { return nil, err @@ -224,6 +123,9 @@ func (m *ArticleRepository) Insert(a *model.Article) error { return err } +// Update an article in the database. Will use the id that is set in the article +// object as an reference to the database row. This may throw an error if the +// connection to database fails. func (m *ArticleRepository) Update(a *model.Article) error { stmt := `UPDATE articles SET title = $1, sourceUrl = $2, content = $4, publishDate = $5, fetchDate = $6 diff --git a/src/internal/model/database/articleviewmodelrepository.go b/src/internal/model/database/articleviewmodelrepository.go new file mode 100644 index 0000000..98aebb8 --- /dev/null +++ b/src/internal/model/database/articleviewmodelrepository.go @@ -0,0 +1,114 @@ +package database + +import ( + "crowsnest/internal/model" + "database/sql" + "net/url" + "strings" +) + +type ArticleViewModelRepository struct { + DB *sql.DB +} + +// Gets all the article as view models objects from the database. This may throw +// an error if the connection to the database fails. +func (m *ArticleViewModelRepository) All(limit int, offset int) ([]*model.ArticleViewModel, error) { + stmt := ` + SELECT a.id, a.title, a.sourceUrl, a.publishDate, d.summary + FROM articles a JOIN documents d ON a.document_id = d.id + ORDER BY a.publishDate DESC + LIMIT $1 OFFSET $2 + ` + rows, err := m.DB.Query(stmt, limit, offset) + if err != nil { + return nil, err + } + + articleVMs := []*model.ArticleViewModel{} + var sourceUrl string + for rows.Next() { + a := model.ArticleViewModel{} + + err := rows.Scan(&a.Id, &a.Title, &sourceUrl, &a.PublishDate, &a.Summary) + if err != nil { + return nil, err + } + + // summary + if a.Summary == "" { + a.Summary = "N/A" + } + + // short url + parsedURL, err := url.Parse(sourceUrl) + if err == nil { + a.ShortSource = parsedURL.Hostname() + } else { + a.ShortSource = "" + } + + // ai summary always false + a.AiSummarized = false + + articleVMs = append(articleVMs, &a) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return articleVMs, nil + +} + +// Will use the full-text search features of the underlying database to search +// articles as view models for a given search query. This may fail if the +// connection to the database fails. +func (m *ArticleViewModelRepository) Search(query string) ([]*model.ArticleViewModel, error) { + stmt := ` + SELECT a.id, a.title, a.sourceUrl, a.publishDate, d.summary + FROM articles a JOIN documents d ON a.document_id = d.id + WHERE to_tsvector('german', d.content) @@ to_tsquery('german', $1) + ORDER BY ts_rank(to_tsvector('german', d.content), to_tsquery('german', $1)) DESC + LIMIT 10 + ` + + query = strings.Join(strings.Split(strings.TrimSpace(query), " "), " | ") + rows, err := m.DB.Query(stmt, query) + if err != nil { + return nil, err + } + + articleVMs := []*model.ArticleViewModel{} + for rows.Next() { + a := &model.ArticleViewModel{} + var sourceUrl string + err := rows.Scan(&a.Id, &a.Title, &sourceUrl, &a.PublishDate, &a.Summary) + if err != nil { + return nil, err + } + // summary + if a.Summary == "" { + a.Summary = "N/A" + } + + // short url + parsedURL, err := url.Parse(sourceUrl) + if err == nil { + a.ShortSource = parsedURL.Hostname() + } else { + a.ShortSource = "" + } + + // ai summary always false + a.AiSummarized = false + + articleVMs = append(articleVMs, a) + } + + if err = rows.Err(); err != nil { + return nil, err + } + return articleVMs, nil +} diff --git a/src/internal/model/database/documentrepository.go b/src/internal/model/database/documentrepository.go index ae9fb01..0bb6adc 100644 --- a/src/internal/model/database/documentrepository.go +++ b/src/internal/model/database/documentrepository.go @@ -88,8 +88,8 @@ func (m *DocumentRepository) Update(d *model.Document) error { // Will transform every document in the database given a transformation // function. Will load the document, parse it to the transform function and call -// update on the returned document. May throw an error if the connection to the -// database fails. +// update on the returned document. Returns the number of processed documents. +// May throw an error if the connection to the database fails. func (d *DocumentRepository) Map(transform func(*model.Document) *model.Document) (int, error) { processed := 0