Major changes: - Add Go backend (backend/) with microservices architecture - Enhanced master-agents-svc: reranker, content-classifier, stealth-crawler, proxy-manager, media-search, fastClassifier, language detection - New web-svc widgets: KnowledgeCard, ProductCard, ProfileCard, VideoCard, UnifiedCard, CardGallery, InlineImageGallery, SourcesPanel, RelatedQuestions - Improved discover-svc with discover-db integration - Docker deployment improvements (Caddyfile, vendor.sh, BUILD.md) - Library-svc: project_id schema migration - Remove deprecated finance-svc and travel-svc - Localization improvements across services Made-with: Cursor
323 lines
7.2 KiB
Go
323 lines
7.2 KiB
Go
package db
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/gooseek/backend/internal/computer"
|
|
)
|
|
|
|
type ComputerArtifactRepo struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
func NewComputerArtifactRepo(db *sql.DB) *ComputerArtifactRepo {
|
|
return &ComputerArtifactRepo{db: db}
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) Migrate() error {
|
|
query := `
|
|
CREATE TABLE IF NOT EXISTS computer_artifacts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
task_id UUID NOT NULL,
|
|
type VARCHAR(50) NOT NULL,
|
|
name VARCHAR(255),
|
|
content BYTEA,
|
|
url TEXT,
|
|
size BIGINT DEFAULT 0,
|
|
mime_type VARCHAR(100),
|
|
metadata JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_computer_artifacts_task_id ON computer_artifacts(task_id);
|
|
CREATE INDEX IF NOT EXISTS idx_computer_artifacts_type ON computer_artifacts(type);
|
|
CREATE INDEX IF NOT EXISTS idx_computer_artifacts_created ON computer_artifacts(created_at DESC);
|
|
`
|
|
|
|
_, err := r.db.Exec(query)
|
|
return err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) Create(ctx context.Context, artifact *computer.Artifact) error {
|
|
metadataJSON, _ := json.Marshal(artifact.Metadata)
|
|
|
|
query := `
|
|
INSERT INTO computer_artifacts (id, task_id, type, name, content, url, size, mime_type, metadata, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
`
|
|
|
|
_, err := r.db.ExecContext(ctx, query,
|
|
artifact.ID,
|
|
artifact.TaskID,
|
|
artifact.Type,
|
|
artifact.Name,
|
|
artifact.Content,
|
|
artifact.URL,
|
|
artifact.Size,
|
|
artifact.MimeType,
|
|
metadataJSON,
|
|
artifact.CreatedAt,
|
|
)
|
|
|
|
return err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) GetByID(ctx context.Context, id string) (*computer.Artifact, error) {
|
|
query := `
|
|
SELECT id, task_id, type, name, content, url, size, mime_type, metadata, created_at
|
|
FROM computer_artifacts
|
|
WHERE id = $1
|
|
`
|
|
|
|
var artifact computer.Artifact
|
|
var content []byte
|
|
var url, mimeType sql.NullString
|
|
var metadataJSON []byte
|
|
|
|
err := r.db.QueryRowContext(ctx, query, id).Scan(
|
|
&artifact.ID,
|
|
&artifact.TaskID,
|
|
&artifact.Type,
|
|
&artifact.Name,
|
|
&content,
|
|
&url,
|
|
&artifact.Size,
|
|
&mimeType,
|
|
&metadataJSON,
|
|
&artifact.CreatedAt,
|
|
)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
artifact.Content = content
|
|
if url.Valid {
|
|
artifact.URL = url.String
|
|
}
|
|
if mimeType.Valid {
|
|
artifact.MimeType = mimeType.String
|
|
}
|
|
if len(metadataJSON) > 0 {
|
|
json.Unmarshal(metadataJSON, &artifact.Metadata)
|
|
}
|
|
|
|
return &artifact, nil
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) GetByTaskID(ctx context.Context, taskID string) ([]computer.Artifact, error) {
|
|
query := `
|
|
SELECT id, task_id, type, name, url, size, mime_type, metadata, created_at
|
|
FROM computer_artifacts
|
|
WHERE task_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
rows, err := r.db.QueryContext(ctx, query, taskID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var artifacts []computer.Artifact
|
|
|
|
for rows.Next() {
|
|
var artifact computer.Artifact
|
|
var url, mimeType sql.NullString
|
|
var metadataJSON []byte
|
|
|
|
err := rows.Scan(
|
|
&artifact.ID,
|
|
&artifact.TaskID,
|
|
&artifact.Type,
|
|
&artifact.Name,
|
|
&url,
|
|
&artifact.Size,
|
|
&mimeType,
|
|
&metadataJSON,
|
|
&artifact.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if url.Valid {
|
|
artifact.URL = url.String
|
|
}
|
|
if mimeType.Valid {
|
|
artifact.MimeType = mimeType.String
|
|
}
|
|
if len(metadataJSON) > 0 {
|
|
json.Unmarshal(metadataJSON, &artifact.Metadata)
|
|
}
|
|
|
|
artifacts = append(artifacts, artifact)
|
|
}
|
|
|
|
return artifacts, nil
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) GetByType(ctx context.Context, taskID, artifactType string) ([]computer.Artifact, error) {
|
|
query := `
|
|
SELECT id, task_id, type, name, url, size, mime_type, metadata, created_at
|
|
FROM computer_artifacts
|
|
WHERE task_id = $1 AND type = $2
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
rows, err := r.db.QueryContext(ctx, query, taskID, artifactType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var artifacts []computer.Artifact
|
|
|
|
for rows.Next() {
|
|
var artifact computer.Artifact
|
|
var url, mimeType sql.NullString
|
|
var metadataJSON []byte
|
|
|
|
err := rows.Scan(
|
|
&artifact.ID,
|
|
&artifact.TaskID,
|
|
&artifact.Type,
|
|
&artifact.Name,
|
|
&url,
|
|
&artifact.Size,
|
|
&mimeType,
|
|
&metadataJSON,
|
|
&artifact.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if url.Valid {
|
|
artifact.URL = url.String
|
|
}
|
|
if mimeType.Valid {
|
|
artifact.MimeType = mimeType.String
|
|
}
|
|
if len(metadataJSON) > 0 {
|
|
json.Unmarshal(metadataJSON, &artifact.Metadata)
|
|
}
|
|
|
|
artifacts = append(artifacts, artifact)
|
|
}
|
|
|
|
return artifacts, nil
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) GetContent(ctx context.Context, id string) ([]byte, error) {
|
|
query := `SELECT content FROM computer_artifacts WHERE id = $1`
|
|
var content []byte
|
|
err := r.db.QueryRowContext(ctx, query, id).Scan(&content)
|
|
return content, err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) UpdateURL(ctx context.Context, id, url string) error {
|
|
query := `UPDATE computer_artifacts SET url = $1 WHERE id = $2`
|
|
_, err := r.db.ExecContext(ctx, query, url, id)
|
|
return err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) Delete(ctx context.Context, id string) error {
|
|
query := `DELETE FROM computer_artifacts WHERE id = $1`
|
|
_, err := r.db.ExecContext(ctx, query, id)
|
|
return err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) DeleteByTaskID(ctx context.Context, taskID string) error {
|
|
query := `DELETE FROM computer_artifacts WHERE task_id = $1`
|
|
_, err := r.db.ExecContext(ctx, query, taskID)
|
|
return err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) DeleteOlderThan(ctx context.Context, days int) (int64, error) {
|
|
query := `
|
|
DELETE FROM computer_artifacts
|
|
WHERE created_at < NOW() - INTERVAL '1 day' * $1
|
|
`
|
|
result, err := r.db.ExecContext(ctx, query, days)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return result.RowsAffected()
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) GetTotalSize(ctx context.Context, taskID string) (int64, error) {
|
|
query := `SELECT COALESCE(SUM(size), 0) FROM computer_artifacts WHERE task_id = $1`
|
|
var size int64
|
|
err := r.db.QueryRowContext(ctx, query, taskID).Scan(&size)
|
|
return size, err
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) Count(ctx context.Context, taskID string) (int64, error) {
|
|
query := `SELECT COUNT(*) FROM computer_artifacts WHERE task_id = $1`
|
|
var count int64
|
|
err := r.db.QueryRowContext(ctx, query, taskID).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
type ArtifactSummary struct {
|
|
ID string `json:"id"`
|
|
TaskID string `json:"taskId"`
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
URL string `json:"url"`
|
|
Size int64 `json:"size"`
|
|
MimeType string `json:"mimeType"`
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
}
|
|
|
|
func (r *ComputerArtifactRepo) GetSummaries(ctx context.Context, taskID string) ([]ArtifactSummary, error) {
|
|
query := `
|
|
SELECT id, task_id, type, name, url, size, mime_type, created_at
|
|
FROM computer_artifacts
|
|
WHERE task_id = $1
|
|
ORDER BY created_at ASC
|
|
`
|
|
|
|
rows, err := r.db.QueryContext(ctx, query, taskID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var summaries []ArtifactSummary
|
|
|
|
for rows.Next() {
|
|
var s ArtifactSummary
|
|
var url, mimeType sql.NullString
|
|
|
|
err := rows.Scan(
|
|
&s.ID,
|
|
&s.TaskID,
|
|
&s.Type,
|
|
&s.Name,
|
|
&url,
|
|
&s.Size,
|
|
&mimeType,
|
|
&s.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if url.Valid {
|
|
s.URL = url.String
|
|
}
|
|
if mimeType.Valid {
|
|
s.MimeType = mimeType.String
|
|
}
|
|
|
|
summaries = append(summaries, s)
|
|
}
|
|
|
|
return summaries, nil
|
|
}
|