// lib/models/user.dart // // مدل کاربر. به‌روزرسانی شده برای پشتیبانی از بک‌اند جدید (Keycloak): // - بک‌اند جدید id را به صورت UUID (string) برمی‌گرداند => `keycloakId`. // - برای سازگاری با کدهای قدیمی پروژه که از `user.id` (int) استفاده می‌کنند، // فیلد `id` نگه داشته شده است (0 در صورت نبود معادل عددی). // - فیلدهای جدید بک‌اند: firstName, lastName, phone, avatar // معادل قدیمی: fullName, phoneNumber, photo // - Keycloak custom attribute ها (مثل phone/avatar) معمولا داخل // `attributes: { phone: ["..."], avatar: ["..."] }` به شکل آرایه // برمی‌گردند، نه رشته‌ی ساده. // - متد `fromJson` هر دو شکل قدیمی و جدید (و شکل آرایه‌ای Keycloak) را // بدون کرش قبول می‌کند. class User { /// Identifier عددی قدیمی (سازگاری). در بک‌اند جدید معمولا 0 خواهد بود. final int id; /// شناسه کاربر در Keycloak (UUID). در بک‌اند جدید کلید اصلی است. final String? keycloakId; final String phoneNumber; final String fullName; final String? username; final String? photo; final String? email; final String? firstName; final String? lastName; const User({ required this.id, required this.username, required this.phoneNumber, required this.photo, required this.fullName, required this.email, this.keycloakId, this.firstName, this.lastName, }); /// مقدار خام یک فیلد را به `String?` امن تبدیل می‌کند. /// - اگر رشته باشد همان را (یا null در صورت خالی بودن) برمی‌گرداند. /// - اگر آرایه باشد (قرارداد Keycloak برای attributes)، اولین عضو را /// می‌گیرد، به‌جای اینکه با یک `as String` نادرست کرش کند. static String? _asString(dynamic value) { if (value == null) return null; if (value is String) return value.isEmpty ? null : value; if (value is List) { return value.isEmpty ? null : _asString(value.first); } return value.toString(); } /// یک کلید را ابتدا در سطح بالای json و در صورت نبود، داخل /// `attributes` (قرارداد Keycloak: `attributes: { key: [...] }`) جست‌وجو /// می‌کند. static String? _lookup( Map json, List keys, ) { for (final key in keys) { final value = _asString(json[key]); if (value != null) return value; } final attributes = json['attributes']; if (attributes is Map) { for (final key in keys) { final value = _asString(attributes[key]); if (value != null) return value; } } return null; } /// سازنده‌ای که هم با شکل قدیمی (`{id, fullName, phoneNumber, photo, ...}`) /// و هم با شکل جدید (`{id: , firstName, lastName, phone, avatar, ...}`) /// و هم با شکل Keycloak (attributes آرایه‌ای) کار می‌کند. factory User.fromJson(Map json) { // id ممکن است int یا String (UUID) باشد. final dynamic rawId = json['id']; int intId = 0; String? keycloakId; if (rawId is int) { intId = rawId; } else if (rawId is String) { keycloakId = rawId; intId = int.tryParse(rawId) ?? 0; } // اولویت با فیلد جدید keycloakId اگر صریح ارسال شده باشد. final explicitKeycloakId = _asString(json['keycloakId']); if (explicitKeycloakId != null) { keycloakId = explicitKeycloakId; } // ساخت fullName: اگر مستقیم آمده استفاده می‌کنیم، // وگرنه از firstName + lastName می‌سازیم. final firstName = _lookup(json, const ['firstName']); final lastName = _lookup(json, const ['lastName']); final directFullName = _lookup(json, const ['fullName', 'name']); final fullName = directFullName ?? [ firstName ?? '', lastName ?? '', ].where((s) => s.isNotEmpty).join(' '); // phoneNumber: قدیم `phoneNumber`، جدید `phone` (احتمالا داخل attributes). final phone = _lookup(json, const ['phoneNumber', 'phone']) ?? ''; // photo: قدیم `photo`، جدید `avatar` (احتمالا داخل attributes). final photo = _lookup(json, const ['photo', 'avatar', 'picture']); // username: معمولا سطح بالا است، اما به‌عنوان fallback هم بررسی می‌شود. final username = _lookup(json, const ['username', 'preferred_username']); return User( id: intId, keycloakId: keycloakId, username: username, phoneNumber: phone, photo: photo, fullName: fullName, email: _lookup(json, const ['email']), firstName: firstName, lastName: lastName, ); } Map toJson() => { 'id': id, if (keycloakId != null) 'keycloakId': keycloakId, 'username': username, 'phoneNumber': phoneNumber, 'photo': photo, 'fullName': fullName, 'email': email, if (firstName != null) 'firstName': firstName, if (lastName != null) 'lastName': lastName, }; User copyWith({ int? id, String? keycloakId, String? username, String? phoneNumber, String? photo, String? fullName, String? email, String? firstName, String? lastName, }) { return User( id: id ?? this.id, keycloakId: keycloakId ?? this.keycloakId, username: username ?? this.username, phoneNumber: phoneNumber ?? this.phoneNumber, photo: photo, fullName: fullName ?? this.fullName, email: email ?? this.email, firstName: firstName ?? this.firstName, lastName: lastName ?? this.lastName, ); } }