369 lines
9.8 KiB
Go
369 lines
9.8 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/rss2/backend/internal/db"
|
|
"github.com/rss2/backend/internal/models"
|
|
)
|
|
|
|
type NewsResponse struct {
|
|
ID string `json:"id"`
|
|
Titulo string `json:"titulo"`
|
|
Resumen string `json:"resumen"`
|
|
URL string `json:"url"`
|
|
Fecha *time.Time `json:"fecha"`
|
|
ImagenURL *string `json:"imagen_url"`
|
|
CategoriaID *int64 `json:"categoria_id"`
|
|
PaisID *int64 `json:"pais_id"`
|
|
FuenteNombre string `json:"fuente_nombre"`
|
|
TitleTranslated *string `json:"title_translated"`
|
|
SummaryTranslated *string `json:"summary_translated"`
|
|
LangTranslated *string `json:"lang_translated"`
|
|
Entities []Entity `json:"entities,omitempty"`
|
|
}
|
|
|
|
func GetNews(c *gin.Context) {
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
perPage, _ := strconv.Atoi(c.DefaultQuery("per_page", "30"))
|
|
query := c.Query("q")
|
|
categoryID := c.Query("category_id")
|
|
countryID := c.Query("country_id")
|
|
translatedOnly := c.Query("translated_only") == "true"
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 || perPage > 100 {
|
|
perPage = 30
|
|
}
|
|
|
|
offset := (page - 1) * perPage
|
|
|
|
where := "1=1"
|
|
args := []interface{}{}
|
|
argNum := 1
|
|
|
|
if query != "" {
|
|
where += fmt.Sprintf(" AND (n.titulo ILIKE $%d OR n.resumen ILIKE $%d)", argNum, argNum)
|
|
args = append(args, "%"+query+"%")
|
|
argNum++
|
|
}
|
|
if categoryID != "" {
|
|
where += fmt.Sprintf(" AND n.categoria_id = $%d", argNum)
|
|
args = append(args, categoryID)
|
|
argNum++
|
|
}
|
|
if countryID != "" {
|
|
where += fmt.Sprintf(" AND n.pais_id = $%d", argNum)
|
|
args = append(args, countryID)
|
|
argNum++
|
|
}
|
|
if translatedOnly {
|
|
where += " AND t.status = 'done' AND t.titulo_trad IS NOT NULL AND t.titulo_trad != n.titulo"
|
|
}
|
|
|
|
var total int
|
|
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM noticias n LEFT JOIN traducciones t ON t.noticia_id = n.id AND t.lang_to = 'es' WHERE %s", where)
|
|
err := db.GetPool().QueryRow(c.Request.Context(), countQuery, args...).Scan(&total)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to count news", Message: err.Error()})
|
|
return
|
|
}
|
|
|
|
if total == 0 {
|
|
c.JSON(http.StatusOK, models.NewsListResponse{
|
|
News: []models.NewsWithTranslations{},
|
|
Total: 0,
|
|
Page: page,
|
|
PerPage: perPage,
|
|
TotalPages: 0,
|
|
})
|
|
return
|
|
}
|
|
|
|
sqlQuery := fmt.Sprintf(`
|
|
SELECT n.id, n.titulo, COALESCE(n.resumen, ''), n.url, n.fecha, n.imagen_url,
|
|
n.categoria_id, n.pais_id, n.fuente_nombre,
|
|
t.titulo_trad,
|
|
t.resumen_trad,
|
|
t.lang_to as lang_trad
|
|
FROM noticias n
|
|
LEFT JOIN traducciones t ON t.noticia_id = n.id AND t.lang_to = 'es'
|
|
WHERE %s
|
|
ORDER BY n.fecha DESC LIMIT $%d OFFSET $%d
|
|
`, where, argNum, argNum+1)
|
|
|
|
args = append(args, perPage, offset)
|
|
|
|
rows, err := db.GetPool().Query(c.Request.Context(), sqlQuery, args...)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to fetch news", Message: err.Error()})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var newsList []NewsResponse
|
|
for rows.Next() {
|
|
var n NewsResponse
|
|
var imagenURL, fuenteNombre *string
|
|
var categoriaID, paisID *int32
|
|
|
|
err := rows.Scan(
|
|
&n.ID, &n.Titulo, &n.Resumen, &n.URL, &n.Fecha, &imagenURL,
|
|
&categoriaID, &paisID, &fuenteNombre,
|
|
&n.TitleTranslated, &n.SummaryTranslated, &n.LangTranslated,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if imagenURL != nil {
|
|
n.ImagenURL = imagenURL
|
|
}
|
|
if fuenteNombre != nil {
|
|
n.FuenteNombre = *fuenteNombre
|
|
}
|
|
if categoriaID != nil {
|
|
catID := int64(*categoriaID)
|
|
n.CategoriaID = &catID
|
|
}
|
|
if paisID != nil {
|
|
pID := int64(*paisID)
|
|
n.PaisID = &pID
|
|
}
|
|
newsList = append(newsList, n)
|
|
}
|
|
|
|
totalPages := (total + perPage - 1) / perPage
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"news": newsList,
|
|
"total": total,
|
|
"page": page,
|
|
"per_page": perPage,
|
|
"total_pages": totalPages,
|
|
})
|
|
}
|
|
|
|
func GetNewsByID(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
sqlQuery := `
|
|
SELECT n.id, n.titulo, COALESCE(n.resumen, ''), n.url, n.fecha, n.imagen_url,
|
|
n.categoria_id, n.pais_id, n.fuente_nombre,
|
|
t.titulo_trad,
|
|
t.resumen_trad,
|
|
t.lang_to as lang_trad
|
|
FROM noticias n
|
|
LEFT JOIN traducciones t ON t.noticia_id = n.id AND t.lang_to = 'es'
|
|
WHERE n.id = $1`
|
|
|
|
var n NewsResponse
|
|
var imagenURL, fuenteNombre *string
|
|
var categoriaID, paisID *int32
|
|
|
|
err := db.GetPool().QueryRow(c.Request.Context(), sqlQuery, id).Scan(
|
|
&n.ID, &n.Titulo, &n.Resumen, &n.URL, &n.Fecha, &imagenURL,
|
|
&categoriaID, &paisID, &fuenteNombre,
|
|
&n.TitleTranslated, &n.SummaryTranslated, &n.LangTranslated,
|
|
)
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, models.ErrorResponse{Error: "News not found"})
|
|
return
|
|
}
|
|
|
|
if imagenURL != nil {
|
|
n.ImagenURL = imagenURL
|
|
}
|
|
if fuenteNombre != nil {
|
|
n.FuenteNombre = *fuenteNombre
|
|
}
|
|
if categoriaID != nil {
|
|
catID := int64(*categoriaID)
|
|
n.CategoriaID = &catID
|
|
}
|
|
if paisID != nil {
|
|
pID := int64(*paisID)
|
|
n.PaisID = &pID
|
|
}
|
|
|
|
// Fetch entities for this news
|
|
entitiesQuery := `
|
|
SELECT t.valor, t.tipo, 1 as cnt, t.wiki_summary, t.wiki_url, t.image_path
|
|
FROM tags_noticia tn
|
|
JOIN tags t ON tn.tag_id = t.id
|
|
JOIN traducciones tr ON tn.traduccion_id = tr.id
|
|
WHERE tr.noticia_id = $1 AND t.tipo IN ('persona', 'organizacion')
|
|
`
|
|
rows, err := db.GetPool().Query(c.Request.Context(), entitiesQuery, id)
|
|
var entities []Entity
|
|
if err == nil {
|
|
defer rows.Close()
|
|
for rows.Next() {
|
|
var e Entity
|
|
if err := rows.Scan(&e.Valor, &e.Tipo, &e.Count, &e.WikiSummary, &e.WikiURL, &e.ImagePath); err == nil {
|
|
entities = append(entities, e)
|
|
}
|
|
}
|
|
}
|
|
if entities == nil {
|
|
entities = []Entity{}
|
|
}
|
|
n.Entities = entities
|
|
|
|
c.JSON(http.StatusOK, n)
|
|
}
|
|
|
|
func DeleteNews(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
result, err := db.GetPool().Exec(c.Request.Context(), "DELETE FROM noticias WHERE id = $1", id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to delete news", Message: err.Error()})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected() == 0 {
|
|
c.JSON(http.StatusNotFound, models.ErrorResponse{Error: "News not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, models.SuccessResponse{Message: "News deleted successfully"})
|
|
}
|
|
|
|
type Entity struct {
|
|
Valor string `json:"valor"`
|
|
Tipo string `json:"tipo"`
|
|
Count int `json:"count"`
|
|
WikiSummary *string `json:"wiki_summary"`
|
|
WikiURL *string `json:"wiki_url"`
|
|
ImagePath *string `json:"image_path"`
|
|
}
|
|
|
|
type EntityListResponse struct {
|
|
Entities []Entity `json:"entities"`
|
|
Total int `json:"total"`
|
|
Page int `json:"page"`
|
|
PerPage int `json:"per_page"`
|
|
TotalPages int `json:"total_pages"`
|
|
}
|
|
|
|
func GetEntities(c *gin.Context) {
|
|
countryID := c.Query("country_id")
|
|
categoryID := c.Query("category_id")
|
|
entityType := c.DefaultQuery("tipo", "persona")
|
|
|
|
q := c.Query("q")
|
|
|
|
pageStr := c.DefaultQuery("page", "1")
|
|
perPageStr := c.DefaultQuery("per_page", "50")
|
|
|
|
page, _ := strconv.Atoi(pageStr)
|
|
perPage, _ := strconv.Atoi(perPageStr)
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if perPage < 1 || perPage > 100 {
|
|
perPage = 50
|
|
}
|
|
|
|
offset := (page - 1) * perPage
|
|
|
|
where := "t.tipo = $1"
|
|
args := []interface{}{entityType}
|
|
|
|
if countryID != "" {
|
|
where += fmt.Sprintf(" AND n.pais_id = $%d", len(args)+1)
|
|
args = append(args, countryID)
|
|
}
|
|
|
|
if categoryID != "" {
|
|
where += fmt.Sprintf(" AND n.categoria_id = $%d", len(args)+1)
|
|
args = append(args, categoryID)
|
|
}
|
|
|
|
if q != "" {
|
|
where += fmt.Sprintf(" AND COALESCE(ea.canonical_name, t.valor) ILIKE $%d", len(args)+1)
|
|
args = append(args, "%"+q+"%")
|
|
}
|
|
|
|
// 1. Get the total count of distinct canonical entities matching the filter
|
|
countQuery := fmt.Sprintf(`
|
|
SELECT COUNT(DISTINCT COALESCE(ea.canonical_name, t.valor))
|
|
FROM tags_noticia tn
|
|
JOIN tags t ON tn.tag_id = t.id
|
|
JOIN traducciones tr ON tn.traduccion_id = tr.id
|
|
JOIN noticias n ON tr.noticia_id = n.id
|
|
LEFT JOIN entity_aliases ea ON LOWER(ea.alias) = LOWER(t.valor) AND ea.tipo = t.tipo
|
|
WHERE %s
|
|
`, where)
|
|
|
|
var total int
|
|
err := db.GetPool().QueryRow(c.Request.Context(), countQuery, args...).Scan(&total)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to get entities count", Message: err.Error()})
|
|
return
|
|
}
|
|
|
|
if total == 0 {
|
|
c.JSON(http.StatusOK, EntityListResponse{
|
|
Entities: []Entity{},
|
|
Total: 0,
|
|
Page: page,
|
|
PerPage: perPage,
|
|
TotalPages: 0,
|
|
})
|
|
return
|
|
}
|
|
|
|
// 2. Fetch the paginated entities
|
|
args = append(args, perPage, offset)
|
|
query := fmt.Sprintf(`
|
|
SELECT COALESCE(ea.canonical_name, t.valor) as valor, t.tipo, COUNT(*)::int as cnt,
|
|
MAX(t.wiki_summary), MAX(t.wiki_url), MAX(t.image_path)
|
|
FROM tags_noticia tn
|
|
JOIN tags t ON tn.tag_id = t.id
|
|
JOIN traducciones tr ON tn.traduccion_id = tr.id
|
|
JOIN noticias n ON tr.noticia_id = n.id
|
|
LEFT JOIN entity_aliases ea ON LOWER(ea.alias) = LOWER(t.valor) AND ea.tipo = t.tipo
|
|
WHERE %s
|
|
GROUP BY COALESCE(ea.canonical_name, t.valor), t.tipo
|
|
ORDER BY cnt DESC
|
|
LIMIT $%d OFFSET $%d
|
|
`, where, len(args)-1, len(args))
|
|
|
|
rows, err := db.GetPool().Query(c.Request.Context(), query, args...)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, models.ErrorResponse{Error: "Failed to get entities", Message: err.Error()})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var entities []Entity
|
|
for rows.Next() {
|
|
var e Entity
|
|
if err := rows.Scan(&e.Valor, &e.Tipo, &e.Count, &e.WikiSummary, &e.WikiURL, &e.ImagePath); err != nil {
|
|
continue
|
|
}
|
|
entities = append(entities, e)
|
|
}
|
|
|
|
if entities == nil {
|
|
entities = []Entity{}
|
|
}
|
|
|
|
totalPages := (total + perPage - 1) / perPage
|
|
|
|
c.JSON(http.StatusOK, EntityListResponse{
|
|
Entities: entities,
|
|
Total: total,
|
|
Page: page,
|
|
PerPage: perPage,
|
|
TotalPages: totalPages,
|
|
})
|
|
}
|