Build and Push Docker Image / build-and-push (push) Successful in 3m43s
Signed-off-by: kaedwen <kaedwen@heinrich.blue>
174 lines
4.4 KiB
Go
174 lines
4.4 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/pheinrich/aitrade/pkg/model"
|
|
)
|
|
|
|
type NewsRepository struct {
|
|
db Database
|
|
}
|
|
|
|
func NewNewsRepository(db Database) *NewsRepository {
|
|
return &NewsRepository{db: db}
|
|
}
|
|
|
|
func (r *NewsRepository) Create(ctx context.Context, article *model.NewsArticle) error {
|
|
query := `
|
|
INSERT INTO news_articles (
|
|
source, title, url, content, published_at, fetched_at, symbols,
|
|
sentiment_score, sentiment_label,
|
|
llm_sentiment_score, llm_model, llm_confidence, sentiment_method
|
|
)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(url) DO NOTHING
|
|
`
|
|
|
|
result, err := r.db.ExecContext(ctx, query,
|
|
article.Source,
|
|
article.Title,
|
|
article.URL,
|
|
article.Content,
|
|
article.PublishedAt,
|
|
article.FetchedAt,
|
|
article.Symbols,
|
|
article.SentimentScore,
|
|
article.SentimentLabel,
|
|
article.LLMSentimentScore,
|
|
article.LLMModel,
|
|
article.LLMConfidence,
|
|
article.SentimentMethod,
|
|
)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to insert news article: %w", err)
|
|
}
|
|
|
|
id, err := result.LastInsertId()
|
|
if err != nil {
|
|
// ON CONFLICT DO NOTHING means no rows affected, but not an error
|
|
rowsAffected, _ := result.RowsAffected()
|
|
if rowsAffected == 0 {
|
|
return nil // Duplicate, silently ignore
|
|
}
|
|
return fmt.Errorf("failed to get last insert id: %w", err)
|
|
}
|
|
|
|
article.ID = id
|
|
return nil
|
|
}
|
|
|
|
func (r *NewsRepository) GetRecent(ctx context.Context, limit int) ([]*model.NewsArticle, error) {
|
|
return r.GetRecentWithOffset(ctx, limit, 0)
|
|
}
|
|
|
|
func (r *NewsRepository) GetRecentWithOffset(ctx context.Context, limit, offset int) ([]*model.NewsArticle, error) {
|
|
query := `
|
|
SELECT id, source, title, url, content, published_at, fetched_at, symbols,
|
|
sentiment_score, sentiment_label,
|
|
llm_sentiment_score, llm_model, llm_confidence, sentiment_method
|
|
FROM news_articles
|
|
ORDER BY published_at DESC
|
|
LIMIT ? OFFSET ?
|
|
`
|
|
|
|
rows, err := r.db.QueryContext(ctx, query, limit, offset)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query recent news: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var articles []*model.NewsArticle
|
|
for rows.Next() {
|
|
var article model.NewsArticle
|
|
if err := rows.Scan(
|
|
&article.ID,
|
|
&article.Source,
|
|
&article.Title,
|
|
&article.URL,
|
|
&article.Content,
|
|
&article.PublishedAt,
|
|
&article.FetchedAt,
|
|
&article.Symbols,
|
|
&article.SentimentScore,
|
|
&article.SentimentLabel,
|
|
&article.LLMSentimentScore,
|
|
&article.LLMModel,
|
|
&article.LLMConfidence,
|
|
&article.SentimentMethod,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("failed to scan news article: %w", err)
|
|
}
|
|
articles = append(articles, &article)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("rows error: %w", err)
|
|
}
|
|
|
|
return articles, nil
|
|
}
|
|
|
|
func (r *NewsRepository) GetBySymbol(ctx context.Context, symbol string, since time.Time, limit int) ([]*model.NewsArticle, error) {
|
|
query := `
|
|
SELECT id, source, title, url, content, published_at, fetched_at, symbols,
|
|
sentiment_score, sentiment_label,
|
|
llm_sentiment_score, llm_model, llm_confidence, sentiment_method
|
|
FROM news_articles
|
|
WHERE symbols LIKE ? AND published_at >= ?
|
|
ORDER BY published_at DESC
|
|
LIMIT ?
|
|
`
|
|
|
|
rows, err := r.db.QueryContext(ctx, query, "%"+symbol+"%", since, limit)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query news by symbol: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var articles []*model.NewsArticle
|
|
for rows.Next() {
|
|
var article model.NewsArticle
|
|
if err := rows.Scan(
|
|
&article.ID,
|
|
&article.Source,
|
|
&article.Title,
|
|
&article.URL,
|
|
&article.Content,
|
|
&article.PublishedAt,
|
|
&article.FetchedAt,
|
|
&article.Symbols,
|
|
&article.SentimentScore,
|
|
&article.SentimentLabel,
|
|
&article.LLMSentimentScore,
|
|
&article.LLMModel,
|
|
&article.LLMConfidence,
|
|
&article.SentimentMethod,
|
|
); err != nil {
|
|
return nil, fmt.Errorf("failed to scan news article: %w", err)
|
|
}
|
|
articles = append(articles, &article)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating news rows: %w", err)
|
|
}
|
|
|
|
return articles, nil
|
|
}
|
|
|
|
// CountRecentByTime returns the count of news articles fetched since the given time
|
|
func (r *NewsRepository) CountRecentByTime(ctx context.Context, since time.Time) (int, error) {
|
|
query := `SELECT COUNT(*) FROM news_articles WHERE fetched_at >= ?`
|
|
|
|
var count int
|
|
err := r.db.QueryRowContext(ctx, query, since).Scan(&count)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("failed to count recent news: %w", err)
|
|
}
|
|
|
|
return count, nil
|
|
}
|