feat: add feature to speak with the AI and create report from contexts

This commit is contained in:
2026-04-19 18:27:42 +02:00
parent eb1fb5ca78
commit 7ef93276e1
19 changed files with 656 additions and 33 deletions

View File

@ -187,20 +187,28 @@ func (r *Repository) UpdateSource(id string, enabled bool) error {
// ── Articles ───────────────────────────────────────────────────────────────
func (r *Repository) UpsertArticle(sourceID, title, content, url string, publishedAt *time.Time) (*Article, error) {
a := &Article{}
// InsertArticleIfNew insère l'article uniquement s'il n'existe pas déjà (par URL).
// Retourne (article, true, nil) si inséré, (nil, false, nil) si déjà présent.
func (r *Repository) InsertArticleIfNew(sourceID, title, content, url string, publishedAt *time.Time) (*Article, bool, error) {
var pa sql.NullTime
if publishedAt != nil {
pa = sql.NullTime{Time: *publishedAt, Valid: true}
}
a := &Article{}
err := r.db.QueryRow(`
INSERT INTO articles (source_id, title, content, url, published_at)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (url) DO UPDATE SET title=EXCLUDED.title, content=EXCLUDED.content
ON CONFLICT (url) DO NOTHING
RETURNING id, source_id, title, content, url, published_at, created_at`,
sourceID, title, content, url, pa,
).Scan(&a.ID, &a.SourceID, &a.Title, &a.Content, &a.URL, &a.PublishedAt, &a.CreatedAt)
return a, err
if err == sql.ErrNoRows {
return nil, false, nil // déjà présent
}
if err != nil {
return nil, false, err
}
return a, true, nil
}
func (r *Repository) AddArticleSymbol(articleID, symbol string) error {
@ -260,7 +268,7 @@ func (r *Repository) GetRecentArticles(hours int) ([]Article, error) {
SELECT a.id, a.source_id, s.name, a.title, a.content, a.url, a.published_at, a.created_at
FROM articles a
JOIN sources s ON s.id = a.source_id
WHERE a.created_at > NOW() - ($1 * INTERVAL '1 hour')
WHERE COALESCE(a.published_at, a.created_at) > NOW() - ($1 * INTERVAL '1 hour')
ORDER BY a.published_at DESC NULLS LAST, a.created_at DESC`, hours)
if err != nil {
return nil, err
@ -581,3 +589,48 @@ func (r *Repository) ListSettings() ([]Setting, error) {
}
return settings, nil
}
// ── Reports ────────────────────────────────────────────────────────────────
func (r *Repository) CreatePendingReport(userID string, summaryID *string, excerpt, question string) (*Report, error) {
rep := &Report{}
err := r.db.QueryRow(`
INSERT INTO reports (user_id, summary_id, context_excerpt, question, answer, status)
VALUES ($1, $2, $3, $4, '', 'generating')
RETURNING id, user_id, summary_id, context_excerpt, question, answer, status, error_msg, created_at`,
userID, summaryID, excerpt, question,
).Scan(&rep.ID, &rep.UserID, &rep.SummaryID, &rep.ContextExcerpt, &rep.Question, &rep.Answer, &rep.Status, &rep.ErrorMsg, &rep.CreatedAt)
return rep, err
}
func (r *Repository) UpdateReport(id, status, answer, errorMsg string) error {
_, err := r.db.Exec(`
UPDATE reports SET status=$1, answer=$2, error_msg=$3 WHERE id=$4`,
status, answer, errorMsg, id)
return err
}
func (r *Repository) ListReports(userID string) ([]Report, error) {
rows, err := r.db.Query(`
SELECT id, user_id, summary_id, context_excerpt, question, answer, status, error_msg, created_at
FROM reports WHERE user_id=$1
ORDER BY created_at DESC`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var reports []Report
for rows.Next() {
var rep Report
if err := rows.Scan(&rep.ID, &rep.UserID, &rep.SummaryID, &rep.ContextExcerpt, &rep.Question, &rep.Answer, &rep.Status, &rep.ErrorMsg, &rep.CreatedAt); err != nil {
return nil, err
}
reports = append(reports, rep)
}
return reports, nil
}
func (r *Repository) DeleteReport(id, userID string) error {
_, err := r.db.Exec(`DELETE FROM reports WHERE id=$1 AND user_id=$2`, id, userID)
return err
}