2026-05-25 19:23:49 +08:00
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 ) )
}
2026-06-10 13:13:06 +08:00
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 ( )
}
2026-05-25 19:23:49 +08:00
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
}