Перейти к содержанию

Users API Endpoints

Version: 1.0.0

Overview

The Users API provides comprehensive admin functionality for managing user accounts, profiles, and wallets within the Saga platform. This API enables administrators to perform CRUD operations on users, search for accounts, manage wallets, and clean up test data.

Key Features

  • User Management: Complete CRUD operations for user accounts
  • Wallet Integration: View and manage user wallets (personal and deposit addresses)
  • Search & Discovery: Email-based search with autocomplete suggestions
  • Admin Profile: Retrieve current administrator profile information
  • Test Data Management: Cleanup utilities for development environments
  • Canonical Architecture: Uses ComprehensiveUserService for single source of truth

Architecture

Clean Architecture Layers:

  • AdminUserManagementRouter - HTTP routing and request handling
  • ComprehensiveUserService (Canonical) - Business logic for user operations
  • CanonicalUserRepository - Data persistence layer
  • AdminService - Legacy admin operations (being phased out)

Security:

  • All endpoints require admin JWT token authentication
  • Admin middleware enforced at router level
  • Test data cleanup restricted to development environments only

Table of Contents

Admin Endpoints

  1. Get Users List
  2. Create User
  3. Get User Details
  4. Update User
  5. Get User Wallets
  6. Get Admin Profile
  7. Search Users by Email
  8. Delete Test Data
  9. Cleanup Test Data

Use Cases


Admin Endpoints

1. Get Users List: `GET /api/admin/users`

Retrieves paginated list of all users with optional filtering by status and email search. Supports querying specific user by ID through `userId` parameter.

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token
  • Header: `Authorization: Bearer `

Query Parameters

Parameter Type Required Default Description
`page` integer No 1 Page number (1-indexed)
`limit` integer No 50 Results per page (max 100)
`status` string No - Filter by user status (active, inactive, suspended)
`search` string No - Search by email (partial match)
`userId` string No - Get specific user by ID (overrides pagination)

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "users": [
      {
        "id": "user_abc123",
        "email": "user@example.com",
        "status": "active",
        "role": "user",
        "totalInvestments": 0,
        "totalAmount": "0.00",
        "createdAt": "2025-10-06T10:00:00Z",
        "updatedAt": "2025-10-06T10:00:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 50,
      "total": 100,
      "totalPages": 2
    }
  },
  "message": "Список пользователей",
  "trace_id": "trace_xyz789"
}

Response: User by ID (200 OK)

{
  "success": true,
  "data": {
    "users": [
      {
        "id": "user_abc123",
        "email": "user@example.com",
        "status": "active",
        "role": "user",
        "totalInvestments": 0,
        "totalAmount": "0.00",
        "createdAt": "2025-10-06T10:00:00Z",
        "updatedAt": "2025-10-06T10:00:00Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 1,
      "total": 1,
      "totalPages": 1
    }
  },
  "message": "Пользователь найден"
}

cURL Examples

# Get all users with pagination
curl -X GET "https://api.saga.surf/api/admin/users?page=1&limit=20" \\
  -H "Authorization: Bearer <admin_token>"

# Filter by status
curl -X GET "https://api.saga.surf/api/admin/users?status=active&page=1&limit=50" \\
  -H "Authorization: Bearer <admin_token>"

# Search by email
curl -X GET "https://api.saga.surf/api/admin/users?search=john&page=1" \\
  -H "Authorization: Bearer <admin_token>"

# Get specific user by ID
curl -X GET "https://api.saga.surf/api/admin/users?userId=user_abc123" \\
  -H "Authorization: Bearer <admin_token>"

TypeScript/React Example

interface AdminUser {
  id: string;
  email: string;
  status: string;
  role: string;
  totalInvestments: number;
  totalAmount: string;
  createdAt: string;
  updatedAt: string;
}

interface GetUsersParams {
  page?: number;
  limit?: number;
  status?: string;
  search?: string;
  userId?: string;
}

const getUsers = async (
  params: GetUsersParams = {}
): Promise<{ users: AdminUser[]; pagination: any }> => {
  const adminToken = localStorage.getItem('admin_auth_token');

  const queryParams = new URLSearchParams();
  if (params.page) queryParams.set('page', params.page.toString());
  if (params.limit) queryParams.set('limit', params.limit.toString());
  if (params.status) queryParams.set('status', params.status);
  if (params.search) queryParams.set('search', params.search);
  if (params.userId) queryParams.set('userId', params.userId);

  const response = await fetch(
    \`/api/admin/users?\${queryParams}\`,
    {
      headers: {
        'Authorization': \`Bearer \${adminToken}\`
      }
    }
  );

  const { data } = await response.json();
  return data;
};

2. Create User: `POST /api/admin/users`

Creates a new user account (primarily for testing purposes). Uses canonical ComprehensiveUserService for user creation.

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Request Body

{
  "id": "user_test_123",
  "email": "newuser@saga-test.com",
  "status": "active",
  "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
}

Fields:

  • `id` (string, optional): User ID (auto-generated if not provided)
  • `email` (string, required): User email address
  • `status` (string, optional): Initial status (default: "active")
  • `walletAddress` (string, optional): Ethereum wallet address

Response: Success (201 Created)

{
  "success": true,
  "data": {
    "id": "user_test_123",
    "email": "newuser@saga-test.com",
    "status": "active",
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "createdAt": "2025-10-06T10:30:00Z",
    "updatedAt": "2025-10-06T10:30:00Z",
    "profile": null,
    "permissions": []
  },
  "message": "Пользователь создан через canonical service",
  "trace_id": "trace_abc123"
}

Response: Validation Error (400 Bad Request)

{
  "success": false,
  "error": {
    "message": "Ошибка валидации данных пользователя",
    "code": "VALIDATION_ERROR",
    "details": "Email обязателен"
  }
}

cURL Example

curl -X POST https://api.saga.surf/api/admin/users \\
  -H "Authorization: Bearer <admin_token>" \\
  -H "Content-Type: application/json" \\
  -d '{
    "email": "newuser@saga-test.com",
    "status": "active",
    "walletAddress": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
  }'

TypeScript/React Example

interface CreateUserRequest {
  email: string;
  status?: string;
  walletAddress?: string;
}

const createUser = async (
  request: CreateUserRequest
): Promise<any> => {
  const adminToken = localStorage.getItem('admin_auth_token');

  const response = await fetch('/api/admin/users', {
    method: 'POST',
    headers: {
      'Authorization': \`Bearer \${adminToken}\`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(request)
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error?.message || 'Failed to create user');
  }

  const { data } = await response.json();
  return data;
};

3. Get User Details: `GET /api/admin/users/:id`

Retrieves detailed information about a specific user by ID.

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Path Parameters

Parameter Type Required Description
`id` string Yes User ID

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "user": {
      "id": "user_abc123",
      "email": "user@example.com",
      "status": "active",
      "walletAddress": "",
      "createdAt": "2025-10-06T10:00:00Z",
      "updatedAt": "2025-10-06T10:00:00Z"
    }
  },
  "message": "Информация о пользователе получена"
}

Response: Not Found (404)

{
  "success": false,
  "error": {
    "message": "Пользователь не найден",
    "code": "USER_NOT_FOUND"
  }
}

cURL Example

curl -X GET https://api.saga.surf/api/admin/users/user_abc123 \\
  -H "Authorization: Bearer <admin_token>"

4. Update User: `PUT /api/admin/users/:id`

Updates user information (email and/or status).

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Path Parameters

Parameter Type Required Description
`id` string Yes User ID

Request Body

{
  "email": "newemail@example.com",
  "status": "active"
}

Fields (all optional):

  • `email` (string): New email address
  • `status` (string): New status (active, inactive, suspended)

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "userId": "user_abc123",
    "email": "newemail@example.com",
    "updatedProfile": {
      "id": "user_abc123",
      "email": "newemail@example.com",
      "status": "active",
      "walletAddress": "",
      "createdAt": "2025-10-06T10:00:00Z",
      "updatedAt": "2025-10-06T11:00:00Z",
      "profile": null,
      "permissions": []
    }
  },
  "message": "Пользователь успешно обновлен"
}

Response: Not Found (404)

{
  "success": false,
  "error": {
    "message": "Пользователь не найден",
    "code": "USER_NOT_FOUND"
  }
}

cURL Example

curl -X PUT https://api.saga.surf/api/admin/users/user_abc123 \\
  -H "Authorization: Bearer <admin_token>" \\
  -H "Content-Type: application/json" \\
  -d '{
    "email": "newemail@example.com",
    "status": "active"
  }'

TypeScript/React Example

interface UpdateUserRequest {
  email?: string;
  status?: string;
}

const updateUser = async (
  userId: string,
  request: UpdateUserRequest
): Promise<any> => {
  const adminToken = localStorage.getItem('admin_auth_token');

  const response = await fetch(\`/api/admin/users/\${userId}\`, {
    method: 'PUT',
    headers: {
      'Authorization': \`Bearer \${adminToken}\`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(request)
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error?.message || 'Failed to update user');
  }

  const { data } = await response.json();
  return data;
};

5. Get User Wallets: `GET /api/admin/users/:id/wallets`

Retrieves all wallets associated with a specific user (personal wallets and deposit addresses).

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Path Parameters

Parameter Type Required Description
`id` string Yes User ID

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "user": {
      "id": "user_abc123",
      "email": "user@example.com",
      "status": "active",
      "personalWallets": [
        {
          "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
          "networkId": 1337,
          "type": "personal",
          "createdAt": "2025-10-06T10:00:00Z"
        }
      ],
      "depositWallets": [
        {
          "address": "0x123456789abcdef...",
          "networkId": 1337,
          "type": "deposit",
          "derivationPath": "m/44'/60'/0'/0/0",
          "addressIndex": 0,
          "createdAt": "2025-10-06T10:00:00Z"
        }
      ]
    }
  },
  "message": "Информация о пользователе и кошельках получена"
}

Response: Not Found (404)

{
  "success": false,
  "error": {
    "message": "Пользователь не найден",
    "code": "USER_NOT_FOUND"
  }
}

cURL Example

curl -X GET https://api.saga.surf/api/admin/users/user_abc123/wallets \\
  -H "Authorization: Bearer <admin_token>"

TypeScript/React Example

interface Wallet {
  address: string;
  networkId: number;
  type: string;
  derivationPath?: string;
  addressIndex?: number;
  createdAt: string;
}

interface UserWithWallets {
  id: string;
  email: string;
  status: string;
  personalWallets: Wallet[];
  depositWallets: Wallet[];
}

const getUserWallets = async (
  userId: string
): Promise<UserWithWallets> => {
  const adminToken = localStorage.getItem('admin_auth_token');

  const response = await fetch(
    \`/api/admin/users/\${userId}/wallets\`,
    {
      headers: {
        'Authorization': \`Bearer \${adminToken}\`
      }
    }
  );

  const { data } = await response.json();
  return data.user;
};

6. Get Admin Profile: `GET /api/admin/profile`

Retrieves the profile information of the currently authenticated administrator.

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "id": "admin_123",
    "email": "admin@saga.surf",
    "role": "superadmin",
    "permissions": [
      "users:read",
      "users:write",
      "investments:read",
      "investments:write"
    ],
    "status": "active",
    "createdAt": "<RFC3339_TIMESTAMP>",
    "updatedAt": "<RFC3339_TIMESTAMP>"
  },
  "message": "Admin profile retrieved successfully"
}

Response: Unauthorized (401)

{
  "success": false,
  "error": {
    "message": "Требуется администраторская авторизация",
    "code": "UNAUTHORIZED"
  }
}

cURL Example

curl -X GET https://api.saga.surf/api/admin/profile \\
  -H "Authorization: Bearer <admin_token>"

TypeScript/React Example

interface AdminProfile {
  id: string;
  email: string;
  role: string;
  permissions: string[];
  status: string;
  createdAt: string;
  updatedAt: string;
}

const getAdminProfile = async (): Promise<AdminProfile> => {
  const adminToken = localStorage.getItem('admin_auth_token');

  const response = await fetch('/api/admin/profile', {
    headers: {
      'Authorization': \`Bearer \${adminToken}\`
    }
  });

  if (!response.ok) {
    throw new Error('Failed to fetch admin profile');
  }

  const { data } = await response.json();
  return data;
};

// Usage in component
const AdminProfileCard: React.FC = () => {
  const [profile, setProfile] = useState<AdminProfile | null>(null);

  useEffect(() => {
    const fetchProfile = async () => {
      try {
        const data = await getAdminProfile();
        setProfile(data);
      } catch (error) {
        console.error('Failed to load profile:', error);
      }
    };

    fetchProfile();
  }, []);

  if (!profile) return <div>Loading...</div>;

  return (
    <div className="admin-profile">
      <h2>{profile.email}</h2>
      <p>Role: {profile.role}</p>
      <p>Permissions: {profile.permissions.join(', ')}</p>
    </div>
  );
};

7. Search Users by Email: `GET /api/admin/search/users`

Searches for users by email with autocomplete suggestions (maximum 10 results).

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Query Parameters

Parameter Type Required Description
`email` string Yes Email search query (minimum 1 character)

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "suggestions": [
      {
        "id": "user_abc123",
        "email": "john@example.com",
        "label": "john@example.com (ID: user_abc...)"
      },
      {
        "id": "user_xyz789",
        "email": "johnny@example.com",
        "label": "johnny@example.com (ID: user_xyz...)"
      }
    ],
    "count": 2
  },
  "message": "Email suggestions retrieved"
}

Response: Validation Error (400 Bad Request)

{
  "success": false,
  "error": {
    "message": "Email параметр обязателен",
    "code": "MISSING_EMAIL"
  }
}

cURL Example

curl -X GET "https://api.saga.surf/api/admin/search/users?email=john" \\
  -H "Authorization: Bearer <admin_token>"

TypeScript/React Example

interface EmailSuggestion {
  id: string;
  email: string;
  label: string;
}

const searchUsersByEmail = async (
  email: string
): Promise<EmailSuggestion[]> => {
  const adminToken = localStorage.getItem('admin_auth_token');

  const response = await fetch(
    \`/api/admin/search/users?email=\${encodeURIComponent(email)}\`,
    {
      headers: {
        'Authorization': \`Bearer \${adminToken}\`
      }
    }
  );

  const { data } = await response.json();
  return data.suggestions;
};

// Autocomplete component
const UserEmailAutocomplete: React.FC = () => {
  const [query, setQuery] = useState('');
  const [suggestions, setSuggestions] = useState<EmailSuggestion[]>([]);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    const fetchSuggestions = async () => {
      if (query.length < 1) {
        setSuggestions([]);
        return;
      }

      setLoading(true);
      try {
        const results = await searchUsersByEmail(query);
        setSuggestions(results);
      } catch (error) {
        console.error('Search failed:', error);
      } finally {
        setLoading(false);
      }
    };

    const debounce = setTimeout(fetchSuggestions, 300);
    return () => clearTimeout(debounce);
  }, [query]);

  return (
    <div className="autocomplete">
      <input
        type="text"
        placeholder="Search by email..."
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      {loading && <div>Loading...</div>}
      {suggestions.length > 0 && (
        <ul className="suggestions">
          {suggestions.map((s) => (
            <li key={s.id} onClick={() => setQuery(s.email)}>
              {s.label}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
};

8. Delete Test Data: `DELETE /api/admin/test-data`

Deletes all test users from the database (development environments only).

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Security

  • Restricted: Only available in development environments
  • Protection: Returns 403 Forbidden in production

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "success": true
  },
  "message": "Test data deleted"
}

Response: Production Protection (403 Forbidden)

{
  "success": false,
  "error": {
    "message": "Операция разрешена только в тестовом окружении",
    "code": "PRODUCTION_PROTECTION",
    "details": "Cleanup операции недоступны в production для защиты данных"
  }
}

cURL Example

curl -X DELETE https://api.saga.surf/api/admin/test-data \\
  -H "Authorization: Bearer <admin_token>"

9. Cleanup Test Data: `POST|DELETE /api/admin/cleanup/test-data`

Alternative endpoint for test data cleanup (compatibility with E2E tests). Supports both POST and DELETE methods.

Authentication

  • Required: Yes (Admin JWT Token)
  • Type: Admin Token

Security

Same as endpoint #8 - restricted to development environments only.

Response: Success (200 OK)

{
  "success": true,
  "data": {
    "success": true
  },
  "message": "Test data deleted"
}

cURL Example

# Using POST
curl -X POST https://api.saga.surf/api/admin/cleanup/test-data \\
  -H "Authorization: Bearer <admin_token>"

# Using DELETE
curl -X DELETE https://api.saga.surf/api/admin/cleanup/test-data \\
  -H "Authorization: Bearer <admin_token>"

Use Cases

Use Case 1: Complete User Management Dashboard

Full-featured admin dashboard for managing users with search, CRUD operations, and wallet viewing.

import { useState, useEffect } from 'react';

// ============= Types =============

interface AdminUser {
  id: string;
  email: string;
  status: string;
  role: string;
  totalInvestments: number;
  totalAmount: string;
  createdAt: string;
  updatedAt: string;
}

interface Wallet {
  address: string;
  networkId: number;
  type: string;
  derivationPath?: string;
  addressIndex?: number;
  createdAt: string;
}

interface UserWithWallets {
  id: string;
  email: string;
  status: string;
  personalWallets: Wallet[];
  depositWallets: Wallet[];
}

// ============= API Functions =============

const userAPI = {
  getUsers: async (
    page: number = 1,
    limit: number = 20,
    filters: { status?: string; search?: string } = {}
  ) => {
    const adminToken = localStorage.getItem('admin_auth_token');
    const params = new URLSearchParams({
      page: page.toString(),
      limit: limit.toString()
    });

    if (filters.status) params.set('status', filters.status);
    if (filters.search) params.set('search', filters.search);

    const response = await fetch(\`/api/admin/users?\${params}\`, {
      headers: { 'Authorization': \`Bearer \${adminToken}\` }
    });

    const { data } = await response.json();
    return data;
  },

  createUser: async (email: string, walletAddress?: string) => {
    const adminToken = localStorage.getItem('admin_auth_token');

    const response = await fetch('/api/admin/users', {
      method: 'POST',
      headers: {
        'Authorization': \`Bearer \${adminToken}\`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ email, walletAddress, status: 'active' })
    });

    if (!response.ok) throw new Error('Failed to create user');
    const { data } = await response.json();
    return data;
  },

  updateUser: async (
    userId: string,
    updates: { email?: string; status?: string }
  ) => {
    const adminToken = localStorage.getItem('admin_auth_token');

    const response = await fetch(\`/api/admin/users/\${userId}\`, {
      method: 'PUT',
      headers: {
        'Authorization': \`Bearer \${adminToken}\`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(updates)
    });

    if (!response.ok) throw new Error('Failed to update user');
    const { data } = await response.json();
    return data;
  },

  getUserWallets: async (userId: string): Promise<UserWithWallets> => {
    const adminToken = localStorage.getItem('admin_auth_token');

    const response = await fetch(\`/api/admin/users/\${userId}/wallets\`, {
      headers: { 'Authorization': \`Bearer \${adminToken}\` }
    });

    const { data } = await response.json();
    return data.user;
  },

  searchUsers: async (email: string) => {
    const adminToken = localStorage.getItem('admin_auth_token');

    const response = await fetch(
      \`/api/admin/search/users?email=\${encodeURIComponent(email)}\`,
      {
        headers: { 'Authorization': \`Bearer \${adminToken}\` }
      }
    );

    const { data } = await response.json();
    return data.suggestions;
  },

  deleteTestData: async () => {
    const adminToken = localStorage.getItem('admin_auth_token');

    const response = await fetch('/api/admin/test-data', {
      method: 'DELETE',
      headers: { 'Authorization': \`Bearer \${adminToken}\` }
    });

    if (!response.ok) throw new Error('Failed to delete test data');
    const { data } = await response.json();
    return data;
  }
};

// ============= Main Dashboard Component =============

const UserManagementDashboard: React.FC = () => {
  const [users, setUsers] = useState<AdminUser[]>([]);
  const [pagination, setPagination] = useState<any>(null);
  const [loading, setLoading] = useState(false);
  const [selectedUser, setSelectedUser] = useState<string | null>(null);
  const [walletsModalOpen, setWalletsModalOpen] = useState(false);
  const [userWallets, setUserWallets] = useState<UserWithWallets | null>(null);

  // Filters
  const [page, setPage] = useState(1);
  const [statusFilter, setStatusFilter] = useState('');
  const [searchQuery, setSearchQuery] = useState('');

  useEffect(() => {
    loadUsers();
  }, [page, statusFilter, searchQuery]);

  const loadUsers = async () => {
    setLoading(true);
    try {
      const data = await userAPI.getUsers(page, 20, {
        status: statusFilter,
        search: searchQuery
      });
      setUsers(data.users);
      setPagination(data.pagination);
    } catch (error) {
      console.error('Failed to load users:', error);
    } finally {
      setLoading(false);
    }
  };

  const handleViewWallets = async (userId: string) => {
    try {
      const wallets = await userAPI.getUserWallets(userId);
      setUserWallets(wallets);
      setWalletsModalOpen(true);
    } catch (error) {
      console.error('Failed to load wallets:', error);
    }
  };

  const handleUpdateUser = async (
    userId: string,
    updates: { email?: string; status?: string }
  ) => {
    try {
      await userAPI.updateUser(userId, updates);
      alert('✅ User updated successfully');
      loadUsers();
    } catch (error: any) {
      alert(\`❌ Error: \${error.message}\`);
    }
  };

  const handleCreateUser = async () => {
    const email = prompt('Enter new user email:');
    if (!email) return;

    try {
      await userAPI.createUser(email);
      alert('✅ User created successfully');
      loadUsers();
    } catch (error: any) {
      alert(\`❌ Error: \${error.message}\`);
    }
  };

  const handleDeleteTestData = async () => {
    if (!confirm('⚠️ Delete all test users? This cannot be undone.')) return;

    try {
      await userAPI.deleteTestData();
      alert('✅ Test data deleted successfully');
      loadUsers();
    } catch (error: any) {
      alert(\`❌ Error: \${error.message}\`);
    }
  };

  return (
    <div className="user-management-dashboard">
      <header className="dashboard-header">
        <h1>👥 User Management</h1>
        <div className="header-actions">
          <button onClick={handleCreateUser} className="btn-primary">
            ➕ Create User
          </button>
          <button onClick={handleDeleteTestData} className="btn-danger">
            🗑️ Delete Test Data
          </button>
        </div>
      </header>

      <div className="filters">
        <input
          type="text"
          placeholder="Search by email..."
          value={searchQuery}
          onChange={(e) => setSearchQuery(e.target.value)}
          className="search-input"
        />
        <select
          value={statusFilter}
          onChange={(e) => setStatusFilter(e.target.value)}
          className="status-filter"
        >
          <option value="">All Statuses</option>
          <option value="active">Active</option>
          <option value="inactive">Inactive</option>
          <option value="suspended">Suspended</option>
        </select>
      </div>

      {loading ? (
        <div className="loading">Loading users...</div>
      ) : (
        <>
          <table className="users-table">
            <thead>
              <tr>
                <th>Email</th>
                <th>Status</th>
                <th>Investments</th>
                <th>Total Amount</th>
                <th>Created</th>
                <th>Actions</th>
              </tr>
            </thead>
            <tbody>
              {users.map((user) => (
                <tr key={user.id}>
                  <td>{user.email}</td>
                  <td>
                    <span className={\`status-badge status-\${user.status}\`}>
                      {user.status}
                    </span>
                  </td>
                  <td>{user.totalInvestments}</td>
                  <td>\${user.totalAmount}</td>
                  <td>{new Date(user.createdAt).toLocaleDateString()}</td>
                  <td>
                    <button
                      onClick={() => handleViewWallets(user.id)}
                      className="btn-small"
                    >
                      🔐 Wallets
                    </button>
                    <button
                      onClick={() => {
                        const email = prompt('New email:', user.email);
                        if (email) handleUpdateUser(user.id, { email });
                      }}
                      className="btn-small"
                    >
                      ✏️ Edit
                    </button>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>

          {pagination && (
            <div className="pagination">
              <button
                onClick={() => setPage(page - 1)}
                disabled={page === 1}
              >
                ← Previous
              </button>
              <span>
                Page {pagination.page} of {pagination.totalPages}
              </span>
              <button
                onClick={() => setPage(page + 1)}
                disabled={page === pagination.totalPages}
              >
                Next →
              </button>
            </div>
          )}
        </>
      )}

      {walletsModalOpen && userWallets && (
        <div className="modal">
          <div className="modal-content">
            <h2>Wallets for {userWallets.email}</h2>

            <h3>Personal Wallets</h3>
            {userWallets.personalWallets.length === 0 ? (
              <p>No personal wallets</p>
            ) : (
              <ul>
                {userWallets.personalWallets.map((w, i) => (
                  <li key={i}>
                    {w.address} (Network: {w.networkId})
                  </li>
                ))}
              </ul>
            )}

            <h3>Deposit Wallets</h3>
            {userWallets.depositWallets.length === 0 ? (
              <p>No deposit wallets</p>
            ) : (
              <ul>
                {userWallets.depositWallets.map((w, i) => (
                  <li key={i}>
                    {w.address} (Index: {w.addressIndex})
                  </li>
                ))}
              </ul>
            )}

            <button onClick={() => setWalletsModalOpen(false)}>
              Close
            </button>
          </div>
        </div>
      )}
    </div>
  );
};

export default UserManagementDashboard;

CSS Styling:

.user-management-dashboard {
  padding: 20px;
  max-width: 1400px;
  margin: 0 auto;
}

.dashboard-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 30px;
}

.dashboard-header h1 {
  margin: 0;
  font-size: 28px;
}

.header-actions {
  display: flex;
  gap: 10px;
}

.btn-primary {
  padding: 10px 20px;
  background: #4169E1;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}

.btn-danger {
  padding: 10px 20px;
  background: #DC143C;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 600;
}

.filters {
  display: flex;
  gap: 15px;
  margin-bottom: 20px;
}

.search-input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 6px;
}

.status-filter {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 6px;
  min-width: 150px;
}

.users-table {
  width: 100%;
  border-collapse: collapse;
  background: white;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.users-table th,
.users-table td {
  padding: 15px;
  text-align: left;
  border-bottom: 1px solid #eee;
}

.users-table th {
  background: #f8f9fa;
  font-weight: 600;
  color: #333;
}

.status-badge {
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 600;
}

.status-active {
  background: #D4EDDA;
  color: #155724;
}

.status-inactive {
  background: #F8D7DA;
  color: #721C24;
}

.status-suspended {
  background: #FFF3CD;
  color: #856404;
}

.btn-small {
  padding: 5px 10px;
  margin-right: 5px;
  border: none;
  border-radius: 4px;
  background: #6c757d;
  color: white;
  cursor: pointer;
  font-size: 12px;
}

.btn-small:hover {
  background: #5a6268;
}

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 20px;
  margin-top: 20px;
}

.pagination button {
  padding: 8px 16px;
  border: 1px solid #ddd;
  background: white;
  border-radius: 4px;
  cursor: pointer;
}

.pagination button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-content {
  background: white;
  padding: 30px;
  border-radius: 8px;
  max-width: 600px;
  max-height: 80vh;
  overflow-y: auto;
}

.modal-content h2 {
  margin-top: 0;
}

.modal-content h3 {
  margin-top: 20px;
  margin-bottom: 10px;
}

.modal-content ul {
  list-style: none;
  padding: 0;
}

.modal-content li {
  padding: 10px;
  background: #f8f9fa;
  margin-bottom: 8px;
  border-radius: 4px;
  font-family: monospace;
  font-size: 14px;
}

.modal-content button {
  margin-top: 20px;
  padding: 10px 20px;
  background: #6c757d;
  color: white;
  border: none;
  border-radius: 6px;
  cursor: pointer;
}

.loading {
  text-align: center;
  padding: 40px;
  color: #666;
}

Technical Details

Authentication

All endpoints require admin JWT token authentication: - Admin Token: Generated through admin authentication flow - Header: `Authorization: Bearer ` - Middleware: AdminAuthMiddleware enforced at router level

Canonical Architecture

ComprehensiveUserService:

  • Single source of truth for user operations
  • Eliminates duplicate CreateUser implementations
  • Provides consistent validation and error handling
  • Clean Architecture pattern: Router → Service → Repository

Error Handling

Common HTTP Status Codes:

  • `200 OK`: Successful operation
  • `201 Created`: User created
  • `400 Bad Request`: Validation error
  • `401 Unauthorized`: Missing or invalid JWT token
  • `403 Forbidden`: Production protection (test data cleanup)
  • `404 Not Found`: User not found
  • `500 Internal Server Error`: Server error

Error Response Format:

{
  "success": false,
  "error": {
    "message": "Human-readable error message",
    "code": "ERROR_CODE",
    "details": "Additional context"
  },
  "trace_id": "trace_abc123"
}

Security Features

  1. Admin-Only Access: All endpoints require admin authentication
  2. Production Protection: Test data cleanup restricted to development
  3. Validation: Email and user data validated at service level
  4. Audit Trail: Admin ID tracked for all operations
  5. Permission-Based: Future support for fine-grained permissions

Database Schema

Users Table:

CREATE TABLE users (
  id VARCHAR(255) PRIMARY KEY,
  email VARCHAR(255) UNIQUE NOT NULL,
  status VARCHAR(50) NOT NULL,
  wallet_address VARCHAR(255),
  created_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP NOT NULL
);

Wallets Table:

CREATE TABLE wallets (
  address VARCHAR(255) PRIMARY KEY,
  user_id VARCHAR(255) NOT NULL,
  network_id INTEGER NOT NULL,
  type VARCHAR(50) NOT NULL,
  derivation_path VARCHAR(255),
  address_index INTEGER,
  created_at TIMESTAMP NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id)
);


Changelog

Version 1.0.0 (2025-10-06)

  • Initial documentation release
  • 9 admin endpoints documented
  • Complete user management coverage
  • Canonical architecture integration
  • Comprehensive dashboard implementation
  • Production protection for test data cleanup


📋 Метаданные

Версия: 2.4.82

Обновлено: 2025-10-21

Статус: Published