saving 25ms-50ms + move summary column

This commit is contained in:
2025-01-20 11:31:32 +01:00
parent 47299d6ef3
commit 0594fb8aeb
6 changed files with 92 additions and 39 deletions

View File

@@ -0,0 +1,19 @@
-- +goose Up
-- +goose StatementBegin
BEGIN;
ALTER TABLE articles DROP COLUMN IF EXISTS aisummary;
ALTER TABLE documents ADD COLUMN summary TEXT DEFAULT '';
COMMIT;
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
BEGIN;
ALTER TABLE articles ADD COLUMN aisummary TEXT DEFAULT '';
ALTER TABLE documents DROP COLUMN IF EXISTS summary;
COMMIT;
-- +goose StatementEnd

View File

@@ -19,7 +19,7 @@ func main() {
} }
// run web crawlers // run web crawlers
articles := &database.ArticleModel{DB: db} articles := &database.ArticleRepository{DB: db}
crawler := crawler.CrawlerFacade{} crawler := crawler.CrawlerFacade{}
crawler.Init() crawler.Init()

View File

@@ -20,7 +20,7 @@ func (app *App) Index(w http.ResponseWriter, req *http.Request) {
} }
// get articles // get articles
articles, err := app.articles.All(int(limit), int(offset)) articleVMs, err := app.articles.AllArticleViewModels(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
@@ -34,12 +34,6 @@ func (app *App) Index(w http.ResponseWriter, req *http.Request) {
} }
totalCount /= pageSize totalCount /= pageSize
// convert to viewmodel
articleVMs := make([]*model.ArticleViewModel, 0, len(articles))
for _, a := range articles {
articleVMs = append(articleVMs, a.ViewModel())
}
// render template // render template
t := template.Must(template.ParseFiles( t := template.Must(template.ParseFiles(
"assets/templates/article.html", "assets/templates/article.html",

View File

@@ -7,12 +7,12 @@ import (
) )
type App struct { type App struct {
articles *database.ArticleModel articles *database.ArticleRepository
} }
func NewApp(db *sql.DB) *App { func NewApp(db *sql.DB) *App {
return &App{ return &App{
articles: &database.ArticleModel{DB: db}, articles: &database.ArticleRepository{DB: db},
} }
} }

View File

@@ -5,7 +5,6 @@ import (
"time" "time"
) )
// TODO docstring
type Article struct { type Article struct {
Id int Id int
SourceUrl string SourceUrl string
@@ -13,7 +12,6 @@ type Article struct {
FetchDate time.Time FetchDate time.Time
Title string Title string
Content string Content string
AiSummary string
} }
func (a *Article) Clone() *Article { func (a *Article) Clone() *Article {
@@ -24,11 +22,9 @@ func (a *Article) Clone() *Article {
FetchDate: a.FetchDate, FetchDate: a.FetchDate,
Title: a.Title, Title: a.Title,
Content: a.Content, Content: a.Content,
AiSummary: a.AiSummary,
} }
} }
// TODO docstring
type ArticleViewModel struct { type ArticleViewModel struct {
Id int Id int
Title string Title string
@@ -49,14 +45,12 @@ type ArticlePageViewModel struct {
// TODO docstring // TODO docstring
func (a *Article) ViewModel() *ArticleViewModel { func (a *Article) ViewModel() *ArticleViewModel {
summary := a.AiSummary var summary string
if summary == "" {
if len(a.Content) > 200 { if len(a.Content) > 200 {
summary = a.Content[:200] summary = a.Content[:200]
} else { } else {
summary = a.Content summary = a.Content
} }
}
short_url := "" short_url := ""
parsedURL, err := url.Parse(a.SourceUrl) parsedURL, err := url.Parse(a.SourceUrl)
@@ -70,15 +64,11 @@ func (a *Article) ViewModel() *ArticleViewModel {
PublishDate: a.PublishDate.Local().Format("02.01.2006"), PublishDate: a.PublishDate.Local().Format("02.01.2006"),
ShortSource: short_url, ShortSource: short_url,
Summary: summary, Summary: summary,
AiSummarized: a.AiSummary != "",
} }
} }
func (a *Article) PageViewModel() *ArticlePageViewModel { func (a *Article) PageViewModel() *ArticlePageViewModel {
summary := a.AiSummary summary := "N/A"
if summary == "" {
summary = "N/A"
}
short_url := "" short_url := ""
parsedURL, err := url.Parse(a.SourceUrl) parsedURL, err := url.Parse(a.SourceUrl)

View File

@@ -3,18 +3,19 @@ package database
import ( import (
"crowsnest/internal/model" "crowsnest/internal/model"
"database/sql" "database/sql"
"net/url"
"strings" "strings"
) )
type ArticleModel struct { type ArticleRepository struct {
DB *sql.DB DB *sql.DB
} }
// 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 *ArticleModel) 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, aisummary SELECT id, title, sourceUrl, content, publishDate, fetchDate
FROM articles FROM articles
ORDER BY publishDate DESC ORDER BY publishDate DESC
LIMIT $1 OFFSET $2 LIMIT $1 OFFSET $2
@@ -27,7 +28,7 @@ func (m *ArticleModel) All(limit int, offset int) ([]model.Article, error) {
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, &a.AiSummary) 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
} }
@@ -42,9 +43,58 @@ func (m *ArticleModel) 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 *ArticleModel) CountAll() (uint, error) { func (m *ArticleRepository) CountAll() (uint, error) {
stmt := `SELECT count(id) FROM articles ` stmt := `SELECT count(id) FROM articles `
rows := m.DB.QueryRow(stmt) rows := m.DB.QueryRow(stmt)
@@ -60,7 +110,7 @@ func (m *ArticleModel) 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 *ArticleModel) Search(query string) ([]model.Article, error) { 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
@@ -94,7 +144,7 @@ func (m *ArticleModel) Search(query string) ([]model.Article, error) {
// Will return an article given an id. This may fail if the connection to the // Will return an article given an id. This may fail if the connection to the
// database fails or there is no aritcle with the given id. // database fails or there is no aritcle with the given id.
func (m *ArticleModel) ById(id int) (*model.Article, error) { func (m *ArticleRepository) ById(id int) (*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 FROM articles a
@@ -114,21 +164,21 @@ func (m *ArticleModel) ById(id int) (*model.Article, error) {
// Inserts a new article into the database. The id attribute of the given // Inserts a new article into the database. The id attribute of the given
// article will be ignored. May throw an error if the execution of the database // article will be ignored. May throw an error if the execution of the database
// query fails. // query fails.
func (m *ArticleModel) Insert(a *model.Article) error { func (m *ArticleRepository) Insert(a *model.Article) error {
// insert article // insert article
stmt := `INSERT INTO articles (title, sourceUrl, content, publishDate, fetchDate, aisummary) stmt := `INSERT INTO articles (title, sourceUrl, content, publishDate, fetchDate)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
` `
_, err := m.DB.Exec(stmt, a.Title, a.SourceUrl, a.Content, a.PublishDate, a.FetchDate, a.AiSummary) _, err := m.DB.Exec(stmt, a.Title, a.SourceUrl, a.Content, a.PublishDate, a.FetchDate)
return err return err
} }
// TODO docstring // TODO docstring
func (m *ArticleModel) 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, aisummary = $7 SET title = $1, sourceUrl = $2, content = $4, publishDate = $5, fetchDate = $6
WHERE id = $8 WHERE id = $8
` `
_, err := m.DB.Exec(stmt, a.Title, a.SourceUrl, a.Content, a.PublishDate, a.FetchDate, a.AiSummary, a.Id) _, err := m.DB.Exec(stmt, a.Title, a.SourceUrl, a.Content, a.PublishDate, a.FetchDate, a.Id)
return err return err
} }