mirror of
https://github.com/Dvorinka/SEEN.git
synced 2026-06-04 20:43:03 +00:00
507 lines
9.3 KiB
Go
507 lines
9.3 KiB
Go
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
|
|
}
|