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 VersionedEntity for automatic versioning
  • Uses JsonSerializable with snake_case field mapping
  • Static schemaName and typeDescriptor for 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 HttpVersionedEntityApi for 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

  1. VersionedEntity Pattern: Automatic versioning and audit trails with before/after JSONB snapshots
  2. Multi-Tab Layouts: Organize complex entity views (details, relationships, history, audit)
  3. Custom Form Fields: Extend with domain-specific fields like TemporaryPasswordField
  4. Immutable Fields: Prevent email changes after creation using enabled property
  5. Custom API Endpoints: Add domain-specific methods beyond standard CRUD
  6. Import/Export: Collection actions for bulk operations
  7. 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.

On this page