package repository import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "github.com/geo-platform/tenant-api/internal/ops/domain" ) var ErrDesktopClientReleaseNotFound = errors.New("desktop client release not found") type DesktopClientReleaseRepository struct { pool *pgxpool.Pool } func NewDesktopClientReleaseRepository(pool *pgxpool.Pool) *DesktopClientReleaseRepository { return &DesktopClientReleaseRepository{pool: pool} } const desktopClientReleaseColumns = ` id, platform, arch, channel, version, min_supported_version, download_source, oss_object_key, custom_download_url, file_name, file_size_bytes, sha256, updater_download_source, updater_oss_object_key, updater_custom_download_url, updater_file_name, updater_file_size_bytes, updater_sha256, release_notes, force_update, enabled, published_at, created_by, updated_by, created_at, updated_at` type DesktopClientReleaseFilter struct { Keyword string Platform string Channel string Enabled *bool Limit int Offset int } type DesktopClientReleaseTarget struct { Platform string Arch string Channel string } type UpsertDesktopClientReleaseInput struct { Platform string Arch string Channel string Version string MinSupportedVersion *string DownloadSource string OSSObjectKey *string CustomDownloadURL *string FileName *string FileSizeBytes *int64 SHA256 *string UpdaterDownloadSource *string UpdaterOSSObjectKey *string UpdaterCustomDownloadURL *string UpdaterFileName *string UpdaterFileSizeBytes *int64 UpdaterSHA256 *string ReleaseNotes *string ForceUpdate bool Enabled bool PublishedAtNow bool ActorID *int64 } func scanDesktopClientRelease(row pgx.Row) (*domain.DesktopClientRelease, error) { var item domain.DesktopClientRelease err := row.Scan( &item.ID, &item.Platform, &item.Arch, &item.Channel, &item.Version, &item.MinSupportedVersion, &item.DownloadSource, &item.OSSObjectKey, &item.CustomDownloadURL, &item.FileName, &item.FileSizeBytes, &item.SHA256, &item.UpdaterDownloadSource, &item.UpdaterOSSObjectKey, &item.UpdaterCustomDownloadURL, &item.UpdaterFileName, &item.UpdaterFileSizeBytes, &item.UpdaterSHA256, &item.ReleaseNotes, &item.ForceUpdate, &item.Enabled, &item.PublishedAt, &item.CreatedBy, &item.UpdatedBy, &item.CreatedAt, &item.UpdatedAt, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil, ErrDesktopClientReleaseNotFound } return nil, err } return &item, nil } func (r *DesktopClientReleaseRepository) List(ctx context.Context, f DesktopClientReleaseFilter) ([]domain.DesktopClientRelease, int64, error) { args := []any{} where := "1=1" if f.Keyword != "" { args = append(args, "%"+f.Keyword+"%") where += fmt.Sprintf( " AND (version ILIKE $%d OR platform ILIKE $%d OR arch ILIKE $%d OR channel ILIKE $%d OR COALESCE(file_name, '') ILIKE $%d OR COALESCE(oss_object_key, '') ILIKE $%d OR COALESCE(custom_download_url, '') ILIKE $%d OR COALESCE(updater_file_name, '') ILIKE $%d OR COALESCE(updater_oss_object_key, '') ILIKE $%d OR COALESCE(updater_custom_download_url, '') ILIKE $%d)", len(args), len(args), len(args), len(args), len(args), len(args), len(args), len(args), len(args), len(args), ) } if f.Platform != "" { args = append(args, f.Platform) where += fmt.Sprintf(" AND platform = $%d", len(args)) } if f.Channel != "" { args = append(args, f.Channel) where += fmt.Sprintf(" AND channel = $%d", len(args)) } if f.Enabled != nil { args = append(args, *f.Enabled) where += fmt.Sprintf(" AND enabled = $%d", len(args)) } var total int64 if err := r.pool.QueryRow(ctx, "SELECT COUNT(*) FROM ops.desktop_client_releases WHERE "+where, args...).Scan(&total); err != nil { return nil, 0, err } limit := f.Limit if limit <= 0 || limit > 200 { limit = 20 } offset := f.Offset if offset < 0 { offset = 0 } args = append(args, limit, offset) limitArg := len(args) - 1 offsetArg := len(args) rows, err := r.pool.Query(ctx, fmt.Sprintf(` SELECT %s FROM ops.desktop_client_releases WHERE %s ORDER BY enabled DESC, channel ASC, platform ASC, arch ASC, updated_at DESC, id DESC LIMIT $%d OFFSET $%d`, desktopClientReleaseColumns, where, limitArg, offsetArg), args...) if err != nil { return nil, 0, err } defer rows.Close() items := make([]domain.DesktopClientRelease, 0, limit) for rows.Next() { item, scanErr := scanDesktopClientRelease(rows) if scanErr != nil { return nil, 0, scanErr } items = append(items, *item) } return items, total, rows.Err() } func (r *DesktopClientReleaseRepository) GetByID(ctx context.Context, id int64) (*domain.DesktopClientRelease, error) { return scanDesktopClientRelease(r.pool.QueryRow(ctx, ` SELECT `+desktopClientReleaseColumns+` FROM ops.desktop_client_releases WHERE id = $1`, id)) } func (r *DesktopClientReleaseRepository) GetByTarget(ctx context.Context, target DesktopClientReleaseTarget) (*domain.DesktopClientRelease, error) { return scanDesktopClientRelease(r.pool.QueryRow(ctx, ` SELECT `+desktopClientReleaseColumns+` FROM ops.desktop_client_releases WHERE platform = $1 AND arch = $2 AND channel = $3`, target.Platform, target.Arch, target.Channel)) } func (r *DesktopClientReleaseRepository) FindEnabled(ctx context.Context, target DesktopClientReleaseTarget) (*domain.DesktopClientRelease, error) { return scanDesktopClientRelease(r.pool.QueryRow(ctx, ` SELECT `+desktopClientReleaseColumns+` FROM ops.desktop_client_releases WHERE platform = $1 AND channel = $2 AND enabled = TRUE AND (arch = $3 OR arch = 'universal') ORDER BY CASE WHEN arch = $3 THEN 0 ELSE 1 END, updated_at DESC, id DESC LIMIT 1`, target.Platform, target.Channel, target.Arch)) } func (r *DesktopClientReleaseRepository) ListEnabledByTarget(ctx context.Context, target DesktopClientReleaseTarget) ([]domain.DesktopClientRelease, error) { rows, err := r.pool.Query(ctx, ` SELECT `+desktopClientReleaseColumns+` FROM ops.desktop_client_releases WHERE platform = $1 AND channel = $2 AND enabled = TRUE AND (arch = $3 OR arch = 'universal') ORDER BY CASE WHEN arch = $3 THEN 0 ELSE 1 END, updated_at DESC, id DESC`, target.Platform, target.Channel, target.Arch) if err != nil { return nil, err } defer rows.Close() items := []domain.DesktopClientRelease{} for rows.Next() { item, scanErr := scanDesktopClientRelease(rows) if scanErr != nil { return nil, scanErr } items = append(items, *item) } return items, rows.Err() } func (r *DesktopClientReleaseRepository) ListEnabledByChannel(ctx context.Context, channel string) ([]domain.DesktopClientRelease, error) { rows, err := r.pool.Query(ctx, ` SELECT `+desktopClientReleaseColumns+` FROM ops.desktop_client_releases WHERE channel = $1 AND enabled = TRUE ORDER BY channel ASC, platform ASC, arch ASC, updated_at DESC, id DESC`, channel) if err != nil { return nil, err } defer rows.Close() items := []domain.DesktopClientRelease{} for rows.Next() { item, scanErr := scanDesktopClientRelease(rows) if scanErr != nil { return nil, scanErr } items = append(items, *item) } return items, rows.Err() } func (r *DesktopClientReleaseRepository) Create(ctx context.Context, in UpsertDesktopClientReleaseInput) (*domain.DesktopClientRelease, error) { return scanDesktopClientRelease(r.pool.QueryRow(ctx, ` INSERT INTO ops.desktop_client_releases ( platform, arch, channel, version, min_supported_version, download_source, oss_object_key, custom_download_url, file_name, file_size_bytes, sha256, updater_download_source, updater_oss_object_key, updater_custom_download_url, updater_file_name, updater_file_size_bytes, updater_sha256, release_notes, force_update, enabled, published_at, created_by, updated_by ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, CASE WHEN $21 THEN NOW() ELSE NULL END, $22, $22 ) RETURNING `+desktopClientReleaseColumns, in.Platform, in.Arch, in.Channel, in.Version, in.MinSupportedVersion, in.DownloadSource, in.OSSObjectKey, in.CustomDownloadURL, in.FileName, in.FileSizeBytes, in.SHA256, in.UpdaterDownloadSource, in.UpdaterOSSObjectKey, in.UpdaterCustomDownloadURL, in.UpdaterFileName, in.UpdaterFileSizeBytes, in.UpdaterSHA256, in.ReleaseNotes, in.ForceUpdate, in.Enabled, in.PublishedAtNow, in.ActorID)) } func (r *DesktopClientReleaseRepository) Update(ctx context.Context, id int64, in UpsertDesktopClientReleaseInput) (*domain.DesktopClientRelease, error) { return scanDesktopClientRelease(r.pool.QueryRow(ctx, ` UPDATE ops.desktop_client_releases SET platform = $1, arch = $2, channel = $3, version = $4, min_supported_version = $5, download_source = $6, oss_object_key = $7, custom_download_url = $8, file_name = $9, file_size_bytes = $10, sha256 = $11, updater_download_source = $12, updater_oss_object_key = $13, updater_custom_download_url = $14, updater_file_name = $15, updater_file_size_bytes = $16, updater_sha256 = $17, release_notes = $18, force_update = $19, enabled = $20, published_at = CASE WHEN $20 = TRUE AND published_at IS NULL THEN NOW() WHEN $20 = FALSE THEN NULL ELSE published_at END, updated_by = $21, updated_at = NOW() WHERE id = $22 RETURNING `+desktopClientReleaseColumns, in.Platform, in.Arch, in.Channel, in.Version, in.MinSupportedVersion, in.DownloadSource, in.OSSObjectKey, in.CustomDownloadURL, in.FileName, in.FileSizeBytes, in.SHA256, in.UpdaterDownloadSource, in.UpdaterOSSObjectKey, in.UpdaterCustomDownloadURL, in.UpdaterFileName, in.UpdaterFileSizeBytes, in.UpdaterSHA256, in.ReleaseNotes, in.ForceUpdate, in.Enabled, in.ActorID, id)) } func (r *DesktopClientReleaseRepository) SetEnabled(ctx context.Context, id int64, enabled bool, actorID *int64) (*domain.DesktopClientRelease, error) { return scanDesktopClientRelease(r.pool.QueryRow(ctx, ` UPDATE ops.desktop_client_releases SET enabled = $1, published_at = CASE WHEN $1 = TRUE AND published_at IS NULL THEN NOW() WHEN $1 = FALSE THEN NULL ELSE published_at END, updated_by = $2, updated_at = NOW() WHERE id = $3 RETURNING `+desktopClientReleaseColumns, enabled, actorID, id)) } func (r *DesktopClientReleaseRepository) Delete(ctx context.Context, id int64) error { cmd, err := r.pool.Exec(ctx, `DELETE FROM ops.desktop_client_releases WHERE id = $1`, id) if err != nil { return err } if cmd.RowsAffected() == 0 { return ErrDesktopClientReleaseNotFound } return nil }