didvan-app/lib/models/user.dart

166 lines
6.0 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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<String, dynamic> json,
List<String> 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: <uuid>, firstName, lastName, phone, avatar, ...}`)
/// و هم با شکل Keycloak (attributes آرایه‌ای) کار می‌کند.
factory User.fromJson(Map<String, dynamic> 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<String, dynamic> 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,
);
}
}