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>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import SwiftUI
|
||||
import Supabase
|
||||
|
||||
@MainActor
|
||||
class AppState: ObservableObject {
|
||||
@Published var isAuthenticated = false
|
||||
@Published var currentUser: User?
|
||||
@Published var windowState: WindowState = .closed
|
||||
|
||||
private var windowTimer: Timer?
|
||||
|
||||
enum WindowState { case closed, open, posted, missed }
|
||||
|
||||
init() {
|
||||
Task { await checkSession() }
|
||||
startWindowTimer()
|
||||
observeAuthChanges()
|
||||
}
|
||||
|
||||
// MARK: - Auth
|
||||
|
||||
func checkSession() async {
|
||||
do {
|
||||
let session = try await supabase.auth.session
|
||||
isAuthenticated = true
|
||||
await loadProfile(userId: session.user.id)
|
||||
} catch {
|
||||
isAuthenticated = false
|
||||
}
|
||||
}
|
||||
|
||||
func signIn(email: String, password: String) async throws {
|
||||
try await supabase.signIn(email: email, password: password)
|
||||
let session = try await supabase.auth.session
|
||||
isAuthenticated = true
|
||||
await loadProfile(userId: session.user.id)
|
||||
}
|
||||
|
||||
func signIn(username: String, password: String) async throws {
|
||||
try await supabase.signIn(username: username, password: password)
|
||||
let session = try await supabase.auth.session
|
||||
isAuthenticated = true
|
||||
await loadProfile(userId: session.user.id)
|
||||
}
|
||||
|
||||
func signUp(email: String, password: String, username: String, displayName: String) async throws {
|
||||
try await supabase.signUp(email: email, password: password, username: username, displayName: displayName)
|
||||
let session = try await supabase.auth.session
|
||||
isAuthenticated = true
|
||||
await loadProfile(userId: session.user.id)
|
||||
}
|
||||
|
||||
func signOut() {
|
||||
Task {
|
||||
try? await supabase.auth.signOut()
|
||||
}
|
||||
isAuthenticated = false
|
||||
currentUser = nil
|
||||
}
|
||||
|
||||
func deleteAccount() async throws {
|
||||
try await supabase.deleteAccount()
|
||||
isAuthenticated = false
|
||||
currentUser = nil
|
||||
}
|
||||
|
||||
private func loadProfile(userId: UUID) async {
|
||||
guard let profile = try? await supabase.getMyProfile() else { return }
|
||||
currentUser = User(
|
||||
id: profile.id.uuidString,
|
||||
username: profile.username,
|
||||
displayName: profile.displayName,
|
||||
bio: profile.bio,
|
||||
avatarURL: profile.avatarUrl.flatMap(URL.init),
|
||||
followerCount: 0,
|
||||
followingCount: 0,
|
||||
postCount: 0,
|
||||
isFollowing: false
|
||||
)
|
||||
}
|
||||
|
||||
private func observeAuthChanges() {
|
||||
Task {
|
||||
for await (event, session) in await supabase.auth.authStateChanges {
|
||||
switch event {
|
||||
case .signedIn:
|
||||
if let session {
|
||||
isAuthenticated = true
|
||||
await loadProfile(userId: session.user.id)
|
||||
}
|
||||
case .signedOut, .userDeleted:
|
||||
isAuthenticated = false
|
||||
currentUser = nil
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Window State
|
||||
|
||||
func updateWindowState() {
|
||||
let hour = Calendar.current.component(.hour, from: Date())
|
||||
guard hour >= 2 && hour < 5 else { windowState = .closed; return }
|
||||
|
||||
let hasPosted = UserDefaults.standard.object(forKey: "lastPostDate")
|
||||
.flatMap { $0 as? Date }
|
||||
.map { Calendar.current.isDateInToday($0) } ?? false
|
||||
windowState = hasPosted ? .posted : .open
|
||||
}
|
||||
|
||||
func markAsPosted() {
|
||||
UserDefaults.standard.set(Date(), forKey: "lastPostDate")
|
||||
updateWindowState()
|
||||
}
|
||||
|
||||
private func startWindowTimer() {
|
||||
updateWindowState()
|
||||
windowTimer = Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] _ in
|
||||
Task { @MainActor [weak self] in self?.updateWindowState() }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user