Yimaru-BackEnd/internal/repository/mobile_app_versions.go
Yared Yemane a719c0daca Add mobile app version management and refresh profile field seeds.
Introduce admin CRUD and public version check APIs for Play Store/App Store releases with force or optional update policies, and update profile dropdown seed data for countries, regions, and learner profile fields.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-25 06:52:20 -07:00

229 lines
6.8 KiB
Go

package repository
import (
"Yimaru-Backend/internal/domain"
"Yimaru-Backend/internal/ports"
"context"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
func NewMobileAppVersionStore(s *Store) ports.MobileAppVersionStore { return s }
func mobileAppVersionToDomain(
id int64,
platform string,
versionName string,
versionCode int32,
updateType string,
releaseNotes pgtype.Text,
storeURL pgtype.Text,
minSupported pgtype.Int4,
status string,
createdAt pgtype.Timestamptz,
updatedAt pgtype.Timestamptz,
) domain.MobileAppVersion {
var minSupportedPtr *int32
if minSupported.Valid {
v := minSupported.Int32
minSupportedPtr = &v
}
return domain.MobileAppVersion{
ID: id,
Platform: platform,
VersionName: versionName,
VersionCode: versionCode,
UpdateType: updateType,
ReleaseNotes: fromPgText(releaseNotes),
StoreURL: fromPgText(storeURL),
MinSupportedVersionCode: minSupportedPtr,
Status: status,
CreatedAt: createdAt.Time,
UpdatedAt: timePtr(updatedAt),
}
}
func scanMobileAppVersion(row pgx.Row) (domain.MobileAppVersion, error) {
var (
id int64
platform string
versionName string
versionCode int32
updateType string
releaseNotes pgtype.Text
storeURL pgtype.Text
minSupported pgtype.Int4
status string
createdAt pgtype.Timestamptz
updatedAt pgtype.Timestamptz
)
if err := row.Scan(&id, &platform, &versionName, &versionCode, &updateType, &releaseNotes, &storeURL, &minSupported, &status, &createdAt, &updatedAt); err != nil {
return domain.MobileAppVersion{}, err
}
return mobileAppVersionToDomain(id, platform, versionName, versionCode, updateType, releaseNotes, storeURL, minSupported, status, createdAt, updatedAt), nil
}
const mobileAppVersionSelectCols = `
id, platform, version_name, version_code, update_type, release_notes, store_url,
min_supported_version_code, status, created_at, updated_at
`
func (s *Store) CreateMobileAppVersion(ctx context.Context, input domain.CreateMobileAppVersionInput) (domain.MobileAppVersion, error) {
updateType := domain.MobileAppUpdateTypeOptional
if input.UpdateType != nil {
updateType = *input.UpdateType
}
status := domain.MobileAppVersionStatusActive
if input.Status != nil {
status = *input.Status
}
row := s.conn.QueryRow(ctx, `
INSERT INTO mobile_app_versions (
platform, version_name, version_code, update_type, release_notes, store_url,
min_supported_version_code, status
)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING `+mobileAppVersionSelectCols,
input.Platform,
input.VersionName,
input.VersionCode,
updateType,
toPgText(input.ReleaseNotes),
toPgText(input.StoreURL),
toPgInt4(input.MinSupportedVersionCode),
status,
)
return scanMobileAppVersion(row)
}
func (s *Store) UpdateMobileAppVersion(ctx context.Context, id int64, input domain.UpdateMobileAppVersionInput) (domain.MobileAppVersion, error) {
releaseNotesSet := input.ReleaseNotes != nil
var releaseNotesValue pgtype.Text
if releaseNotesSet {
releaseNotesValue = toPgText(input.ReleaseNotes)
}
storeURLSet := input.StoreURL != nil
var storeURLValue pgtype.Text
if storeURLSet {
storeURLValue = toPgText(input.StoreURL)
}
minSupportedSet := input.MinSupportedVersionCode != nil
var minSupportedValue pgtype.Int4
if minSupportedSet {
minSupportedValue = toPgInt4(input.MinSupportedVersionCode)
}
row := s.conn.QueryRow(ctx, `
UPDATE mobile_app_versions
SET version_name = COALESCE($2, version_name),
version_code = COALESCE($3, version_code),
update_type = COALESCE($4, update_type),
release_notes = CASE WHEN $5::boolean THEN $6 ELSE release_notes END,
store_url = CASE WHEN $7::boolean THEN $8 ELSE store_url END,
min_supported_version_code = CASE WHEN $9::boolean THEN $10::int ELSE min_supported_version_code END,
status = COALESCE($11, status),
updated_at = NOW()
WHERE id = $1
RETURNING `+mobileAppVersionSelectCols,
id,
input.VersionName,
input.VersionCode,
input.UpdateType,
releaseNotesSet,
releaseNotesValue,
storeURLSet,
storeURLValue,
minSupportedSet,
minSupportedValue,
input.Status,
)
return scanMobileAppVersion(row)
}
func (s *Store) GetMobileAppVersionByID(ctx context.Context, id int64) (domain.MobileAppVersion, error) {
row := s.conn.QueryRow(ctx, `
SELECT `+mobileAppVersionSelectCols+`
FROM mobile_app_versions
WHERE id = $1
`, id)
return scanMobileAppVersion(row)
}
func (s *Store) ListMobileAppVersions(ctx context.Context, platform *string, status *string, limit int32, offset int32) ([]domain.MobileAppVersion, int64, error) {
rows, err := s.conn.Query(ctx, `
SELECT `+mobileAppVersionSelectCols+`
FROM mobile_app_versions
WHERE ($1::text IS NULL OR platform = $1)
AND ($2::text IS NULL OR status = $2)
ORDER BY platform ASC, version_code DESC, id DESC
LIMIT $3 OFFSET $4
`, toPgText(platform), toPgText(status), limit, offset)
if err != nil {
return nil, 0, err
}
defer rows.Close()
versions := make([]domain.MobileAppVersion, 0)
for rows.Next() {
var (
id int64
rowPlatform string
versionName string
versionCode int32
updateType string
releaseNotes pgtype.Text
storeURL pgtype.Text
minSupported pgtype.Int4
rowStatus string
createdAt pgtype.Timestamptz
updatedAt pgtype.Timestamptz
)
if err := rows.Scan(&id, &rowPlatform, &versionName, &versionCode, &updateType, &releaseNotes, &storeURL, &minSupported, &rowStatus, &createdAt, &updatedAt); err != nil {
return nil, 0, err
}
versions = append(versions, mobileAppVersionToDomain(id, rowPlatform, versionName, versionCode, updateType, releaseNotes, storeURL, minSupported, rowStatus, createdAt, updatedAt))
}
if err := rows.Err(); err != nil {
return nil, 0, err
}
var totalCount int64
if err := s.conn.QueryRow(ctx, `
SELECT COUNT(*)
FROM mobile_app_versions
WHERE ($1::text IS NULL OR platform = $1)
AND ($2::text IS NULL OR status = $2)
`, toPgText(platform), toPgText(status)).Scan(&totalCount); err != nil {
return nil, 0, err
}
return versions, totalCount, nil
}
func (s *Store) DeleteMobileAppVersion(ctx context.Context, id int64) error {
cmd, err := s.conn.Exec(ctx, `DELETE FROM mobile_app_versions WHERE id = $1`, id)
if err != nil {
return err
}
if cmd.RowsAffected() == 0 {
return pgx.ErrNoRows
}
return nil
}
func (s *Store) GetLatestActiveMobileAppVersion(ctx context.Context, platform string) (domain.MobileAppVersion, error) {
row := s.conn.QueryRow(ctx, `
SELECT `+mobileAppVersionSelectCols+`
FROM mobile_app_versions
WHERE platform = $1
AND status = 'ACTIVE'
ORDER BY version_code DESC, id DESC
LIMIT 1
`, platform)
return scanMobileAppVersion(row)
}