update database

This commit is contained in:
Phuoc Nguyen
2025-10-24 11:31:48 +07:00
parent f95fa9d0a6
commit c4272f9a21
126 changed files with 23528 additions and 2234 deletions

View File

@@ -0,0 +1,300 @@
import 'dart:convert';
import 'package:hive_ce/hive.dart';
import 'package:worker/core/constants/storage_constants.dart';
import 'package:worker/core/database/models/enums.dart';
part 'user_model.g.dart';
/// User Model
///
/// Hive CE model for caching user data locally.
/// Maps to the 'users' table in the database.
///
/// Type ID: 0
@HiveType(typeId: HiveTypeIds.userModel)
class UserModel extends HiveObject {
UserModel({
required this.userId,
required this.phoneNumber,
this.passwordHash,
required this.fullName,
this.email,
required this.role,
required this.status,
required this.loyaltyTier,
required this.totalPoints,
this.companyInfo,
this.cccd,
this.attachments,
this.address,
this.avatarUrl,
this.referralCode,
this.referredBy,
this.erpnextCustomerId,
required this.createdAt,
this.updatedAt,
this.lastLoginAt,
});
/// User ID (Primary Key)
@HiveField(0)
final String userId;
/// Phone number (unique, used for login)
@HiveField(1)
final String phoneNumber;
/// Password hash (stored encrypted)
@HiveField(2)
final String? passwordHash;
/// Full name of the user
@HiveField(3)
final String fullName;
/// Email address
@HiveField(4)
final String? email;
/// User role (customer, distributor, admin, staff)
@HiveField(5)
final UserRole role;
/// Account status (active, inactive, suspended, pending)
@HiveField(6)
final UserStatus status;
/// Loyalty tier (bronze, silver, gold, platinum, diamond, titan)
@HiveField(7)
final LoyaltyTier loyaltyTier;
/// Total accumulated loyalty points
@HiveField(8)
final int totalPoints;
/// Company information (JSON encoded)
/// Contains: company_name, tax_id, business_type, etc.
@HiveField(9)
final String? companyInfo;
/// Citizen ID (CCCD/CMND)
@HiveField(10)
final String? cccd;
/// Attachments (JSON encoded list)
/// Contains: identity_card_images, business_license, etc.
@HiveField(11)
final String? attachments;
/// Address
@HiveField(12)
final String? address;
/// Avatar URL
@HiveField(13)
final String? avatarUrl;
/// Referral code for this user
@HiveField(14)
final String? referralCode;
/// ID of user who referred this user
@HiveField(15)
final String? referredBy;
/// ERPNext customer ID for integration
@HiveField(16)
final String? erpnextCustomerId;
/// Account creation timestamp
@HiveField(17)
final DateTime createdAt;
/// Last update timestamp
@HiveField(18)
final DateTime? updatedAt;
/// Last login timestamp
@HiveField(19)
final DateTime? lastLoginAt;
// =========================================================================
// JSON SERIALIZATION
// =========================================================================
/// Create UserModel from JSON
factory UserModel.fromJson(Map<String, dynamic> json) {
return UserModel(
userId: json['user_id'] as String,
phoneNumber: json['phone_number'] as String,
passwordHash: json['password_hash'] as String?,
fullName: json['full_name'] as String,
email: json['email'] as String?,
role: UserRole.values.firstWhere(
(e) => e.name == (json['role'] as String),
orElse: () => UserRole.customer,
),
status: UserStatus.values.firstWhere(
(e) => e.name == (json['status'] as String),
orElse: () => UserStatus.pending,
),
loyaltyTier: LoyaltyTier.values.firstWhere(
(e) => e.name == (json['loyalty_tier'] as String),
orElse: () => LoyaltyTier.bronze,
),
totalPoints: json['total_points'] as int? ?? 0,
companyInfo: json['company_info'] != null
? jsonEncode(json['company_info'])
: null,
cccd: json['cccd'] as String?,
attachments: json['attachments'] != null
? jsonEncode(json['attachments'])
: null,
address: json['address'] as String?,
avatarUrl: json['avatar_url'] as String?,
referralCode: json['referral_code'] as String?,
referredBy: json['referred_by'] as String?,
erpnextCustomerId: json['erpnext_customer_id'] as String?,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: json['updated_at'] != null
? DateTime.parse(json['updated_at'] as String)
: null,
lastLoginAt: json['last_login_at'] != null
? DateTime.parse(json['last_login_at'] as String)
: null,
);
}
/// Convert UserModel to JSON
Map<String, dynamic> toJson() {
return {
'user_id': userId,
'phone_number': phoneNumber,
'password_hash': passwordHash,
'full_name': fullName,
'email': email,
'role': role.name,
'status': status.name,
'loyalty_tier': loyaltyTier.name,
'total_points': totalPoints,
'company_info': companyInfo != null ? jsonDecode(companyInfo!) : null,
'cccd': cccd,
'attachments': attachments != null ? jsonDecode(attachments!) : null,
'address': address,
'avatar_url': avatarUrl,
'referral_code': referralCode,
'referred_by': referredBy,
'erpnext_customer_id': erpnextCustomerId,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt?.toIso8601String(),
'last_login_at': lastLoginAt?.toIso8601String(),
};
}
// =========================================================================
// HELPER METHODS
// =========================================================================
/// Get company info as Map
Map<String, dynamic>? get companyInfoMap {
if (companyInfo == null) return null;
try {
return jsonDecode(companyInfo!) as Map<String, dynamic>;
} catch (e) {
return null;
}
}
/// Get attachments as List
List<dynamic>? get attachmentsList {
if (attachments == null) return null;
try {
return jsonDecode(attachments!) as List<dynamic>;
} catch (e) {
return null;
}
}
/// Check if user is active
bool get isActive => status == UserStatus.active;
/// Check if user is admin or staff
bool get isStaff => role == UserRole.admin || role == UserRole.staff;
/// Get user initials for avatar
String get initials {
final parts = fullName.trim().split(' ');
if (parts.length >= 2) {
return '${parts.first[0]}${parts.last[0]}'.toUpperCase();
}
return fullName.isNotEmpty ? fullName[0].toUpperCase() : '?';
}
// =========================================================================
// COPY WITH
// =========================================================================
/// Create a copy with updated fields
UserModel copyWith({
String? userId,
String? phoneNumber,
String? passwordHash,
String? fullName,
String? email,
UserRole? role,
UserStatus? status,
LoyaltyTier? loyaltyTier,
int? totalPoints,
String? companyInfo,
String? cccd,
String? attachments,
String? address,
String? avatarUrl,
String? referralCode,
String? referredBy,
String? erpnextCustomerId,
DateTime? createdAt,
DateTime? updatedAt,
DateTime? lastLoginAt,
}) {
return UserModel(
userId: userId ?? this.userId,
phoneNumber: phoneNumber ?? this.phoneNumber,
passwordHash: passwordHash ?? this.passwordHash,
fullName: fullName ?? this.fullName,
email: email ?? this.email,
role: role ?? this.role,
status: status ?? this.status,
loyaltyTier: loyaltyTier ?? this.loyaltyTier,
totalPoints: totalPoints ?? this.totalPoints,
companyInfo: companyInfo ?? this.companyInfo,
cccd: cccd ?? this.cccd,
attachments: attachments ?? this.attachments,
address: address ?? this.address,
avatarUrl: avatarUrl ?? this.avatarUrl,
referralCode: referralCode ?? this.referralCode,
referredBy: referredBy ?? this.referredBy,
erpnextCustomerId: erpnextCustomerId ?? this.erpnextCustomerId,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
);
}
@override
String toString() {
return 'UserModel(userId: $userId, fullName: $fullName, role: $role, tier: $loyaltyTier, points: $totalPoints)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UserModel && other.userId == userId;
}
@override
int get hashCode => userId.hashCode;
}

View File

@@ -0,0 +1,98 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class UserModelAdapter extends TypeAdapter<UserModel> {
@override
final typeId = 0;
@override
UserModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return UserModel(
userId: fields[0] as String,
phoneNumber: fields[1] as String,
passwordHash: fields[2] as String?,
fullName: fields[3] as String,
email: fields[4] as String?,
role: fields[5] as UserRole,
status: fields[6] as UserStatus,
loyaltyTier: fields[7] as LoyaltyTier,
totalPoints: (fields[8] as num).toInt(),
companyInfo: fields[9] as String?,
cccd: fields[10] as String?,
attachments: fields[11] as String?,
address: fields[12] as String?,
avatarUrl: fields[13] as String?,
referralCode: fields[14] as String?,
referredBy: fields[15] as String?,
erpnextCustomerId: fields[16] as String?,
createdAt: fields[17] as DateTime,
updatedAt: fields[18] as DateTime?,
lastLoginAt: fields[19] as DateTime?,
);
}
@override
void write(BinaryWriter writer, UserModel obj) {
writer
..writeByte(20)
..writeByte(0)
..write(obj.userId)
..writeByte(1)
..write(obj.phoneNumber)
..writeByte(2)
..write(obj.passwordHash)
..writeByte(3)
..write(obj.fullName)
..writeByte(4)
..write(obj.email)
..writeByte(5)
..write(obj.role)
..writeByte(6)
..write(obj.status)
..writeByte(7)
..write(obj.loyaltyTier)
..writeByte(8)
..write(obj.totalPoints)
..writeByte(9)
..write(obj.companyInfo)
..writeByte(10)
..write(obj.cccd)
..writeByte(11)
..write(obj.attachments)
..writeByte(12)
..write(obj.address)
..writeByte(13)
..write(obj.avatarUrl)
..writeByte(14)
..write(obj.referralCode)
..writeByte(15)
..write(obj.referredBy)
..writeByte(16)
..write(obj.erpnextCustomerId)
..writeByte(17)
..write(obj.createdAt)
..writeByte(18)
..write(obj.updatedAt)
..writeByte(19)
..write(obj.lastLoginAt);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,185 @@
import 'package:hive_ce/hive.dart';
import 'package:worker/core/constants/storage_constants.dart';
part 'user_session_model.g.dart';
/// User Session Model
///
/// Hive CE model for caching user session data locally.
/// Maps to the 'user_sessions' table in the database.
///
/// Type ID: 1
@HiveType(typeId: HiveTypeIds.userSessionModel)
class UserSessionModel extends HiveObject {
UserSessionModel({
required this.sessionId,
required this.userId,
required this.deviceId,
this.deviceType,
this.deviceName,
this.ipAddress,
this.userAgent,
this.refreshToken,
required this.expiresAt,
required this.createdAt,
this.lastActivity,
});
/// Session ID (Primary Key)
@HiveField(0)
final String sessionId;
/// User ID (Foreign Key to users)
@HiveField(1)
final String userId;
/// Device ID (unique identifier for the device)
@HiveField(2)
final String deviceId;
/// Device type (android, ios, web, etc.)
@HiveField(3)
final String? deviceType;
/// Device name (e.g., "Samsung Galaxy S21")
@HiveField(4)
final String? deviceName;
/// IP address of the device
@HiveField(5)
final String? ipAddress;
/// User agent string
@HiveField(6)
final String? userAgent;
/// Refresh token for session renewal
@HiveField(7)
final String? refreshToken;
/// Session expiration timestamp
@HiveField(8)
final DateTime expiresAt;
/// Session creation timestamp
@HiveField(9)
final DateTime createdAt;
/// Last activity timestamp
@HiveField(10)
final DateTime? lastActivity;
// =========================================================================
// JSON SERIALIZATION
// =========================================================================
/// Create UserSessionModel from JSON
factory UserSessionModel.fromJson(Map<String, dynamic> json) {
return UserSessionModel(
sessionId: json['session_id'] as String,
userId: json['user_id'] as String,
deviceId: json['device_id'] as String,
deviceType: json['device_type'] as String?,
deviceName: json['device_name'] as String?,
ipAddress: json['ip_address'] as String?,
userAgent: json['user_agent'] as String?,
refreshToken: json['refresh_token'] as String?,
expiresAt: DateTime.parse(json['expires_at'] as String),
createdAt: DateTime.parse(json['created_at'] as String),
lastActivity: json['last_activity'] != null
? DateTime.parse(json['last_activity'] as String)
: null,
);
}
/// Convert UserSessionModel to JSON
Map<String, dynamic> toJson() {
return {
'session_id': sessionId,
'user_id': userId,
'device_id': deviceId,
'device_type': deviceType,
'device_name': deviceName,
'ip_address': ipAddress,
'user_agent': userAgent,
'refresh_token': refreshToken,
'expires_at': expiresAt.toIso8601String(),
'created_at': createdAt.toIso8601String(),
'last_activity': lastActivity?.toIso8601String(),
};
}
// =========================================================================
// HELPER METHODS
// =========================================================================
/// Check if session is expired
bool get isExpired => DateTime.now().isAfter(expiresAt);
/// Check if session is valid (not expired)
bool get isValid => !isExpired;
/// Get session duration
Duration get duration => DateTime.now().difference(createdAt);
/// Get time until expiration
Duration get timeUntilExpiration => expiresAt.difference(DateTime.now());
/// Get session age
Duration get age => DateTime.now().difference(createdAt);
/// Get time since last activity
Duration? get timeSinceLastActivity {
if (lastActivity == null) return null;
return DateTime.now().difference(lastActivity!);
}
// =========================================================================
// COPY WITH
// =========================================================================
/// Create a copy with updated fields
UserSessionModel copyWith({
String? sessionId,
String? userId,
String? deviceId,
String? deviceType,
String? deviceName,
String? ipAddress,
String? userAgent,
String? refreshToken,
DateTime? expiresAt,
DateTime? createdAt,
DateTime? lastActivity,
}) {
return UserSessionModel(
sessionId: sessionId ?? this.sessionId,
userId: userId ?? this.userId,
deviceId: deviceId ?? this.deviceId,
deviceType: deviceType ?? this.deviceType,
deviceName: deviceName ?? this.deviceName,
ipAddress: ipAddress ?? this.ipAddress,
userAgent: userAgent ?? this.userAgent,
refreshToken: refreshToken ?? this.refreshToken,
expiresAt: expiresAt ?? this.expiresAt,
createdAt: createdAt ?? this.createdAt,
lastActivity: lastActivity ?? this.lastActivity,
);
}
@override
String toString() {
return 'UserSessionModel(sessionId: $sessionId, userId: $userId, isValid: $isValid)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UserSessionModel && other.sessionId == sessionId;
}
@override
int get hashCode => sessionId.hashCode;
}

View File

@@ -0,0 +1,71 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_session_model.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class UserSessionModelAdapter extends TypeAdapter<UserSessionModel> {
@override
final typeId = 1;
@override
UserSessionModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return UserSessionModel(
sessionId: fields[0] as String,
userId: fields[1] as String,
deviceId: fields[2] as String,
deviceType: fields[3] as String?,
deviceName: fields[4] as String?,
ipAddress: fields[5] as String?,
userAgent: fields[6] as String?,
refreshToken: fields[7] as String?,
expiresAt: fields[8] as DateTime,
createdAt: fields[9] as DateTime,
lastActivity: fields[10] as DateTime?,
);
}
@override
void write(BinaryWriter writer, UserSessionModel obj) {
writer
..writeByte(11)
..writeByte(0)
..write(obj.sessionId)
..writeByte(1)
..write(obj.userId)
..writeByte(2)
..write(obj.deviceId)
..writeByte(3)
..write(obj.deviceType)
..writeByte(4)
..write(obj.deviceName)
..writeByte(5)
..write(obj.ipAddress)
..writeByte(6)
..write(obj.userAgent)
..writeByte(7)
..write(obj.refreshToken)
..writeByte(8)
..write(obj.expiresAt)
..writeByte(9)
..write(obj.createdAt)
..writeByte(10)
..write(obj.lastActivity);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is UserSessionModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@@ -0,0 +1,314 @@
/// Domain Entity: User
///
/// Represents a user account in the Worker application.
/// Contains authentication, profile, and loyalty information.
library;
/// User role enum
enum UserRole {
/// Customer/worker user
customer,
/// Sales representative
sales,
/// System administrator
admin,
/// Accountant
accountant,
/// Designer
designer;
}
/// User status enum
enum UserStatus {
/// Account pending approval
pending,
/// Active account
active,
/// Suspended account
suspended,
/// Rejected account
rejected;
}
/// Loyalty tier enum
enum LoyaltyTier {
/// No tier
none,
/// Gold tier (entry level)
gold,
/// Platinum tier (mid level)
platinum,
/// Diamond tier (highest level)
diamond;
/// Get display name for tier
String get displayName {
switch (this) {
case LoyaltyTier.none:
return 'NONE';
case LoyaltyTier.gold:
return 'GOLD';
case LoyaltyTier.platinum:
return 'PLATINUM';
case LoyaltyTier.diamond:
return 'DIAMOND';
}
}
}
/// Company information
class CompanyInfo {
/// Company name
final String? name;
/// Tax identification number
final String? taxId;
/// Company address
final String? address;
/// Business type
final String? businessType;
/// Business license number
final String? licenseNumber;
const CompanyInfo({
this.name,
this.taxId,
this.address,
this.businessType,
this.licenseNumber,
});
/// Create from JSON map
factory CompanyInfo.fromJson(Map<String, dynamic> json) {
return CompanyInfo(
name: json['name'] as String?,
taxId: json['tax_id'] as String?,
address: json['address'] as String?,
businessType: json['business_type'] as String?,
licenseNumber: json['license_number'] as String?,
);
}
/// Convert to JSON map
Map<String, dynamic> toJson() {
return {
'name': name,
'tax_id': taxId,
'address': address,
'business_type': businessType,
'license_number': licenseNumber,
};
}
}
/// User Entity
///
/// Represents a complete user profile including:
/// - Authentication credentials
/// - Personal information
/// - Company details (if applicable)
/// - Loyalty program membership
/// - Referral information
class User {
/// Unique user identifier
final String userId;
/// Phone number (used for login)
final String phoneNumber;
/// Full name
final String fullName;
/// Email address
final String? email;
/// User role
final UserRole role;
/// Account status
final UserStatus status;
/// Current loyalty tier
final LoyaltyTier loyaltyTier;
/// Total loyalty points
final int totalPoints;
/// Company information (optional)
final CompanyInfo? companyInfo;
/// CCCD/ID card number
final String? cccd;
/// Attachment URLs (ID cards, licenses, etc.)
final List<String> attachments;
/// Address
final String? address;
/// Avatar URL
final String? avatarUrl;
/// Referral code (unique for this user)
final String? referralCode;
/// ID of user who referred this user
final String? referredBy;
/// ERPNext customer ID
final String? erpnextCustomerId;
/// Account creation timestamp
final DateTime createdAt;
/// Last update timestamp
final DateTime updatedAt;
/// Last login timestamp
final DateTime? lastLoginAt;
const User({
required this.userId,
required this.phoneNumber,
required this.fullName,
this.email,
required this.role,
required this.status,
required this.loyaltyTier,
required this.totalPoints,
this.companyInfo,
this.cccd,
required this.attachments,
this.address,
this.avatarUrl,
this.referralCode,
this.referredBy,
this.erpnextCustomerId,
required this.createdAt,
required this.updatedAt,
this.lastLoginAt,
});
/// Check if user is active
bool get isActive => status == UserStatus.active;
/// Check if user is pending approval
bool get isPending => status == UserStatus.pending;
/// Check if user has company info
bool get hasCompanyInfo => companyInfo != null && companyInfo!.name != null;
/// Check if user is an admin
bool get isAdmin => role == UserRole.admin;
/// Check if user is a customer
bool get isCustomer => role == UserRole.customer;
/// Get display name for user
String get displayName => fullName;
/// Copy with method for immutability
User copyWith({
String? userId,
String? phoneNumber,
String? fullName,
String? email,
UserRole? role,
UserStatus? status,
LoyaltyTier? loyaltyTier,
int? totalPoints,
CompanyInfo? companyInfo,
String? cccd,
List<String>? attachments,
String? address,
String? avatarUrl,
String? referralCode,
String? referredBy,
String? erpnextCustomerId,
DateTime? createdAt,
DateTime? updatedAt,
DateTime? lastLoginAt,
}) {
return User(
userId: userId ?? this.userId,
phoneNumber: phoneNumber ?? this.phoneNumber,
fullName: fullName ?? this.fullName,
email: email ?? this.email,
role: role ?? this.role,
status: status ?? this.status,
loyaltyTier: loyaltyTier ?? this.loyaltyTier,
totalPoints: totalPoints ?? this.totalPoints,
companyInfo: companyInfo ?? this.companyInfo,
cccd: cccd ?? this.cccd,
attachments: attachments ?? this.attachments,
address: address ?? this.address,
avatarUrl: avatarUrl ?? this.avatarUrl,
referralCode: referralCode ?? this.referralCode,
referredBy: referredBy ?? this.referredBy,
erpnextCustomerId: erpnextCustomerId ?? this.erpnextCustomerId,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User &&
other.userId == userId &&
other.phoneNumber == phoneNumber &&
other.fullName == fullName &&
other.email == email &&
other.role == role &&
other.status == status &&
other.loyaltyTier == loyaltyTier &&
other.totalPoints == totalPoints &&
other.cccd == cccd &&
other.address == address &&
other.avatarUrl == avatarUrl &&
other.referralCode == referralCode &&
other.referredBy == referredBy &&
other.erpnextCustomerId == erpnextCustomerId;
}
@override
int get hashCode {
return Object.hash(
userId,
phoneNumber,
fullName,
email,
role,
status,
loyaltyTier,
totalPoints,
cccd,
address,
avatarUrl,
referralCode,
referredBy,
erpnextCustomerId,
);
}
@override
String toString() {
return 'User(userId: $userId, phoneNumber: $phoneNumber, fullName: $fullName, '
'role: $role, status: $status, loyaltyTier: $loyaltyTier, totalPoints: $totalPoints)';
}
}

View File

@@ -0,0 +1,142 @@
/// Domain Entity: User Session
///
/// Represents an active user session with device and authentication information.
library;
/// User Session Entity
///
/// Contains information about an active user session:
/// - Device details
/// - Authentication tokens
/// - Session timing
class UserSession {
/// Unique session identifier
final String sessionId;
/// User ID associated with this session
final String userId;
/// Unique device identifier
final String deviceId;
/// Device type (ios, android, web)
final String? deviceType;
/// Device name
final String? deviceName;
/// IP address
final String? ipAddress;
/// User agent string
final String? userAgent;
/// Refresh token for renewing access
final String? refreshToken;
/// Session expiration timestamp
final DateTime expiresAt;
/// Session creation timestamp
final DateTime createdAt;
/// Last activity timestamp
final DateTime? lastActivity;
const UserSession({
required this.sessionId,
required this.userId,
required this.deviceId,
this.deviceType,
this.deviceName,
this.ipAddress,
this.userAgent,
this.refreshToken,
required this.expiresAt,
required this.createdAt,
this.lastActivity,
});
/// Check if session is expired
bool get isExpired => DateTime.now().isAfter(expiresAt);
/// Check if session is expiring soon (within 1 hour)
bool get isExpiringSoon {
final hoursUntilExpiry = expiresAt.difference(DateTime.now()).inHours;
return hoursUntilExpiry > 0 && hoursUntilExpiry <= 1;
}
/// Check if session is active
bool get isActive => !isExpired;
/// Get device display name
String get deviceDisplayName => deviceName ?? deviceType ?? 'Unknown Device';
/// Get time since last activity
Duration? get timeSinceLastActivity {
if (lastActivity == null) return null;
return DateTime.now().difference(lastActivity!);
}
/// Copy with method for immutability
UserSession copyWith({
String? sessionId,
String? userId,
String? deviceId,
String? deviceType,
String? deviceName,
String? ipAddress,
String? userAgent,
String? refreshToken,
DateTime? expiresAt,
DateTime? createdAt,
DateTime? lastActivity,
}) {
return UserSession(
sessionId: sessionId ?? this.sessionId,
userId: userId ?? this.userId,
deviceId: deviceId ?? this.deviceId,
deviceType: deviceType ?? this.deviceType,
deviceName: deviceName ?? this.deviceName,
ipAddress: ipAddress ?? this.ipAddress,
userAgent: userAgent ?? this.userAgent,
refreshToken: refreshToken ?? this.refreshToken,
expiresAt: expiresAt ?? this.expiresAt,
createdAt: createdAt ?? this.createdAt,
lastActivity: lastActivity ?? this.lastActivity,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UserSession &&
other.sessionId == sessionId &&
other.userId == userId &&
other.deviceId == deviceId &&
other.deviceType == deviceType &&
other.deviceName == deviceName &&
other.ipAddress == ipAddress &&
other.refreshToken == refreshToken;
}
@override
int get hashCode {
return Object.hash(
sessionId,
userId,
deviceId,
deviceType,
deviceName,
ipAddress,
refreshToken,
);
}
@override
String toString() {
return 'UserSession(sessionId: $sessionId, userId: $userId, deviceId: $deviceId, '
'deviceType: $deviceType, expiresAt: $expiresAt, isActive: $isActive)';
}
}