package database import ( "crowsnest/internal/model" "database/sql" "net/url" "strings" ) type ArticleRepository struct { DB *sql.DB } // 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) { stmt := ` SELECT id, title, sourceUrl, content, publishDate, fetchDate FROM articles ORDER BY publishDate DESC LIMIT $1 OFFSET $2 ` rows, err := m.DB.Query(stmt, limit, offset) if err != nil { return nil, err } articles := []model.Article{} for rows.Next() { a := model.Article{} err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate) if err != nil { return nil, err } articles = append(articles, a) } if err = rows.Err(); err != nil { return nil, err } 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) { stmt := `SELECT count(id) FROM articles ` rows := m.DB.QueryRow(stmt) count := uint(0) if err := rows.Scan(&count); err != nil { return 0, err } return count, 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) 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) { 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 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 } articles := []model.Article{} for rows.Next() { a := model.Article{} err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate) if err != nil { return nil, err } articles = append(articles, a) } if err = rows.Err(); err != nil { return nil, err } return articles, nil } // 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. func (m *ArticleRepository) ById(id int) (*model.Article, error) { stmt := ` SELECT a.id, a.title, a.sourceurl, a.content, a.publishdate, a.fetchDate FROM articles a WHERE a.id = $1 ` rows := m.DB.QueryRow(stmt, id) a := &model.Article{} if err := rows.Scan(&a.Id, &a.Title, &a.SourceUrl, &a.Content, &a.PublishDate, &a.FetchDate); err != nil { return nil, err } return a, nil } // 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 // query fails. func (m *ArticleRepository) Insert(a *model.Article) error { // insert article stmt := `INSERT INTO articles (title, sourceUrl, content, publishDate, fetchDate) VALUES ($1, $2, $3, $4, $5, $6) ` _, err := m.DB.Exec(stmt, a.Title, a.SourceUrl, a.Content, a.PublishDate, a.FetchDate) return err } func (m *ArticleRepository) Update(a *model.Article) error { stmt := `UPDATE articles SET title = $1, sourceUrl = $2, content = $4, publishDate = $5, fetchDate = $6 WHERE id = $8 ` _, err := m.DB.Exec(stmt, a.Title, a.SourceUrl, a.Content, a.PublishDate, a.FetchDate, a.Id) return err }