199 lines
6.1 KiB
Swift
199 lines
6.1 KiB
Swift
//
|
|
// User.swift
|
|
// AppClientBaseSwift
|
|
//
|
|
// User data model
|
|
// Model dữ liệu người dùng
|
|
//
|
|
|
|
import Foundation
|
|
|
|
// MARK: - User Model
|
|
// Model người dùng
|
|
|
|
/// User entity representing authenticated user
|
|
/// Entity người dùng đại diện cho user đã xác thực
|
|
struct User: Codable, Identifiable, Equatable {
|
|
|
|
/// Unique user identifier
|
|
/// Định danh người dùng duy nhất
|
|
let id: String
|
|
|
|
/// User email address
|
|
/// Địa chỉ email người dùng
|
|
let email: String
|
|
|
|
/// User display name
|
|
/// Tên hiển thị người dùng
|
|
let name: String
|
|
|
|
/// User avatar URL
|
|
/// URL ảnh đại diện người dùng
|
|
let avatarUrl: String?
|
|
|
|
/// User phone number
|
|
/// Số điện thoại người dùng
|
|
let phoneNumber: String?
|
|
|
|
/// Whether email is verified
|
|
/// Email đã được xác minh chưa
|
|
let isEmailVerified: Bool
|
|
|
|
/// Account creation date
|
|
/// Ngày tạo tài khoản
|
|
let createdAt: Date?
|
|
|
|
/// Last update date
|
|
/// Ngày cập nhật cuối
|
|
let updatedAt: Date?
|
|
|
|
// MARK: - CodingKeys
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
case id
|
|
case email
|
|
case name
|
|
case firstName
|
|
case lastName
|
|
case avatarUrl
|
|
case phoneNumber
|
|
case isEmailVerified = "emailConfirmed"
|
|
case createdAt
|
|
case updatedAt
|
|
}
|
|
|
|
// MARK: - Custom Decoding
|
|
|
|
/// Custom decoder to handle firstName + lastName from API
|
|
/// Custom decoder để xử lý firstName + lastName từ API
|
|
init(from decoder: Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
id = try container.decode(String.self, forKey: .id)
|
|
email = try container.decode(String.self, forKey: .email)
|
|
|
|
// Handle name from either "name" field or "firstName" + "lastName"
|
|
// Xử lý name từ field "name" hoặc "firstName" + "lastName"
|
|
if let fullName = try? container.decode(String.self, forKey: .name), !fullName.isEmpty {
|
|
name = fullName
|
|
} else {
|
|
let firstName = try container.decodeIfPresent(String.self, forKey: .firstName) ?? ""
|
|
let lastName = try container.decodeIfPresent(String.self, forKey: .lastName) ?? ""
|
|
name = "\(firstName) \(lastName)".trimmingCharacters(in: .whitespaces)
|
|
}
|
|
|
|
avatarUrl = try container.decodeIfPresent(String.self, forKey: .avatarUrl)
|
|
phoneNumber = try container.decodeIfPresent(String.self, forKey: .phoneNumber)
|
|
isEmailVerified = try container.decodeIfPresent(Bool.self, forKey: .isEmailVerified) ?? false
|
|
createdAt = try container.decodeIfPresent(Date.self, forKey: .createdAt)
|
|
updatedAt = try container.decodeIfPresent(Date.self, forKey: .updatedAt)
|
|
}
|
|
|
|
// MARK: - Custom Encoding
|
|
|
|
/// Custom encoder for Encodable conformance
|
|
/// Custom encoder cho Encodable conformance
|
|
func encode(to encoder: Encoder) throws {
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
try container.encode(id, forKey: .id)
|
|
try container.encode(email, forKey: .email)
|
|
try container.encode(name, forKey: .name)
|
|
try container.encodeIfPresent(avatarUrl, forKey: .avatarUrl)
|
|
try container.encodeIfPresent(phoneNumber, forKey: .phoneNumber)
|
|
try container.encode(isEmailVerified, forKey: .isEmailVerified)
|
|
try container.encodeIfPresent(createdAt, forKey: .createdAt)
|
|
try container.encodeIfPresent(updatedAt, forKey: .updatedAt)
|
|
}
|
|
|
|
// MARK: - Standard Init
|
|
|
|
/// Standard initializer for creating User instances
|
|
/// Initializer chuẩn để tạo User instances
|
|
init(
|
|
id: String,
|
|
email: String,
|
|
name: String,
|
|
avatarUrl: String? = nil,
|
|
phoneNumber: String? = nil,
|
|
isEmailVerified: Bool = false,
|
|
createdAt: Date? = nil,
|
|
updatedAt: Date? = nil
|
|
) {
|
|
self.id = id
|
|
self.email = email
|
|
self.name = name
|
|
self.avatarUrl = avatarUrl
|
|
self.phoneNumber = phoneNumber
|
|
self.isEmailVerified = isEmailVerified
|
|
self.createdAt = createdAt
|
|
self.updatedAt = updatedAt
|
|
}
|
|
}
|
|
|
|
// MARK: - User Extensions
|
|
// Các extension của User
|
|
|
|
extension User {
|
|
|
|
/// Get user initials for avatar placeholder
|
|
/// Lấy chữ cái đầu tên cho placeholder avatar
|
|
var initials: String {
|
|
let components = name.split(separator: " ")
|
|
let firstInitial = components.first?.prefix(1) ?? ""
|
|
let lastInitial = components.count > 1 ? components.last?.prefix(1) ?? "" : ""
|
|
return "\(firstInitial)\(lastInitial)".uppercased()
|
|
}
|
|
|
|
/// Get display name (first name only)
|
|
/// Lấy tên hiển thị (chỉ tên đầu)
|
|
var firstName: String {
|
|
String(name.split(separator: " ").first ?? Substring(name))
|
|
}
|
|
|
|
/// Get masked email for privacy display
|
|
/// Lấy email ẩn để hiển thị bảo mật
|
|
var maskedEmail: String {
|
|
email.maskedEmail
|
|
}
|
|
|
|
/// Get masked phone for privacy display
|
|
/// Lấy số điện thoại ẩn để hiển thị bảo mật
|
|
var maskedPhone: String? {
|
|
phoneNumber?.maskedPhone
|
|
}
|
|
}
|
|
|
|
// MARK: - Mock Data
|
|
// Dữ liệu mẫu
|
|
|
|
#if DEBUG
|
|
extension User {
|
|
|
|
/// Sample user for previews and testing
|
|
/// Người dùng mẫu cho preview và testing
|
|
static let sample = User(
|
|
id: "user_123456",
|
|
email: "john.doe@example.com",
|
|
name: "John Doe",
|
|
avatarUrl: "https://api.dicebear.com/7.x/avataaars/png?seed=john",
|
|
phoneNumber: "0912345678",
|
|
isEmailVerified: true,
|
|
createdAt: Date(),
|
|
updatedAt: Date()
|
|
)
|
|
|
|
/// Sample user without avatar
|
|
/// Người dùng mẫu không có avatar
|
|
static let sampleNoAvatar = User(
|
|
id: "user_789012",
|
|
email: "jane.doe@example.com",
|
|
name: "Jane Doe",
|
|
avatarUrl: nil,
|
|
phoneNumber: nil,
|
|
isEmailVerified: false,
|
|
createdAt: Date(),
|
|
updatedAt: nil
|
|
)
|
|
}
|
|
#endif
|