166 lines
6.0 KiB
Dart
166 lines
6.0 KiB
Dart
// 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,
|
||
);
|
||
}
|
||
}
|