User Management
Complete SSO user management system with authentication and access control
A complete user management system demonstrating authentication, roles, and access control patterns.
Overview
This example showcases:
- SSO user entity with authentication fields
- Multi-tab detail layouts (details, groups, versions, audit)
- Custom form fields (TemporaryPasswordField)
- Import/export actions
- Custom API endpoints for related data
Entity Design
// packages/sso_entities/lib/user.dart
import 'package:json_annotation/json_annotation.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
part 'user.g.dart';
@JsonSerializable(createToJson: true, fieldRename: FieldRename.snake)
class User extends VersionedEntity {
static const schemaName = 'sso.user';
static final typeDescriptor = TypeDescriptor(
schemaType: schemaName,
title: 'User',
fromJson: User.fromJson,
);
// Core fields
final String email;
final String? department;
final String? phone;
final DateTime? lastLoginAt;
User({
required super.id,
required super.name,
required this.email,
this.department,
this.phone,
this.lastLoginAt,
super.createdAt,
super.updatedAt,
super.createdBy,
super.updatedBy,
super.versionNumber = 1,
super.isActive = true,
}) : super(schemaType: schemaName, layout: null, modifiers: const []);
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
@override
Map<String, dynamic> toJson() => _$UserToJson(this);
}Key Features:
- Extends
VersionedEntityfor automatic versioning - Uses
JsonSerializablewith snake_case field mapping - Static
schemaNameandtypeDescriptorfor registration - Inherited audit fields:
id,createdAt,updatedAt,createdBy,updatedBy,versionNumber,isActive
Layouts
List Layouts
// features/feature_sso/lib/entities/user/user_config.dart (excerpt)
layouts: EntityLayoutDescriptor<User>(
list: [
// Table layout with multiple columns
EntityLayout<User>.table(
key: 'table',
title: 'Users Table',
columns: 4,
columnConfigs: [
TableColumnConfig(
key: 'name_email',
label: 'User',
sortable: true,
flex: 2,
),
TableColumnConfig(
key: 'department',
label: 'Department',
sortable: true,
),
TableColumnConfig(
key: 'status',
label: 'Status',
width: 120,
),
TableColumnConfig(
key: 'actions',
label: '',
width: 100,
),
],
),
// Grid layout with cards
EntityLayout<User>.grid(
key: 'grid',
title: 'Users Grid',
columns: 4,
aspectRatio: 1.5,
),
],
// ... detail layouts below
)Detail Layouts
detail: [
// Basic information tab
EntityLayout<User>.detail(
key: 'details',
title: 'User Details',
builder: (context, user) => UserDetailLayout(user: user),
),
// User groups tab (many-to-many relationship)
EntityLayout<User>.detail(
key: 'user_groups',
title: 'User Groups',
builder: (context, user) => StandardRelatedEntitiesTab<User, UserGroup>(
parent: user,
relationSegment: 'user-groups',
emptyMessage: 'This user is not assigned to any groups',
),
),
// Version history
EntityLayout<User>.detail(
key: 'versions',
title: 'Versions',
builder: (context, user) => EntityVersionWidget(entity: user),
),
// Audit trail
EntityLayout<User>.detail(
key: 'audit',
title: 'Audit Trail',
builder: (context, user) => EntityAuditWidget(entity: user),
),
]Form Configuration
// features/feature_sso/lib/entities/user/user_config.dart (excerpt)
form: EntityFormDescriptor<User>(
builder: (context, entity) => vf.Form(
title: entity == null ? 'Create User' : 'Edit User',
autovalidateMode: AutovalidateMode.onUserInteraction,
layout: vf.ColumnFormLayout(columns: 2, spacing: 24.0, runSpacing: 24.0),
items: [
// Email field (required, validated, unique check)
vf.TextField(
name: 'email',
label: 'Email',
initialValue: entity?.email,
validators: [
vf.requiredValidator,
vf.emailValidator,
],
enabled: entity == null, // Can't change after creation
),
// Name field
vf.TextField(
name: 'name',
label: 'Full Name',
initialValue: entity?.name,
validators: [vf.requiredValidator],
),
// Department field
vf.TextField(
name: 'department',
label: 'Department',
initialValue: entity?.department,
),
// Phone field
vf.TextField(
name: 'phone',
label: 'Phone',
initialValue: entity?.phone,
),
// Active status toggle
vf.BooleanField(
name: 'is_active',
label: 'Active',
initialValue: entity?.isActive ?? true,
),
],
),
)Form Features:
- Two-column layout for compact presentation
- Email validation with uniqueness checking
- Immutable fields (email disabled on edit)
- Boolean toggle for active status
Actions
// features/feature_sso/lib/entities/user/user_config.dart (excerpt)
actions: EntityActionsDescriptor<User>(
// Inline actions (edit, archive, unarchive)
inline: EntityActions.defaults<User>(),
// Collection actions
collection: [
EntityAction<User>(
key: 'import_users',
label: 'Import Users',
icon: Icons.upload,
onExecute: (context, users) async {
// Show CSV import dialog
final file = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['csv'],
);
if (file != null) {
// Parse and import CSV
await _importUsersFromCsv(context, file.files.first);
}
},
),
EntityAction<User>(
key: 'export_users',
label: 'Export Users',
icon: Icons.download,
onExecute: (context, users) async {
// Generate CSV
final csv = _generateUsersCsv(users);
// Download file
final fileName = 'users_${DateTime.now().millisecondsSinceEpoch}.csv';
await FileSaver.instance.saveFile(fileName, csv, 'csv');
},
),
],
)Action Types:
- Inline: Edit, archive/unarchive (per-entity actions)
- Collection: Import/export CSV (bulk operations)
API Implementation
// features/feature_sso/lib/entities/user/user_api.dart
class UserApi extends HttpVersionedEntityApi<User> {
UserApi()
: super(
entityType: 'users',
schemaType: User.schemaName,
pathBuilder: VersionedEndpointBuilder(prefix: 'sso/users'),
fromJson: User.fromJson,
);
// Custom endpoint: Get user's accessible areas
Future<List<Area>> getUserAreas(String userId) async {
final uri = pathBuilder.buildUri('/$userId/areas');
final response = await vyuh.network.get(uri, headers: defaultHeaders);
if (response.statusCode != 200) {
throw Exception('Failed to load areas: ${response.statusCode}');
}
final data = jsonDecode(response.body);
return (data as List).map((json) => Area.fromJson(json)).toList();
}
// Custom endpoint: Get user's accessible equipment
Future<List<Equipment>> getUserEquipment(String userId) async {
final uri = pathBuilder.buildUri('/$userId/equipments');
final response = await vyuh.network.get(uri, headers: defaultHeaders);
if (response.statusCode != 200) {
throw Exception('Failed to load equipment: ${response.statusCode}');
}
final data = jsonDecode(response.body);
return (data as List).map((json) => Equipment.fromJson(json)).toList();
}
// Password reset
Future<void> resetPassword(String email) async {
final uri = pathBuilder.buildUri('/reset-password');
await vyuh.network.post(
uri,
headers: defaultHeaders,
body: jsonEncode({'email': email}),
);
}
}API Features:
- Extends
HttpVersionedEntityApifor standard CRUD - Custom endpoints for related entities (areas, equipment)
- Password management methods
- Uses
pathBuilder.buildUri()to construct endpoint paths
Note: This assumes API endpoints are available at a base URL (e.g., http://localhost:3000 or your cloud endpoint) that will service these requests.
Key Takeaways
- VersionedEntity Pattern: Automatic versioning and audit trails with before/after JSONB snapshots
- Multi-Tab Layouts: Organize complex entity views (details, relationships, history, audit)
- Custom Form Fields: Extend with domain-specific fields like
TemporaryPasswordField - Immutable Fields: Prevent email changes after creation using
enabledproperty - Custom API Endpoints: Add domain-specific methods beyond standard CRUD
- Import/Export: Collection actions for bulk operations
- Server Validation: Enforce uniqueness and business rules at the API level
This pattern can be adapted for any SSO or user management system requiring authentication and access control.