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>
229 lines
6.8 KiB
Go
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)
|
|
}
|