clean up repositories; move viewmodel repo into own file;

This commit is contained in:
2025-01-20 21:56:58 +01:00
parent d6cf444def
commit b1236140b6
7 changed files with 138 additions and 120 deletions

View File

@@ -8,11 +8,13 @@ import (
type App struct { type App struct {
articles *database.ArticleRepository articles *database.ArticleRepository
articleVMs *database.ArticleViewModelRepository
} }
func NewApp(db *sql.DB) *App { func NewApp(db *sql.DB) *App {
return &App{ return &App{
articles: &database.ArticleRepository{DB: db}, articles: &database.ArticleRepository{DB: db},
articleVMs: &database.ArticleViewModelRepository{DB: db},
} }
} }

View File

@@ -20,7 +20,7 @@ func (app *App) Index(w http.ResponseWriter, req *http.Request) {
} }
// get articles // get articles
articleVMs, err := app.articles.AllArticleViewModels(int(limit), int(offset)) articleVMs, err := app.articleVMs.All(int(limit), int(offset))
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return

View File

@@ -16,7 +16,7 @@ func (app *App) UpSearch(w http.ResponseWriter, req *http.Request) {
} }
// get articles // get articles
articleVMs, err := app.articles.SearchArticleViewModel(searchTerms) articleVMs, err := app.articleVMs.Search(searchTerms)
if err != nil { if err != nil {
// treat as no result // treat as no result
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View File

@@ -3,7 +3,6 @@ package database
import ( import (
"crowsnest/internal/model" "crowsnest/internal/model"
"database/sql" "database/sql"
"net/url"
"strings" "strings"
) )
@@ -13,7 +12,7 @@ type ArticleRepository struct {
// Gets all the article objects from the database. This may throw an error if // Gets all the article objects from the database. This may throw an error if
// the connection to the database fails. // 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 := ` stmt := `
SELECT id, title, sourceUrl, content, publishDate, fetchDate SELECT id, title, sourceUrl, content, publishDate, fetchDate
FROM articles FROM articles
@@ -25,9 +24,9 @@ func (m *ArticleRepository) All(limit int, offset int) ([]model.Article, error)
return nil, err return nil, err
} }
articles := []model.Article{} articles := []*model.Article{}
for rows.Next() { for rows.Next() {
a := model.Article{} a := &model.Article{}
err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate) err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -43,55 +42,6 @@ func (m *ArticleRepository) All(limit int, offset int) ([]model.Article, error)
return articles, nil 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 // Counts all articles in the database. This may throw an error if the
// connection to the database fails. // connection to the database fails.
func (m *ArticleRepository) CountAll() (uint, error) { 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 // 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 // articles for a given search query. This may fail if the connection to the
// database fails. // database fails.
func (m *ArticleRepository) SearchArticleViewModel(query string) ([]*model.ArticleViewModel, error) { func (m *ArticleRepository) Search(query string) ([]*model.Article, 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) {
stmt := ` stmt := `
SELECT a.id, a.title, a.sourceurl, a.content, a.publishdate, a.fetchDate 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 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 return nil, err
} }
articles := []model.Article{} articles := []*model.Article{}
for rows.Next() { for rows.Next() {
a := model.Article{} a := &model.Article{}
err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate) err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -224,6 +123,9 @@ func (m *ArticleRepository) Insert(a *model.Article) error {
return err 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 { func (m *ArticleRepository) Update(a *model.Article) error {
stmt := `UPDATE articles stmt := `UPDATE articles
SET title = $1, sourceUrl = $2, content = $4, publishDate = $5, fetchDate = $6 SET title = $1, sourceUrl = $2, content = $4, publishDate = $5, fetchDate = $6

View File

@@ -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
}

View File

@@ -88,8 +88,8 @@ func (m *DocumentRepository) Update(d *model.Document) error {
// Will transform every document in the database given a transformation // Will transform every document in the database given a transformation
// function. Will load the document, parse it to the transform function and call // 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 // update on the returned document. Returns the number of processed documents.
// database fails. // May throw an error if the connection to the database fails.
func (d *DocumentRepository) Map(transform func(*model.Document) *model.Document) (int, error) { func (d *DocumentRepository) Map(transform func(*model.Document) *model.Document) (int, error) {
processed := 0 processed := 0