# Users Controller

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

## Принципы работы с пользователями

Модуль предусматривает ведение глобального реестра пользователей сервиса, безотносительно к тому, через какой канал происходит взаимодействие с сервисом. В то же время, на текущий момент настроена работа только через один канал - телеграм-боты.

Пользователи могут взаимодействовать с системой через разные каналы, в которых у них свои идентификаторы и метаданные. Каждому пользователю присваивается глобальный UUID, и разные каналы коммуникаций связываются с этим ID.

Особенность задачи в том, что при подключении каждого нового канала коммуникаций пользователь считается "новым", пока не будет выполнена связь с другими каналами. Когда пользователь выполняет привязку (через предоставление своего глобального ID), идентификатор "нового" канала переписывается.

## Типы хранилища

Модуль предусматривает три типа хранилища:

### 1. Общий реестр пользователей
- **Таблица**: `users_global`
- **Модуль**: `UsersGlobal`
- **Назначение**: Связывает идентификаторы каналов с глобальными UUID пользователей
- **Структура**: тип канала, идентификатор в канале, глобальный идентификатор, дата регистрации

### 2. Пользовательские данные
- **Хранилище**: Key-value база данных
- **Модуль**: `UserData`
- **Назначение**: Хранение объектов с различной пользовательской информацией
- **Особенности**: Использует кэширование для оптимизации производительности

### 3. Пакетная обработка пользовательских данных
- **Таблица**: `users_processing_{domain}`
- **Модуль**: `UserProcessing`
- **Назначение**: Временное хранение данных для пакетной обработки
- **Оптимизация**: Создано для снижения затрат на операции чтения/записи

## Конфигурация базы данных

### Рекомендуемая структура
С точки зрения структуры хранения данных рекомендуется иметь глобальную базу данных, в которой будут храниться данные по всем пользователям. Адрес к этой базе прописывается в переменной `GLOBAL_DB_ADDRESS`, либо указывается при инициализации.

### Альтернативная структура
Также допускается, но не рекомендуется ситуация, что данные (таблицы) распределены по разным базам данных. В этом случае требуется выполнять раздельную инициализацию для каждого из используемых хранилищ.

## Правила именования для пакетной обработки

При инициализации данных для пакетной обработки нужно соблюдать правила именования для параметра `domain`, поскольку его значение становится частью названия таблицы, в которой будет вестись работа. В частности, допустимы:
- прописные латинские буквы (A-Z)
- строчные латинские буквы (a-z)
- цифры (0-9)
- специальные символы: `.`, `-` и `_`

## Установка

```bash
npm install @dieugene/users-controller
```

## Зависимости

- `@dieugene/key-value-db` - для работы с key-value хранилищем
- `@dieugene/utils` - утилиты
- `@dieugene/ydb-serverless` - для работы с YDB
- `uuid` - генерация уникальных идентификаторов

## Дополнительная документация

📋 **[Руководство по работе с таблицами YDB](./YDB_TABLE_OPERATIONS_GUIDE.md)** - подробное руководство по CRUD операциям с использованием @dieugene/ydb-serverless, включая:
- Оптимизацию стоимости операций (UPSERT vs INSERT vs UPDATE)
- Рекомендации по индексации полей
- Лучшие практики работы с YDB
- Примеры кода для всех типов операций

## Быстрый старт

```javascript
const users = require('@dieugene/users-controller');

// Инициализация с использованием переменной окружения GLOBAL_DB_ADDRESS
users.init();

// Или с указанием адреса базы данных
users.init('/region/folder-id/database-id');
```

## API

### Основная инициализация

#### `init(users_database?)`
Инициализирует все модули с указанной базой данных.

**Параметры:**
- `users_database` (string, optional) - адрес базы данных. По умолчанию используется `process.env.GLOBAL_DB_ADDRESS`

### Модуль UsersGlobal (`users.globals`)

#### `init(users_database?)`
Инициализирует модуль работы с глобальным реестром пользователей.

#### `getUserUuid(channelType, inChannelId)`
Получает глобальный UUID пользователя по данным канала.

**Параметры:**
- `channelType` (string) - тип канала коммуникации
- `inChannelId` (string) - идентификатор пользователя в канале

**Возвращает:** Promise<string|undefined> - глобальный UUID пользователя

#### `setUser(channelType, inChannelId)`
Создает нового пользователя в глобальном реестре.

**Параметры:**
- `channelType` (string) - тип канала коммуникации
- `inChannelId` (string) - идентификатор пользователя в канале

**Возвращает:** Promise<string> - новый глобальный UUID пользователя

#### `getChannelUsers(channelType)`
Получает список всех пользователей определенного канала.

**Параметры:**
- `channelType` (string) - тип канала коммуникации

### Модуль UserData (`users.data`)

#### `init(database?)`
Инициализирует модуль работы с пользовательскими данными.

#### `set_user_data(user_uuid, data)`
Сохраняет данные пользователя.

**Параметры:**
- `user_uuid` (string) - глобальный UUID пользователя
- `data` (object) - данные для сохранения

#### `get_user_data(user_uuid, forced?)`
Получает данные пользователя.

**Параметры:**
- `user_uuid` (string) - глобальный UUID пользователя
- `forced` (boolean, optional) - принудительное обновление кэша

**Возвращает:** Promise<object> - данные пользователя

### Модуль UserProcessing (`users.processing`)

#### `init(domain, users_database?)`
Инициализирует модуль пакетной обработки.

**Параметры:**
- `domain` (string) - домен для именования таблицы (должен соответствовать правилам именования)
- `users_database` (string, optional) - адрес базы данных

#### `set(uuid_list)`
Добавляет список UUID пользователей для обработки.

**Параметры:**
- `uuid_list` (string[]) - массив UUID пользователей

#### `set_channel_users(channel)`
Добавляет всех пользователей канала для обработки.

**Параметры:**
- `channel` (string) - тип канала

#### `get(limit?)`
Получает пакет пользователей для обработки.

**Параметры:**
- `limit` (number, optional) - количество пользователей (по умолчанию 50)

**Возвращает:** Promise<string[]> - массив UUID пользователей

#### `del(id_list)`
Удаляет обработанных пользователей из очереди.

**Параметры:**
- `id_list` (string[]) - массив UUID пользователей для удаления

### Модуль TelegramUsers (`users.tg`)

#### `get_user_uuid(ctx, tgId?)`
Получает глобальный UUID пользователя Telegram.

**Параметры:**
- `ctx` - контекст Telegram бота
- `tgId` (number, optional) - ID пользователя Telegram

**Возвращает:** Promise<string> - глобальный UUID пользователя

#### `stringify_user_data(userData)`
Форматирует данные пользователя Telegram в строку.

**Параметры:**
- `userData` (object) - данные пользователя с полями `first_name`, `last_name`, `username`

**Возвращает:** string - отформатированная строка

#### `get_bot_users(botId)`
Получает список пользователей конкретного Telegram бота.

**Параметры:**
- `botId` (string) - ID Telegram бота

#### `set_telegram_data(user_data, ctx)`
Обновляет пользовательские данные информацией из Telegram контекста.

**Параметры:**
- `user_data` (object) - объект данных пользователя
- `ctx` - контекст Telegram бота

**Возвращает:** object - обновленный объект данных пользователя

**Заполняемые поля:**
- `telegram_data.telegram_id` - ID пользователя в Telegram
- `telegram_data.first_name` - имя пользователя
- `telegram_data.last_name` - фамилия пользователя
- `telegram_data.username` - никнейм пользователя
- `telegram_data.language_code` - код языка пользователя
- `telegram_data.is_bot` - флаг бота
- `telegram_data.is_premium` - флаг Telegram Premium
- `telegram_data.updated_at` - Unix timestamp последнего обновления

### Вспомогательные методы

#### `set_user_data(user_uuid, data)`
Прямой доступ к сохранению пользовательских данных.

#### `get_user_data(user_uuid, forced?)`
Прямой доступ к получению пользовательских данных.

## Примеры использования

### Работа с пользователями Telegram

```javascript
const users = require('@dieugene/users-controller');

// Инициализация
users.init();

// Получение UUID пользователя Telegram
async function handleTelegramUser(ctx) {
    const userUuid = await users.tg.get_user_uuid(ctx);
    
    // Получение данных пользователя
    let userData = await users.get_user_data(userUuid);
    
    // Обновление данных из Telegram (имя, фамилия, username и т.д.)
    userData = users.tg.set_telegram_data(userData, ctx);
    
    // Добавление дополнительных данных
    userData.last_interaction = new Date();
    userData.preferences = { language: 'ru' };
    
    // Сохранение обновленных данных
    await users.set_user_data(userUuid, userData);
}
```

### Пакетная обработка пользователей

```javascript
// Инициализация модуля пакетной обработки
await users.processing.init('newsletter');

// Добавление всех пользователей Telegram для обработки
await users.processing.set_channel_users('telegram_bot_id');

// Обработка пользователей пакетами
async function processUsers() {
    const usersList = await users.processing.get(100);
    
    for (const userUuid of usersList) {
        // Обработка пользователя
        await processUser(userUuid);
    }
    
    // Удаление обработанных пользователей
    await users.processing.del(usersList);
}
```

## Переменные окружения

- `GLOBAL_DB_ADDRESS` - адрес глобальной базы данных (формат: `/region/folder-id/database-id`)

## Лицензия

ISC

## Автор

Eugene Ditkovsky
