- Go API server with PostgreSQL + Redis - AI floor replacement (OpenRouter Gemini) - Product database (10 brands, 3539 products) - Recommendation engine, calculator, articles - Redis async queue + worker pool - Hot product caching, brand view tracking - JWT auth, favorites, projects - Docker deployment ready Co-Authored-By: Claude <noreply@anthropic.com>
115 lines
2.8 KiB
Go
115 lines
2.8 KiB
Go
package repository
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"floorvisualizer/internal/model"
|
|
)
|
|
|
|
func InsertArticles(d *sql.DB, articles []model.Article) error {
|
|
for i := range articles {
|
|
articles[i].SetImageJSON()
|
|
}
|
|
const batchSize = 50
|
|
for start := 0; start < len(articles); start += batchSize {
|
|
end := start + batchSize
|
|
if end > len(articles) {
|
|
end = len(articles)
|
|
}
|
|
batch := articles[start:end]
|
|
for _, a := range batch {
|
|
_, err := d.Exec(`
|
|
INSERT INTO articles (title, summary, content, cover_url, image_urls)
|
|
VALUES ($1,$2,$3,$4,$5)
|
|
`, a.Title, a.Summary, a.Content, a.CoverURL, a.ImageJSON)
|
|
if err != nil {
|
|
return fmt.Errorf("insert article: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GetArticleByID(d *sql.DB, id int) (*model.Article, error) {
|
|
var a model.Article
|
|
var imageJSON string
|
|
err := d.QueryRow(`
|
|
SELECT id, title, summary, content, cover_url, image_urls::text, created_at
|
|
FROM articles WHERE id = $1
|
|
`, id).Scan(&a.ID, &a.Title, &a.Summary, &a.Content, &a.CoverURL, &imageJSON, &a.CreatedAt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
json.Unmarshal([]byte(imageJSON), &a.ImageURLs)
|
|
if a.ImageURLs == nil {
|
|
a.ImageURLs = []string{}
|
|
}
|
|
return &a, nil
|
|
}
|
|
|
|
func ListArticles(d *sql.DB, page, limit int) ([]model.Article, int, error) {
|
|
if limit <= 0 {
|
|
limit = 10
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
var total int
|
|
if err := d.QueryRow("SELECT COUNT(*) FROM articles").Scan(&total); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
offset := (page - 1) * limit
|
|
rows, err := d.Query(`
|
|
SELECT id, title, summary, content, cover_url, image_urls::text, created_at
|
|
FROM articles ORDER BY id LIMIT $1 OFFSET $2
|
|
`, limit, offset)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []model.Article
|
|
for rows.Next() {
|
|
var a model.Article
|
|
var imageJSON string
|
|
if err := rows.Scan(&a.ID, &a.Title, &a.Summary, &a.Content, &a.CoverURL, &imageJSON, &a.CreatedAt); err != nil {
|
|
return nil, 0, err
|
|
}
|
|
json.Unmarshal([]byte(imageJSON), &a.ImageURLs)
|
|
if a.ImageURLs == nil {
|
|
a.ImageURLs = []string{}
|
|
}
|
|
out = append(out, a)
|
|
}
|
|
return out, total, rows.Err()
|
|
}
|
|
|
|
func RandomArticles(d *sql.DB, count int) ([]model.Article, error) {
|
|
rows, err := d.Query(`
|
|
SELECT id, title, summary, content, cover_url, image_urls::text, created_at
|
|
FROM articles ORDER BY RANDOM() LIMIT $1
|
|
`, count)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []model.Article
|
|
for rows.Next() {
|
|
var a model.Article
|
|
var imageJSON string
|
|
if err := rows.Scan(&a.ID, &a.Title, &a.Summary, &a.Content, &a.CoverURL, &imageJSON, &a.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
json.Unmarshal([]byte(imageJSON), &a.ImageURLs)
|
|
if a.ImageURLs == nil {
|
|
a.ImageURLs = []string{}
|
|
}
|
|
out = append(out, a)
|
|
}
|
|
return out, rows.Err()
|
|
}
|