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

Notifications Endpoints

Аудитория: разработчики, frontend-инженеры Последнее обновление: 2025-11-17 Краткое содержание: Детальная документация Notification endpoints — получение уведомлений пользователя, пометка прочитанных, счетчик непрочитанных. Real-time notification system integration.


Обзор Endpoints

Метод Endpoint Описание Auth требуется
GET /api/user/notifications Получить список уведомлений ✅ Да
PUT /api/user/notifications/{id}/read Пометить уведомление прочитанным ✅ Да
PUT /api/user/notifications/mark-all-read Пометить все уведомления прочитанными ✅ Да
GET /api/user/notifications/unread/count Получить количество непрочитанных ✅ Да

🔔 GET /api/user/notifications

Получить постраничный список уведомлений для аутентифицированного пользователя.

Запрос

GET /api/user/notifications?page=1&limit=20&unread=true&type=investment&sortBy=createdAt&orderBy=desc
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Заголовки:

Заголовок Значение Обязательно Описание
Authorization Bearer <token> ✅ Да User JWT токен

Query параметры:

Параметр Тип Обязательно Описание По умолчанию
page number ❌ Нет Номер страницы (с 1) 1
limit number ❌ Нет Элементов на странице (макс: 100) 20
unread boolean ❌ Нет Фильтр по непрочитанным: true, false Все
type string ❌ Нет Фильтр по типу: investment, withdrawal, earning, security, system Все типы
sortBy string ❌ Нет Поле сортировки: createdAt, priority createdAt
orderBy string ❌ Нет Порядок сортировки: asc, desc desc

Ответ

Успех (200 OK):

{
  "success": true,
  "data": [
    {
      "id": "notif_abc123",
      "type": "investment",
      "title": "Инвестиция успешно создана",
      "message": "Ваша инвестиция в стратегию Balanced (10% APY) на сумму $500.00 успешно создана",
      "priority": "normal",
      "isRead": false,
      "category": "investment",
      "actionUrl": "/investments/inv_xyz789",
      "metadata": {
        "investmentId": "inv_xyz789",
        "strategyName": "Balanced 10% APY",
        "amount": "500.00",
        "currency": "USDC"
      },
      "createdAt": "2025-10-06T12:00:00Z",
      "readAt": null
    },
    {
      "id": "notif_def456",
      "type": "earning",
      "title": "Получена доходность",
      "message": "Вы получили $5.50 доходности от стратегии Balanced (10% APY)",
      "priority": "low",
      "isRead": true,
      "category": "earning",
      "actionUrl": "/transactions/txn_earning123",
      "metadata": {
        "amount": "5.50",
        "currency": "USDC",
        "strategyName": "Balanced 10% APY",
        "period": "2025-10-05 to 2025-10-06"
      },
      "createdAt": "2025-10-06T00:00:05Z",
      "readAt": "2025-10-06T08:30:00Z"
    },
    {
      "id": "notif_ghi789",
      "type": "security",
      "title": "Вход в аккаунт",
      "message": "Обнаружен вход в аккаунт с нового устройства: Chrome на Windows",
      "priority": "high",
      "isRead": false,
      "category": "security",
      "actionUrl": "/security/sessions",
      "metadata": {
        "device": "Chrome on Windows",
        "ip": "192.168.1.100",
        "location": "Moscow, Russia"
      },
      "createdAt": "2025-10-05T18:30:00Z",
      "readAt": null
    },
    {
      "id": "notif_jkl012",
      "type": "withdrawal",
      "title": "Вывод одобрен",
      "message": "Ваш запрос на вывод $1,000.00 USDC одобрен администратором",
      "priority": "high",
      "isRead": false,
      "category": "withdrawal",
      "actionUrl": "/withdrawals/wd_approved456",
      "metadata": {
        "withdrawalId": "wd_approved456",
        "amount": "1000.00",
        "currency": "USDC",
        "approvedBy": "admin@saga.surf"
      },
      "createdAt": "2025-10-05T15:00:00Z",
      "readAt": null
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 47,
    "totalPages": 3,
    "hasNext": true,
    "hasPrevious": false,
    "unreadCount": 12
  },
  "timestamp": "2025-10-06T12:34:56Z"
}

Типы уведомлений:

Тип Описание Priority
investment Создание/изменение инвестиций normal
earning Получение доходности low
withdrawal Операции вывода средств high
deposit Операции депозита normal
security Безопасность аккаунта (логины, изменения) high
system Системные уведомления normal
promotion Маркетинговые предложения low

Приоритеты уведомлений:

Priority Описание Поведение UI
critical Критические уведомления (безопасность) Красный badge, звук
high Важные уведомления (выводы, безопасность) Оранжевый badge
normal Стандартные уведомления Синий badge
low Информационные уведомления Серый badge

Поля ответа:

Поле Тип Описание
data[].id string Уникальный ID уведомления
data[].type string Тип уведомления (см. таблицу выше)
data[].title string Заголовок уведомления
data[].message string Текст уведомления
data[].priority string Приоритет (critical, high, normal, low)
data[].isRead boolean Прочитано ли уведомление
data[].actionUrl string URL для перехода при клике
data[].metadata object Дополнительные данные
data[].createdAt string Timestamp создания
data[].readAt string Timestamp прочтения (null если непрочитано)
pagination.unreadCount number Общее количество непрочитанных

Ошибки:

Код статуса Код ошибки Описание
400 INVALID_PAGINATION page < 1 или limit > 100
401 TOKEN_EXPIRED JWT токен истёк
401 TOKEN_INVALID Некорректная JWT подпись

Пример cURL

TOKEN="eyJhbGciOiJIUzI1NiIs..."

# Получить все уведомления
curl -X GET "https://app.saga.surf/api/user/notifications?page=1&limit=20" \
  -H "Authorization: Bearer $TOKEN"

# Получить только непрочитанные
curl -X GET "https://app.saga.surf/api/user/notifications?unread=true" \
  -H "Authorization: Bearer $TOKEN"

# Получить уведомления по типу
curl -X GET "https://app.saga.surf/api/user/notifications?type=investment&type=earning" \
  -H "Authorization: Bearer $TOKEN"

Пример TypeScript

// Получить уведомления с фильтрацией
const response = await fetch('/api/user/notifications?page=1&limit=20&unread=true&sortBy=createdAt&orderBy=desc', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

const { data: notifications, pagination } = await response.json();

console.log(`Показано ${notifications.length} из ${pagination.total} уведомлений`);
console.log(`Непрочитанных: ${pagination.unreadCount}`);

notifications.forEach(notif => {
  const badge = notif.isRead ? '' : '🔴';
  const priorityIcon = {
    critical: '🚨',
    high: '⚠️',
    normal: 'ℹ️',
    low: '💡'
  }[notif.priority];

  console.log(`${badge} ${priorityIcon} ${notif.title}: ${notif.message}`);
});

PUT /api/user/notifications/{id}/read

Пометить конкретное уведомление как прочитанное.

Запрос

PUT /api/user/notifications/notif_abc123/read
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Заголовки:

Заголовок Значение Обязательно Описание
Authorization Bearer <token> ✅ Да User JWT токен

Path параметры:

Параметр Тип Обязательно Описание
id string ✅ Да Notification ID (например, notif_abc123)

Ответ

Успех (200 OK):

{
  "success": true,
  "data": {
    "id": "notif_abc123",
    "type": "investment",
    "title": "Инвестиция успешно создана",
    "message": "Ваша инвестиция в стратегию Balanced (10% APY) на сумму $500.00 успешно создана",
    "priority": "normal",
    "isRead": true,
    "category": "investment",
    "actionUrl": "/investments/inv_xyz789",
    "metadata": {
      "investmentId": "inv_xyz789",
      "strategyName": "Balanced 10% APY",
      "amount": "500.00",
      "currency": "USDC"
    },
    "createdAt": "2025-10-06T12:00:00Z",
    "readAt": "2025-10-06T12:35:00Z"
  },
  "timestamp": "2025-10-06T12:35:00Z"
}

Ошибки:

Код статуса Код ошибки Описание
401 TOKEN_EXPIRED JWT токен истёк
403 PERMISSION_DENIED Уведомление принадлежит другому пользователю
404 NOTIFICATION_NOT_FOUND Notification ID не существует
400 ALREADY_READ Уведомление уже помечено прочитанным

Пример cURL

TOKEN="eyJhbGciOiJIUzI1NiIs..."
curl -X PUT https://app.saga.surf/api/user/notifications/notif_abc123/read \
  -H "Authorization: Bearer $TOKEN"

Пример TypeScript

// Пометить уведомление прочитанным
async function markNotificationRead(notificationId: string) {
  const response = await fetch(`/api/user/notifications/${notificationId}/read`, {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

  const { data } = await response.json();
  console.log(`Уведомление ${data.id} помечено прочитанным в ${data.readAt}`);

  return data;
}

// Пометить при клике
notificationElement.addEventListener('click', () => {
  markNotificationRead(notification.id);
  // Обновить UI
  updateNotificationBadge();
});

PUT /api/user/notifications/mark-all-read

Пометить все уведомления пользователя как прочитанные (массовая операция).

Запрос

PUT /api/user/notifications/mark-all-read
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Заголовки:

Заголовок Значение Обязательно Описание
Authorization Bearer <token> ✅ Да User JWT токен

Body параметры (опциональные):

{
  "type": "investment",        // Пометить только уведомления данного типа
  "beforeDate": "2025-10-01"  // Пометить только уведомления до этой даты
}

Ответ

Успех (200 OK):

{
  "success": true,
  "data": {
    "markedCount": 15,
    "remainingUnread": 3,
    "details": {
      "investment": 7,
      "earning": 5,
      "system": 3
    }
  },
  "timestamp": "2025-10-06T12:35:00Z"
}

Поля ответа:

Поле Тип Описание
data.markedCount number Количество помеченных уведомлений
data.remainingUnread number Осталось непрочитанных после операции
data.details object Разбивка по типам помеченных уведомлений

Ошибки:

Код статуса Код ошибки Описание
401 TOKEN_EXPIRED JWT токен истёк
400 NO_UNREAD_NOTIFICATIONS У пользователя нет непрочитанных уведомлений

Пример cURL

TOKEN="eyJhbGciOiJIUzI1NiIs..."

# Пометить все уведомления
curl -X PUT https://app.saga.surf/api/user/notifications/mark-all-read \
  -H "Authorization: Bearer $TOKEN"

# Пометить только уведомления определенного типа
curl -X PUT https://app.saga.surf/api/user/notifications/mark-all-read \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type": "earning"}'

Пример TypeScript

// Пометить все уведомления прочитанными
async function markAllNotificationsRead(type?: string) {
  const body = type ? JSON.stringify({ type }) : undefined;

  const response = await fetch('/api/user/notifications/mark-all-read', {
    method: 'PUT',
    headers: {
      'Authorization': `Bearer ${token}`,
      ...(body && { 'Content-Type': 'application/json' })
    },
    ...(body && { body })
  });

  const { data } = await response.json();
  console.log(`Помечено ${data.markedCount} уведомлений, осталось ${data.remainingUnread}`);

  return data;
}

// UI кнопка "Пометить все прочитанными"
markAllButton.addEventListener('click', async () => {
  await markAllNotificationsRead();
  refreshNotifications();
  updateNotificationBadge();
});

🔢 GET /api/user/notifications/unread/count

Получить количество непрочитанных уведомлений (легковесный endpoint для badge).

Запрос

GET /api/user/notifications/unread/count
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

Заголовки:

Заголовок Значение Обязательно Описание
Authorization Bearer <token> ✅ Да User JWT токен

Ответ

Успех (200 OK):

{
  "success": true,
  "data": {
    "total": 15,
    "byType": {
      "investment": 3,
      "earning": 5,
      "withdrawal": 2,
      "security": 1,
      "system": 4
    },
    "byPriority": {
      "critical": 0,
      "high": 3,
      "normal": 9,
      "low": 3
    }
  },
  "timestamp": "2025-10-06T12:34:56Z"
}

Поля ответа:

Поле Тип Описание
data.total number Общее количество непрочитанных
data.byType object Разбивка по типам уведомлений
data.byPriority object Разбивка по приоритетам

Ошибки:

Код статуса Код ошибки Описание
401 TOKEN_EXPIRED JWT токен истёк

Пример cURL

TOKEN="eyJhbGciOiJIUzI1NiIs..."
curl -X GET https://app.saga.surf/api/user/notifications/unread/count \
  -H "Authorization: Bearer $TOKEN"

Пример TypeScript

// Получить количество непрочитанных
async function getUnreadCount(): Promise<number> {
  const response = await fetch('/api/user/notifications/unread/count', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });

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

// Обновление badge каждые 30 секунд
setInterval(async () => {
  const count = await getUnreadCount();
  updateNotificationBadge(count);
}, 30000);

// Функция обновления UI badge
function updateNotificationBadge(count: number) {
  const badge = document.querySelector('.notification-badge');
  if (count > 0) {
    badge.textContent = count > 99 ? '99+' : count.toString();
    badge.classList.remove('hidden');
  } else {
    badge.classList.add('hidden');
  }
}

Распространённые сценарии использования

Отображение списка уведомлений

// Компонент списка уведомлений
async function NotificationsList() {
  const { data: notifications, pagination } = await fetch('/api/user/notifications?limit=20', {
    headers: { 'Authorization': `Bearer ${token}` }
  }).then(r => r.json());

  return (
    <div className="notifications-list">
      <h2>Уведомления ({pagination.total})</h2>

      {notifications.map(notif => (
        <div
          key={notif.id}
          className={`notification-item ${notif.isRead ? 'read' : 'unread'}`}
          onClick={() => handleNotificationClick(notif)}
        >
          <div className="notification-header">
            <span className={`priority-badge ${notif.priority}`}>
              {notif.priority}
            </span>
            <span className="notification-time">
              {formatRelativeTime(notif.createdAt)}
            </span>
          </div>
          <h3>{notif.title}</h3>
          <p>{notif.message}</p>
        </div>
      ))}

      {pagination.hasNext && (
        <button onClick={() => loadMoreNotifications(pagination.page + 1)}>
          Загрузить ещё
        </button>
      )}
    </div>
  );
}

Real-time notification badge

// Реактивный notification badge
function NotificationBadge() {
  const [unreadCount, setUnreadCount] = useState(0);

  useEffect(() => {
    // Начальная загрузка
    fetchUnreadCount();

    // Периодическое обновление
    const interval = setInterval(fetchUnreadCount, 30000);

    return () => clearInterval(interval);
  }, []);

  async function fetchUnreadCount() {
    const response = await fetch('/api/user/notifications/unread/count', {
      headers: { 'Authorization': `Bearer ${token}` }
    });
    const { data } = await response.json();
    setUnreadCount(data.total);
  }

  return (
    <button className="notification-button" onClick={openNotifications}>
      <BellIcon />
      {unreadCount > 0 && (
        <span className="notification-badge">
          {unreadCount > 99 ? '99+' : unreadCount}
        </span>
      )}
    </button>
  );
}

Автоматическая пометка прочитанных

// Автоматическая пометка при просмотре
function handleNotificationClick(notification) {
  // Перейти по actionUrl
  router.push(notification.actionUrl);

  // Пометить прочитанным если ещё не помечено
  if (!notification.isRead) {
    fetch(`/api/user/notifications/${notification.id}/read`, {
      method: 'PUT',
      headers: { 'Authorization': `Bearer ${token}` }
    });

    // Обновить локальное состояние
    markLocalNotificationRead(notification.id);
    updateUnreadCount(-1);
  }
}

Фильтрация уведомлений по типу

// UI фильтры для уведомлений
function NotificationFilters({ onFilterChange }) {
  const [selectedType, setSelectedType] = useState('all');
  const [showUnreadOnly, setShowUnreadOnly] = useState(false);

  const notificationTypes = [
    { value: 'all', label: 'Все уведомления' },
    { value: 'investment', label: 'Инвестиции' },
    { value: 'earning', label: 'Доходность' },
    { value: 'withdrawal', label: 'Выводы' },
    { value: 'security', label: 'Безопасность' },
    { value: 'system', label: 'Системные' },
  ];

  const handleFilterChange = () => {
    const params = new URLSearchParams();
    if (selectedType !== 'all') params.append('type', selectedType);
    if (showUnreadOnly) params.append('unread', 'true');

    onFilterChange(params.toString());
  };

  return (
    <div className="notification-filters">
      <select value={selectedType} onChange={e => {
        setSelectedType(e.target.value);
        handleFilterChange();
      }}>
        {notificationTypes.map(type => (
          <option key={type.value} value={type.value}>{type.label}</option>
        ))}
      </select>

      <label>
        <input
          type="checkbox"
          checked={showUnreadOnly}
          onChange={e => {
            setShowUnreadOnly(e.target.checked);
            handleFilterChange();
          }}
        />
        Только непрочитанные
      </label>
    </div>
  );
}

Связанная документация

Другие User Endpoints:




📋 Метаданные

Версия: 2.6.268

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

Статус: Published