135 lines
3.2 KiB
Swift
135 lines
3.2 KiB
Swift
import Foundation
|
|
|
|
import FirebaseFirestore
|
|
|
|
struct User: Codable, Identifiable, Sendable {
|
|
let id: String
|
|
var email: String
|
|
var displayName: String
|
|
var photoUrl: String
|
|
var sex: String
|
|
var partnerId: String?
|
|
var coupleId: String?
|
|
var plan: String // "free" | "premium"
|
|
var createdAt: Date
|
|
var lastActiveAt: Date
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case email
|
|
case displayName
|
|
case photoUrl
|
|
case sex
|
|
case partnerId
|
|
case coupleId
|
|
case plan
|
|
case createdAt
|
|
case lastActiveAt
|
|
}
|
|
}
|
|
|
|
struct Couple: Codable, Identifiable, Sendable {
|
|
let id: String
|
|
var userIds: [String]
|
|
var inviteCode: String
|
|
var createdAt: Date
|
|
var currentQuestionId: String?
|
|
var streakCount: Int
|
|
var lastAnsweredAt: Date?
|
|
var activePackId: String?
|
|
|
|
// E2EE fields (optional — MVP can skip)
|
|
var encryptionVersion: Int // 0=plaintext, 1=migrating, 2=strict
|
|
var wrappedCoupleKey: String?
|
|
var kdfSalt: String?
|
|
var kdfParams: String?
|
|
var encryptionMigrationUsers: [String: Bool]?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case userIds
|
|
case inviteCode
|
|
case createdAt
|
|
case currentQuestionId
|
|
case streakCount
|
|
case lastAnsweredAt
|
|
case activePackId
|
|
case encryptionVersion
|
|
case wrappedCoupleKey
|
|
case kdfSalt
|
|
case kdfParams
|
|
case encryptionMigrationUsers
|
|
}
|
|
}
|
|
|
|
struct Invite: Codable, Identifiable, Sendable {
|
|
let id: String // = document ID (6-char code)
|
|
var code: String
|
|
var inviterUserId: String
|
|
var inviteeEmail: String?
|
|
var coupleId: String?
|
|
var status: String // "pending" | "accepted" | "expired"
|
|
var createdAt: Date
|
|
var expiresAt: Date
|
|
var acceptedAt: Date?
|
|
var acceptedByUserId: String?
|
|
|
|
// E2EE
|
|
var wrappedCoupleKey: String?
|
|
var kdfSalt: String?
|
|
var kdfParams: String?
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case code
|
|
case inviterUserId
|
|
case inviteeEmail
|
|
case coupleId
|
|
case status
|
|
case createdAt
|
|
case expiresAt
|
|
case acceptedAt
|
|
case acceptedByUserId
|
|
case wrappedCoupleKey
|
|
case kdfSalt
|
|
case kdfParams
|
|
}
|
|
}
|
|
|
|
struct Entitlement: Codable, Identifiable, Sendable {
|
|
let id: String
|
|
var userId: String
|
|
var source: String
|
|
var productId: String
|
|
var isActive: Bool
|
|
var expiresAt: Date?
|
|
var updatedAt: Date
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case userId
|
|
case source
|
|
case productId
|
|
case isActive
|
|
case expiresAt
|
|
case updatedAt
|
|
}
|
|
}
|
|
|
|
struct DailyQuestion: Codable, Identifiable, Sendable {
|
|
var id: String?
|
|
var questionId: String
|
|
var date: String // YYYY-MM-DD
|
|
var assignedAt: Date
|
|
var expiresAt: Date
|
|
}
|
|
|
|
struct DailyAnswer: Codable, Identifiable, Sendable {
|
|
var id: String? // = userId
|
|
var sealedAnswer: String? // "sealed:v1:{base64}"
|
|
var commitment: String? // "sha256:{urlsafe-base64}"
|
|
var questionType: String // "text" | "multiple_choice" | "scale"
|
|
var submittedAt: Date
|
|
}
|
|
|