package appversions import ( "Yimaru-Backend/internal/domain" "Yimaru-Backend/internal/ports" "context" "errors" "fmt" "strings" "github.com/jackc/pgx/v5" ) type Service struct { store ports.MobileAppVersionStore } func NewService(store ports.MobileAppVersionStore) *Service { return &Service{store: store} } func normalizePlatform(raw string) (string, error) { value := strings.ToUpper(strings.TrimSpace(raw)) switch value { case domain.MobileAppPlatformAndroid, domain.MobileAppPlatformIOS: return value, nil default: return "", fmt.Errorf("platform must be one of %s, %s", domain.MobileAppPlatformAndroid, domain.MobileAppPlatformIOS) } } func normalizeUpdateType(raw *string) (string, error) { if raw == nil || strings.TrimSpace(*raw) == "" { return domain.MobileAppUpdateTypeOptional, nil } value := strings.ToUpper(strings.TrimSpace(*raw)) switch value { case domain.MobileAppUpdateTypeForce, domain.MobileAppUpdateTypeOptional: return value, nil default: return "", fmt.Errorf("update_type must be one of %s, %s", domain.MobileAppUpdateTypeForce, domain.MobileAppUpdateTypeOptional) } } func normalizeStatus(raw *string) (string, error) { if raw == nil || strings.TrimSpace(*raw) == "" { return domain.MobileAppVersionStatusActive, nil } value := strings.ToUpper(strings.TrimSpace(*raw)) switch value { case domain.MobileAppVersionStatusActive, domain.MobileAppVersionStatusInactive: return value, nil default: return "", fmt.Errorf("status must be one of %s, %s", domain.MobileAppVersionStatusActive, domain.MobileAppVersionStatusInactive) } } func optionalTrimmedText(raw *string) *string { if raw == nil { return nil } trimmed := strings.TrimSpace(*raw) if trimmed == "" { empty := "" return &empty } return &trimmed } func (s *Service) CreateMobileAppVersion(ctx context.Context, input domain.CreateMobileAppVersionInput) (domain.MobileAppVersion, error) { platform, err := normalizePlatform(input.Platform) if err != nil { return domain.MobileAppVersion{}, err } input.Platform = platform input.VersionName = strings.TrimSpace(input.VersionName) if input.VersionName == "" { return domain.MobileAppVersion{}, fmt.Errorf("version_name is required") } if input.VersionCode <= 0 { return domain.MobileAppVersion{}, fmt.Errorf("version_code must be a positive integer") } if input.MinSupportedVersionCode != nil && *input.MinSupportedVersionCode <= 0 { return domain.MobileAppVersion{}, fmt.Errorf("min_supported_version_code must be a positive integer") } if input.MinSupportedVersionCode != nil && *input.MinSupportedVersionCode > input.VersionCode { return domain.MobileAppVersion{}, fmt.Errorf("min_supported_version_code cannot exceed version_code") } updateType, err := normalizeUpdateType(input.UpdateType) if err != nil { return domain.MobileAppVersion{}, err } input.UpdateType = &updateType status, err := normalizeStatus(input.Status) if err != nil { return domain.MobileAppVersion{}, err } input.Status = &status input.ReleaseNotes = optionalTrimmedText(input.ReleaseNotes) input.StoreURL = optionalTrimmedText(input.StoreURL) return s.store.CreateMobileAppVersion(ctx, input) } func (s *Service) UpdateMobileAppVersion(ctx context.Context, id int64, input domain.UpdateMobileAppVersionInput) (domain.MobileAppVersion, error) { if id <= 0 { return domain.MobileAppVersion{}, fmt.Errorf("invalid app version id") } if input.VersionName != nil { trimmed := strings.TrimSpace(*input.VersionName) if trimmed == "" { return domain.MobileAppVersion{}, fmt.Errorf("version_name cannot be empty") } input.VersionName = &trimmed } if input.VersionCode != nil && *input.VersionCode <= 0 { return domain.MobileAppVersion{}, fmt.Errorf("version_code must be a positive integer") } if input.MinSupportedVersionCode != nil && *input.MinSupportedVersionCode <= 0 { return domain.MobileAppVersion{}, fmt.Errorf("min_supported_version_code must be a positive integer") } if input.UpdateType != nil { updateType, err := normalizeUpdateType(input.UpdateType) if err != nil { return domain.MobileAppVersion{}, err } input.UpdateType = &updateType } if input.Status != nil { status, err := normalizeStatus(input.Status) if err != nil { return domain.MobileAppVersion{}, err } input.Status = &status } input.ReleaseNotes = optionalTrimmedText(input.ReleaseNotes) input.StoreURL = optionalTrimmedText(input.StoreURL) updated, err := s.store.UpdateMobileAppVersion(ctx, id, input) if err != nil { return domain.MobileAppVersion{}, err } if updated.MinSupportedVersionCode != nil && *updated.MinSupportedVersionCode > updated.VersionCode { return domain.MobileAppVersion{}, fmt.Errorf("min_supported_version_code cannot exceed version_code") } return updated, nil } func (s *Service) GetMobileAppVersionByID(ctx context.Context, id int64) (domain.MobileAppVersion, error) { if id <= 0 { return domain.MobileAppVersion{}, fmt.Errorf("invalid app version id") } return s.store.GetMobileAppVersionByID(ctx, id) } func (s *Service) ListMobileAppVersions(ctx context.Context, platform *string, status *string, limit int32, offset int32) ([]domain.MobileAppVersion, int64, error) { if platform != nil { normalized, err := normalizePlatform(*platform) if err != nil { return nil, 0, err } platform = &normalized } if status != nil { normalized, err := normalizeStatus(status) if err != nil { return nil, 0, err } status = &normalized } if limit <= 0 { limit = 20 } if limit > 200 { limit = 200 } if offset < 0 { offset = 0 } return s.store.ListMobileAppVersions(ctx, platform, status, limit, offset) } func (s *Service) DeleteMobileAppVersion(ctx context.Context, id int64) error { if id <= 0 { return fmt.Errorf("invalid app version id") } return s.store.DeleteMobileAppVersion(ctx, id) } func (s *Service) CheckMobileAppVersion(ctx context.Context, platform string, clientVersionCode int32) (domain.MobileAppVersionCheckResult, error) { normalizedPlatform, err := normalizePlatform(platform) if err != nil { return domain.MobileAppVersionCheckResult{}, err } if clientVersionCode <= 0 { return domain.MobileAppVersionCheckResult{}, fmt.Errorf("version_code must be a positive integer") } latest, err := s.store.GetLatestActiveMobileAppVersion(ctx, normalizedPlatform) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return domain.MobileAppVersionCheckResult{ Platform: normalizedPlatform, ClientVersionCode: clientVersionCode, UpdateAvailable: false, }, nil } return domain.MobileAppVersionCheckResult{}, err } result := domain.MobileAppVersionCheckResult{ Platform: normalizedPlatform, ClientVersionCode: clientVersionCode, LatestVersionName: latest.VersionName, LatestVersionCode: latest.VersionCode, ReleaseNotes: latest.ReleaseNotes, StoreURL: latest.StoreURL, } if clientVersionCode >= latest.VersionCode { result.UpdateAvailable = false return result, nil } result.UpdateAvailable = true result.UpdateType = latest.UpdateType result.ForceUpdate = latest.UpdateType == domain.MobileAppUpdateTypeForce if latest.MinSupportedVersionCode != nil && clientVersionCode < *latest.MinSupportedVersionCode { result.ForceUpdate = true } return result, nil }