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:
denshooter
2026-04-23 23:31:38 +02:00
commit 5bc81d5b3b
80 changed files with 9958 additions and 0 deletions
+232
View File
@@ -0,0 +1,232 @@
import SwiftUI
/// Impressum + Datenschutzerklärung
/// Muss vor dem Launch von einem Anwalt geprüft/ergänzt werden!
/// Kosten: ca. 300500 einmalig für Impressum + AGB + DSGVO-Datenschutzerklärung
struct LegalView: View {
@Environment(\.dismiss) var dismiss
@State private var tab = 0
var body: some View {
NavigationStack {
ZStack {
Color.nightBase.ignoresSafeArea()
VStack(spacing: 0) {
// Tab Switcher
HStack(spacing: 0) {
ForEach(["impressum", "datenschutz", "nutzungsbedingungen"], id: \.self) { label in
let idx = ["impressum", "datenschutz", "nutzungsbedingungen"].firstIndex(of: label)!
Button(label) { tab = idx }
.font(.nightLabel(12, weight: tab == idx ? .semibold : .regular))
.foregroundColor(tab == idx ? .nightPrimary : .nightSecondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.overlay(
Rectangle()
.fill(tab == idx ? Color.nightPurple : .clear)
.frame(height: 2),
alignment: .bottom
)
}
}
.padding(.horizontal, 16)
.overlay(Rectangle().fill(Color.nightBorder).frame(height: 1), alignment: .bottom)
ScrollView {
VStack(alignment: .leading, spacing: 0) {
switch tab {
case 0: ImpressumContent()
case 1: DatenschutzContent()
default: NutzungsbedingungenContent()
}
}
.padding(20)
}
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Schließen") { dismiss() }
.foregroundColor(.nightSecondary)
}
}
}
.preferredColorScheme(.dark)
}
}
// MARK: - Impressum
struct ImpressumContent: View {
var body: some View {
LegalSection(title: "Impressum") {
// PFLICHTANGABEN vor Launch ausfüllen!
LegalParagraph(title: "Angaben gemäß § 5 TMG") {
"""
[DEIN NAME]
[STRASSE HAUSNUMMER]
[PLZ ORT]
Deutschland
⚠️ Vor Launch ausfüllen!
"""
}
LegalParagraph(title: "Kontakt") {
"""
E-Mail: legal@xxx.dk0.dev
⚠️ Vor Launch ausfüllen!
"""
}
LegalParagraph(title: "Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV") {
"[DEIN NAME], [ADRESSE]"
}
LegalParagraph(title: "Hinweis") {
"""
Diese App ist ein privates Projekt. Für die Richtigkeit, \
Vollständigkeit und Aktualität der Inhalte kann keine Gewähr übernommen werden.
"""
}
}
}
}
// MARK: - Datenschutz
struct DatenschutzContent: View {
var body: some View {
LegalSection(title: "Datenschutzerklärung") {
LegalParagraph(title: "⚠️ Hinweis") {
"""
Diese Datenschutzerklärung ist ein Entwurf und muss vor dem Launch \
von einem Datenschutzanwalt geprüft und vervollständigt werden. \
Kosten: ca. 300500€.
"""
}
LegalParagraph(title: "Verantwortlicher") {
"[DEIN NAME], [ADRESSE], [E-MAIL]"
}
LegalParagraph(title: "Welche Daten wir speichern") {
"""
• E-Mail-Adresse (für Account & Passwort-Reset)
• Benutzername und Anzeigename
• Posts, Reaktionen, Kommentare (Inhalte die du selbst erstellst)
• Push-Token (für Benachrichtigungen, optional)
• IP-Adresse in Server-Logs (max. 14 Tage)
"""
}
LegalParagraph(title: "Wofür wir Daten verwenden") {
"""
• Betrieb des Dienstes (Authentifizierung, Feed, Benachrichtigungen)
• Moderation (Meldungen von Inhalten)
• Keine Weitergabe an Dritte außer für den Betrieb notwendige Dienste
"""
}
LegalParagraph(title: "Serverstandort") {
"Alle Daten werden auf Servern in der EU gespeichert."
}
LegalParagraph(title: "Deine Rechte (DSGVO)") {
"""
• Auskunft über gespeicherte Daten: legal@xxx.dk0.dev
• Berichtigung falscher Daten
• Löschung: Account in den Einstellungen löschen — entfernt alle deine Daten sofort
• Datenübertragbarkeit: auf Anfrage per E-Mail
• Widerspruch gegen Verarbeitung: legal@xxx.dk0.dev
• Beschwerde bei der Datenschutzbehörde
"""
}
LegalParagraph(title: "Datenlöschung") {
"""
Posts werden 14 Stunden nach Erstellung aus dem öffentlichen Feed entfernt. \
Dein persönliches Tagebuch behältst du so lange du möchtest. \
Account-Löschung entfernt alle Daten dauerhaft und unwiderruflich.
"""
}
LegalParagraph(title: "Cookies / Tracking") {
"Wir verwenden keine Cookies, keine Tracker, keine Werbenetze."
}
}
}
}
// MARK: - Nutzungsbedingungen
struct NutzungsbedingungenContent: View {
var body: some View {
LegalSection(title: "Nutzungsbedingungen") {
LegalParagraph(title: "⚠️ Entwurf") {
"Diese Nutzungsbedingungen sind ein Entwurf und müssen vor dem Launch von einem Anwalt geprüft werden."
}
LegalParagraph(title: "Nutzung") {
"""
nightly ist ein Dienst für Personen ab 17 Jahren. \
Du bist für die Inhalte die du postest selbst verantwortlich.
"""
}
LegalParagraph(title: "Verbotene Inhalte") {
"""
Folgende Inhalte sind verboten:
• Hassrede, Diskriminierung, Bedrohung
• Belästigung oder Mobbing
• Illegale Inhalte jeglicher Art
• Spam oder kommerzielle Werbung
• Inhalte die andere Personen ohne deren Zustimmung zeigen
"""
}
LegalParagraph(title: "Moderation") {
"""
Gemeldete Inhalte werden geprüft und können ohne Vorankündigung entfernt werden. \
Bei schwerwiegenden Verstößen behalten wir uns die Sperrung des Accounts vor.
"""
}
LegalParagraph(title: "Haftungsausschluss") {
"""
Wir übernehmen keine Haftung für nutzergenerierte Inhalte. \
Der Dienst wird ohne Gewähr für Verfügbarkeit bereitgestellt.
"""
}
}
}
}
// MARK: - Reusable components
struct LegalSection<Content: View>: View {
let title: String
@ViewBuilder let content: Content
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Text(title)
.font(.nightTitle(22))
.foregroundColor(.nightPrimary)
.padding(.bottom, 4)
content
}
}
}
struct LegalParagraph: View {
let title: String
let body: String
init(title: String, _ body: () -> String) {
self.title = title
self.body = body()
}
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title)
.font(.nightLabel(14, weight: .semibold))
.foregroundColor(.nightPrimary)
Text(body)
.font(.nightBody(14))
.foregroundColor(.nightSecondary)
.lineSpacing(4)
.fixedSize(horizontal: false, vertical: true)
}
}
}