small fix, don't worry about it

This commit is contained in:
Tomas Dvorak
2026-04-10 12:06:24 +02:00
commit 5c500a72b0
243 changed files with 44176 additions and 0 deletions
@@ -0,0 +1,506 @@
package postgres
import (
"context"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/tdvorak/seen/backend/internal/services/download"
)
const createDownloadJobSQL = `
INSERT INTO download_jobs (
user_id,
source_type,
source,
title,
status,
queue_position,
progress_percent,
bytes_total,
bytes_downloaded,
download_speed_mbps,
eta_seconds,
error_message,
retry_count,
created_at,
updated_at
) VALUES (
$1::uuid,
$2::text,
$3::text,
COALESCE(NULLIF($4::text, ''), $3::text),
'queued',
NULL,
0,
0,
0,
0,
NULL,
'',
0,
NOW(),
NOW()
)
RETURNING
id::text,
user_id::text,
source_type,
source,
title,
status,
COALESCE(queue_position, 0),
progress_percent,
bytes_total,
bytes_downloaded,
download_speed_mbps,
COALESCE(eta_seconds, 0),
error_message,
retry_count,
created_at,
updated_at;
`
const listDownloadJobsSQL = `
SELECT
id::text,
user_id::text,
source_type,
source,
title,
status,
COALESCE(queue_position, 0),
progress_percent,
bytes_total,
bytes_downloaded,
download_speed_mbps,
COALESCE(eta_seconds, 0),
error_message,
retry_count,
created_at,
updated_at,
COALESCE(started_at, NOW()),
COALESCE(completed_at, NOW()),
COALESCE(cancelled_at, NOW())
FROM download_jobs
WHERE user_id = $1::uuid
AND ($2::text = '' OR status = $2::text)
ORDER BY updated_at DESC, created_at DESC
LIMIT $3::int
OFFSET $4::int;
`
const getDownloadJobByIDSQL = `
SELECT
id::text,
user_id::text,
source_type,
source,
title,
status,
COALESCE(queue_position, 0),
progress_percent,
bytes_total,
bytes_downloaded,
download_speed_mbps,
COALESCE(eta_seconds, 0),
error_message,
retry_count,
created_at,
updated_at,
COALESCE(started_at, NOW()),
COALESCE(completed_at, NOW()),
COALESCE(cancelled_at, NOW())
FROM download_jobs
WHERE user_id = $1::uuid
AND id = $2::uuid
LIMIT 1;
`
const cancelDownloadJobSQL = `
UPDATE download_jobs
SET
status = 'cancelled',
cancelled_at = NOW(),
updated_at = NOW()
WHERE user_id = $1::uuid
AND id = $2::uuid
RETURNING
id::text,
user_id::text,
source_type,
source,
title,
status,
COALESCE(queue_position, 0),
progress_percent,
bytes_total,
bytes_downloaded,
download_speed_mbps,
COALESCE(eta_seconds, 0),
error_message,
retry_count,
created_at,
updated_at,
COALESCE(started_at, NOW()),
COALESCE(completed_at, NOW()),
COALESCE(cancelled_at, NOW());
`
const appendDownloadEventSQL = `
INSERT INTO download_events (
job_id,
status,
message,
progress_percent,
payload,
created_at
) VALUES (
$1::uuid,
$2::text,
$3::text,
$4::int,
$5::jsonb,
NOW()
);
`
const updateDownloadJobSQL = `
UPDATE download_jobs
SET
status = $3::text,
progress_percent = $4::int,
bytes_total = $5::bigint,
bytes_downloaded = $6::bigint,
download_speed_mbps = $7::double precision,
eta_seconds = CASE WHEN $8::int <= 0 THEN NULL ELSE $8::int END,
error_message = $9::text,
retry_count = $10::smallint,
started_at = CASE
WHEN $3::text IN ('preparing', 'downloading') AND started_at IS NULL THEN NOW()
ELSE started_at
END,
completed_at = CASE
WHEN $3::text = 'completed' THEN NOW()
ELSE completed_at
END,
cancelled_at = CASE
WHEN $3::text = 'cancelled' THEN NOW()
ELSE cancelled_at
END,
updated_at = NOW()
WHERE user_id = $1::uuid
AND id = $2::uuid
RETURNING
id::text,
user_id::text,
source_type,
source,
title,
status,
COALESCE(queue_position, 0),
progress_percent,
bytes_total,
bytes_downloaded,
download_speed_mbps,
COALESCE(eta_seconds, 0),
error_message,
retry_count,
created_at,
updated_at,
COALESCE(started_at, NOW()),
COALESCE(completed_at, NOW()),
COALESCE(cancelled_at, NOW());
`
const listDownloadEventsSQL = `
SELECT
id,
job_id::text,
status,
message,
progress_percent,
payload::text,
created_at
FROM download_events
WHERE job_id = $1::uuid
AND ($2::timestamptz IS NULL OR created_at > $2::timestamptz)
ORDER BY created_at DESC
LIMIT $3::int;
`
type DownloadRepository struct {
pool *pgxpool.Pool
}
func NewDownloadRepository(pool *pgxpool.Pool) *DownloadRepository {
return &DownloadRepository{pool: pool}
}
func (r *DownloadRepository) CreateJob(
ctx context.Context,
userID uuid.UUID,
input download.CreateInput,
) (download.Job, error) {
var job download.Job
err := r.pool.QueryRow(
ctx,
createDownloadJobSQL,
userID,
input.SourceType,
input.Source,
input.Title,
).Scan(
&job.ID,
&job.UserID,
&job.SourceType,
&job.Source,
&job.Title,
&job.Status,
&job.QueuePosition,
&job.ProgressPercent,
&job.BytesTotal,
&job.BytesDownloaded,
&job.DownloadSpeedMbps,
&job.EtaSeconds,
&job.ErrorMessage,
&job.RetryCount,
&job.CreatedAt,
&job.UpdatedAt,
)
if err != nil {
return download.Job{}, err
}
return job, nil
}
func (r *DownloadRepository) ListJobs(
ctx context.Context,
userID uuid.UUID,
params download.ListParams,
) ([]download.Job, error) {
rows, err := r.pool.Query(
ctx,
listDownloadJobsSQL,
userID,
params.Status,
params.Limit,
params.Offset,
)
if err != nil {
return nil, err
}
defer rows.Close()
jobs := make([]download.Job, 0, params.Limit)
for rows.Next() {
var job download.Job
if err := rows.Scan(
&job.ID,
&job.UserID,
&job.SourceType,
&job.Source,
&job.Title,
&job.Status,
&job.QueuePosition,
&job.ProgressPercent,
&job.BytesTotal,
&job.BytesDownloaded,
&job.DownloadSpeedMbps,
&job.EtaSeconds,
&job.ErrorMessage,
&job.RetryCount,
&job.CreatedAt,
&job.UpdatedAt,
&job.StartedAt,
&job.CompletedAt,
&job.CancelledAt,
); err != nil {
return nil, err
}
jobs = append(jobs, job)
}
return jobs, rows.Err()
}
func (r *DownloadRepository) GetJobByID(
ctx context.Context,
userID uuid.UUID,
jobID string,
) (download.Job, error) {
var job download.Job
err := r.pool.QueryRow(ctx, getDownloadJobByIDSQL, userID, jobID).Scan(
&job.ID,
&job.UserID,
&job.SourceType,
&job.Source,
&job.Title,
&job.Status,
&job.QueuePosition,
&job.ProgressPercent,
&job.BytesTotal,
&job.BytesDownloaded,
&job.DownloadSpeedMbps,
&job.EtaSeconds,
&job.ErrorMessage,
&job.RetryCount,
&job.CreatedAt,
&job.UpdatedAt,
&job.StartedAt,
&job.CompletedAt,
&job.CancelledAt,
)
if err != nil {
return download.Job{}, download.ErrNotFound
}
return job, nil
}
func (r *DownloadRepository) CancelJob(
ctx context.Context,
userID uuid.UUID,
jobID string,
) (download.Job, error) {
var job download.Job
err := r.pool.QueryRow(ctx, cancelDownloadJobSQL, userID, jobID).Scan(
&job.ID,
&job.UserID,
&job.SourceType,
&job.Source,
&job.Title,
&job.Status,
&job.QueuePosition,
&job.ProgressPercent,
&job.BytesTotal,
&job.BytesDownloaded,
&job.DownloadSpeedMbps,
&job.EtaSeconds,
&job.ErrorMessage,
&job.RetryCount,
&job.CreatedAt,
&job.UpdatedAt,
&job.StartedAt,
&job.CompletedAt,
&job.CancelledAt,
)
if err != nil {
return download.Job{}, download.ErrNotFound
}
return job, nil
}
func (r *DownloadRepository) ListEvents(
ctx context.Context,
userID uuid.UUID,
jobID string,
params download.EventParams,
) ([]download.Event, error) {
var after pgtype.Timestamptz
if params.After != "" {
t, err := time.Parse(time.RFC3339, params.After)
if err == nil {
after = pgtype.Timestamptz{
Time: t,
Valid: true,
}
}
}
rows, err := r.pool.Query(ctx, listDownloadEventsSQL, jobID, after, params.Limit)
if err != nil {
return nil, err
}
defer rows.Close()
events := make([]download.Event, 0, params.Limit)
for rows.Next() {
var event download.Event
if err := rows.Scan(
&event.ID,
&event.JobID,
&event.Status,
&event.Message,
&event.ProgressPercent,
&event.Payload,
&event.CreatedAt,
); err != nil {
return nil, err
}
events = append(events, event)
}
return events, rows.Err()
}
func (r *DownloadRepository) AppendEvent(
ctx context.Context,
jobID string,
event download.Event,
) error {
_, err := r.pool.Exec(
ctx,
appendDownloadEventSQL,
jobID,
event.Status,
event.Message,
event.ProgressPercent,
event.Payload,
)
return err
}
func (r *DownloadRepository) UpdateJob(
ctx context.Context,
userID uuid.UUID,
jobID string,
input download.UpdateInput,
) (download.Job, error) {
var job download.Job
err := r.pool.QueryRow(
ctx,
updateDownloadJobSQL,
userID,
jobID,
input.Status,
input.ProgressPercent,
input.BytesTotal,
input.BytesDownloaded,
input.DownloadSpeedMbps,
input.EtaSeconds,
input.ErrorMessage,
input.RetryCount,
).Scan(
&job.ID,
&job.UserID,
&job.SourceType,
&job.Source,
&job.Title,
&job.Status,
&job.QueuePosition,
&job.ProgressPercent,
&job.BytesTotal,
&job.BytesDownloaded,
&job.DownloadSpeedMbps,
&job.EtaSeconds,
&job.ErrorMessage,
&job.RetryCount,
&job.CreatedAt,
&job.UpdatedAt,
&job.StartedAt,
&job.CompletedAt,
&job.CancelledAt,
)
if err != nil {
return download.Job{}, download.ErrNotFound
}
return job, nil
}