Files
thoughts/ios/NightThoughts/Services/RealtimeService.swift
T
denshooter 5bc81d5b3b Initial commit: nightly iOS app + Supabase backend
iOS SwiftUI app with Supabase auth/realtime, Node.js backend,
Docker/Supabase self-hosted infrastructure, and APNs scheduler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 23:31:38 +02:00

87 lines
2.5 KiB
Swift

import Foundation
import Supabase
/// Verwaltet die Echtzeit-Verbindung für "Gerade Jetzt".
/// Neue Posts erscheinen sofort ohne Polling.
@MainActor
class RealtimeService: ObservableObject {
@Published var newPostsCount = 0
private var channel: RealtimeChannelV2?
private var onNewPost: ((Post) -> Void)?
func startListening(onNewPost: @escaping (Post) -> Void) async {
self.onNewPost = onNewPost
guard channel == nil else { return }
let ch = await supabase.channel("public:posts")
// Neue Posts in Echtzeit empfangen
let stream = await ch.postgresChange(
InsertAction.self,
schema: "public",
table: "posts"
)
await ch.subscribe()
self.channel = ch
// Stream im Hintergrund konsumieren
Task { [weak self] in
for await action in stream {
await self?.handleInsert(action)
}
}
}
func stopListening() async {
if let ch = channel {
await supabase.removeChannel(ch)
channel = nil
}
}
private func handleInsert(_ action: InsertAction) {
// Den neuen Post aus dem Record dekodieren
guard
let id = action.record["id"]?.stringValue,
let content = action.record["content"]?.stringValue,
let createdAt = action.record["created_at"]?.stringValue
.flatMap({ ISO8601DateFormatter().date(from: $0) }),
let userId = action.record["user_id"]?.stringValue,
let isAnon = action.record["is_anonymous"]?.boolValue
else { return }
let moodString = action.record["mood"]?.stringValue
let mood = moodString.flatMap(Mood.init(rawValue:))
let post = Post(
id: id,
author: User.anonymousPlaceholder, // Profil wird lazily nachgeladen
content: content,
mood: mood,
createdAt: createdAt,
resonanceCount: 0,
hasResonated: false,
commentCount: 0,
isAnonymous: isAnon,
nightOf: createdAt
)
newPostsCount += 1
onNewPost?(post)
}
}
// MARK: - User placeholder für Realtime (Profil wird nachgeladen)
extension User {
static let anonymousPlaceholder = User(
id: "anonymous",
username: "anonym",
displayName: "anonym",
bio: nil, avatarURL: nil,
followerCount: 0, followingCount: 0, postCount: 0, isFollowing: false
)
}