Как в базе данных указать несколько ролей у одного пользователя?

Ответ

В Node.js-приложениях выбор структуры зависит от типа базы данных. Вот основные подходы:

1. Для реляционных БД (PostgreSQL, MySQL) — Связующая таблица (Many-to-Many): Это наиболее нормализованный и гибкий способ.

-- Таблицы
CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255));
CREATE TABLE roles (id SERIAL PRIMARY KEY, name VARCHAR(50));
-- Связующая таблица
CREATE TABLE user_roles (
  user_id INT REFERENCES users(id) ON DELETE CASCADE,
  role_id INT REFERENCES roles(id) ON DELETE CASCADE,
  PRIMARY KEY (user_id, role_id)
);

Работа через Sequelize (ORM):

// Модели с ассоциацией Many-to-Many
User.belongsToMany(Role, { through: 'UserRole' });
Role.belongsToMany(User, { through: 'UserRole' });

// Назначение ролей пользователю
const user = await User.findByPk(1);
const adminRole = await Role.findOne({ where: { name: 'admin' } });
await user.addRole(adminRole);

// Получение пользователя с его ролями
const userWithRoles = await User.findByPk(1, {
  include: Role
});

2. Для документ-ориентированных БД (MongoDB) — Массив в документе: Простой и быстрый способ для случаев, когда набор ролей ограничен и не требует сложных запросов.

// Схема Mongoose
const userSchema = new mongoose.Schema({
  email: String,
  roles: [{
    type: String,
    enum: ['user', 'editor', 'admin', 'moderator'] // Валидация
  }]
});

// Добавление роли
await User.updateOne(
  { _id: userId },
  { $addToSet: { roles: 'editor' } } // $addToSet предотвращает дубликаты
);

3. Битовая маска (для простых, статичных наборов прав): Эффективно по памяти, но менее гибко и читаемо.

// Определение ролей как степеней двойки
const ROLES = {
  USER: 1 << 0, // 1
  EDITOR: 1 << 1, // 2
  ADMIN: 1 << 2, // 4
  MODERATOR: 1 << 3 // 8
};

// Пользователь - редактор и модератор
user.permissions = ROLES.EDITOR | ROLES.MODERATOR; // 10 (2 + 8)

// Проверка роли
const isEditor = (user.permissions & ROLES.EDITOR) === ROLES.EDITOR;

Выбор подхода:

  • Связующая таблица — для сложных систем с динамическим управлением ролями, ACL (Access Control Lists).
  • Массив в документе — для микросервисов или приложений, где данные пользователя часто читаются целиком.
  • Битовая маска — для высоконагруженных систем с простой, неизменной логикой проверки прав.

Ответ 18+ 🔞

А, ну это же классика, ёпта! Сидишь ты такой, пишешь приложение на Node.js, и тут бац — нужно роли пользователям раздавать. И тут начинается: а как, блядь, это правильно организовать? Способов-то дохуя, и каждый норовит тебе мозг выебать по-своему.

Смотри, чувак, тут всё от твоей базы данных зависит, как от печки плясать. Если ты с PostgreSQL или MySQL дружишь, то нормальные пацаны делают через связующую таблицу. Это типа золотой стандарт, чтоб потом не охуеть от своей же архитектуры.

-- Таблицы
CREATE TABLE users (id SERIAL PRIMARY KEY, email VARCHAR(255));
CREATE TABLE roles (id SERIAL PRIMARY KEY, name VARCHAR(50));
-- Связующая таблица
CREATE TABLE user_roles (
  user_id INT REFERENCES users(id) ON DELETE CASCADE,
  role_id INT REFERENCES roles(id) ON DELETE CASCADE,
  PRIMARY KEY (user_id, role_id)
);

В коде через Sequelize это выглядит как по маслу, если, конечно, ты не распиздяй.

// Модели с ассоциацией Many-to-Many
User.belongsToMany(Role, { through: 'UserRole' });
Role.belongsToMany(User, { through: 'UserRole' });

// Назначение ролей пользователю
const user = await User.findByPk(1);
const adminRole = await Role.findOne({ where: { name: 'admin' } });
await user.addRole(adminRole);

// Получение пользователя с его ролями
const userWithRoles = await User.findByPk(1, {
  include: Role
});

А теперь, если ты на MongoDB сидишь, то там вообще манда с ушами простая. Зачем тебе лишние коллекции, когда можно роли прямо в документ пользователя запихнуть, как носки в чемодан?

// Схема Mongoose
const userSchema = new mongoose.Schema({
  email: String,
  roles: [{
    type: String,
    enum: ['user', 'editor', 'admin', 'moderator'] // Валидация
  }]
});

// Добавление роли
await User.updateOne(
  { _id: userId },
  { $addToSet: { roles: 'editor' } } // $addToSet предотвращает дубликаты
);

Ну и для самых отчаянных перфекционистов, у которых терпения ноль ебать и нужно всё супер-быстро, есть старый добрый трюк — битовая маска. Выглядит, конечно, как шифровка от шпиона, но работает быстрее, чем я утром на работу собираюсь.

// Определение ролей как степеней двойки
const ROLES = {
  USER: 1 << 0, // 1
  EDITOR: 1 << 1, // 2
  ADMIN: 1 << 2, // 4
  MODERATOR: 1 << 3 // 8
};

// Пользователь - редактор и модератор
user.permissions = ROLES.EDITOR | ROLES.MODERATOR; // 10 (2 + 8)

// Проверка роли
const isEditor = (user.permissions & ROLES.EDITOR) === ROLES.EDITOR;

Так какого хуя выбрать-то?

  • Связующая таблица — это когда у тебя система сложная, как жизнь, и роли могут плодиться, как кролики. Идеально для всяких ACL, где права — это отдельная песня.
  • Массив в документе — твой выбор, если ты в микросервисах шаришь и тебе похуй на нормальные формы, лишь бы быстро достать всё про пользователя одним запросом.
  • Битовая маска — это когда ты сам от себя охуел от требований к производительности, а логика прав проще, чем дважды два. Только не забудь потом, что там и как работает, а то через месяц будешь сидеть и думать, что за хуйню ты написал.

Вот и вся философия, блядь. Выбирай, что подходит, и не выебывайся с архитектурой просто так.