feat(ban-system): implement multi-level user ban checks across services
- Added a comprehensive ban checking system to prevent access for banned users at multiple levels, including database, API, and client-side. - Introduced `bannedUntil` field in the customer model to manage temporary and permanent bans effectively. - Enhanced `BaseService` and various service classes to include ban checks, ensuring that banned users cannot perform actions or access data. - Updated error handling to provide consistent feedback for banned users across the application. - Improved user experience with a dedicated ban check component and a user-friendly ban notification page.
This commit is contained in:
parent
5018560f29
commit
7c1f79db2a
@ -99,6 +99,7 @@ msg-unhandled = ❓ Неизвестная команда. Попробуйте
|
||||
|
||||
# Ошибки
|
||||
err-generic = ⚠️ Что-то пошло не так. Попробуйте еще раз через несколько секунд
|
||||
err-banned = 🚫 Ваш аккаунт заблокирован
|
||||
err-with-details = ❌ Произошла ошибка
|
||||
{ $error }
|
||||
err-limit-exceeded = 🚫 Слишком много запросов! Подождите немного
|
||||
@ -1,10 +1,16 @@
|
||||
import { type Context } from '../context';
|
||||
import { getUpdateInfo } from '../helpers/logging';
|
||||
import { KEYBOARD_REMOVE } from '@/config/keyboards';
|
||||
import { ERRORS } from '@repo/graphql/constants/errors';
|
||||
import { type ErrorHandler } from 'grammy';
|
||||
|
||||
export const errorHandler: ErrorHandler<Context> = (error) => {
|
||||
export const errorHandler: ErrorHandler<Context> = async (error) => {
|
||||
const { ctx } = error;
|
||||
|
||||
const text = error.message.includes(ERRORS.NO_PERMISSION) ? 'err-banned' : 'err-generic';
|
||||
|
||||
await ctx.reply(ctx.t(text), { ...KEYBOARD_REMOVE, parse_mode: 'HTML' });
|
||||
|
||||
ctx.logger.error({
|
||||
err: error.error,
|
||||
update: getUpdateInfo(ctx),
|
||||
|
||||
@ -8,20 +8,22 @@ const bot = createBot({
|
||||
token: environment.BOT_TOKEN,
|
||||
});
|
||||
|
||||
const runner = run(bot);
|
||||
bot.catch((error) => {
|
||||
logger.error('Grammy bot error:');
|
||||
logger.error(`Message: ${error?.message}`);
|
||||
logger.error(error.error);
|
||||
});
|
||||
|
||||
const runner = run(bot);
|
||||
const redis = getRedisInstance();
|
||||
|
||||
// Graceful shutdown function
|
||||
async function gracefulShutdown(signal: string) {
|
||||
logger.info(`Received ${signal}, starting graceful shutdown...`);
|
||||
|
||||
try {
|
||||
// Stop the bot
|
||||
await runner.stop();
|
||||
logger.info('Bot stopped');
|
||||
|
||||
// Disconnect Redis
|
||||
redis.disconnect();
|
||||
logger.info('Redis disconnected');
|
||||
} catch (error) {
|
||||
@ -30,9 +32,15 @@ async function gracefulShutdown(signal: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Stopping the bot when the Node.js process
|
||||
// is about to be terminated
|
||||
process.once('SIGINT', () => gracefulShutdown('SIGINT'));
|
||||
process.once('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
logger.error('Unhandled Rejection: ' + reason);
|
||||
});
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
logger.error('Uncaught Exception: ' + error);
|
||||
});
|
||||
|
||||
logger.info('Bot started');
|
||||
|
||||
@ -1,51 +1,44 @@
|
||||
# Проверка бана пользователей
|
||||
# Система проверки бана пользователей
|
||||
|
||||
В проекте реализована система проверки бана пользователей на нескольких уровнях:
|
||||
## Обзор
|
||||
|
||||
## Поле `bannedUntil` в базе данных
|
||||
Реализована многоуровневая система проверки бана пользователей, которая предотвращает доступ заблокированных пользователей к функциональности приложения.
|
||||
|
||||
Проверка бана основана на поле `bannedUntil` в таблице `Customer`:
|
||||
## 1. База данных (`bannedUntil` поле)
|
||||
|
||||
- `bannedUntil: null` - пользователь не забанен (по умолчанию)
|
||||
- `bannedUntil: "2024-12-31T23:59:59Z"` - забанен до этой даты
|
||||
- `bannedUntil: "2099-12-31T23:59:59Z"` - забанен навсегда (100 лет)
|
||||
В Strapi добавлено поле `bannedUntil` типа `datetime` в модель `Customer`:
|
||||
- `null` = пользователь не забанен
|
||||
- `дата в будущем` = временный бан до указанной даты
|
||||
- `дата в далеком будущем` = постоянный бан
|
||||
|
||||
## Утилита для проверки бана
|
||||
## 2. Утилита проверки (`packages/utils/src/customer.ts`)
|
||||
|
||||
```typescript
|
||||
// utils/customer.ts
|
||||
export function isCustomerBanned(customer: { bannedUntil?: string | null }): boolean {
|
||||
return Boolean(customer.bannedUntil && new Date() < new Date(customer.bannedUntil));
|
||||
}
|
||||
```
|
||||
|
||||
## 1. Next Auth Configuration (`config/auth.ts`)
|
||||
## 3. Next Auth проверка (`apps/web/config/auth.ts`)
|
||||
|
||||
Основная проверка происходит в callback `authorize` провайдера Credentials:
|
||||
В `authorize` callback добавлена проверка бана:
|
||||
|
||||
```typescript
|
||||
async authorize(credentials) {
|
||||
const { telegramId } = credentials ?? {};
|
||||
|
||||
if (!telegramId) {
|
||||
throw new Error('Invalid Telegram ID');
|
||||
}
|
||||
if (!telegramId) { throw new Error('Invalid Telegram ID'); }
|
||||
|
||||
try {
|
||||
// Проверяем, не забанен ли пользователь
|
||||
const { query } = await getClientWithToken();
|
||||
const result = await query({
|
||||
query: GetCustomerDocument,
|
||||
variables: { telegramId: Number(telegramId) },
|
||||
});
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
// Если пользователь не найден или забанен
|
||||
if (!customer || isCustomerBanned(customer)) {
|
||||
throw new Error('User is banned or not found');
|
||||
}
|
||||
|
||||
return { id: telegramId };
|
||||
} catch (error) {
|
||||
throw new Error('Authentication failed');
|
||||
@ -53,49 +46,66 @@ async authorize(credentials) {
|
||||
}
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Проверка происходит на уровне аутентификации
|
||||
- Забаненные пользователи не могут войти в систему
|
||||
- Автоматическая блокировка при попытке входа
|
||||
## 4. Универсальная проверка в BaseService (`packages/graphql/api/base.ts`)
|
||||
|
||||
## 2. BaseService (`packages/graphql/api/base.ts`)
|
||||
|
||||
Проверка на уровне API сервисов:
|
||||
Добавлен метод `checkIsBanned()` в `BaseService`:
|
||||
|
||||
```typescript
|
||||
protected async _getUser() {
|
||||
/**
|
||||
* Универсальная проверка статуса бана пользователя
|
||||
* Должна вызываться в начале каждого метода сервиса
|
||||
*/
|
||||
protected async checkIsBanned() {
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
query: GQL.GetCustomerDocument,
|
||||
variables: this._user,
|
||||
});
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
if (!customer) throw new Error(ERRORS.NOT_FOUND_CUSTOMER);
|
||||
if (!customer) {
|
||||
throw new Error(BASE_ERRORS.NOT_FOUND_CUSTOMER);
|
||||
}
|
||||
|
||||
// Проверяем, не забанен ли пользователь
|
||||
if (isCustomerBanned(customer)) {
|
||||
throw new Error(ERRORS.USER_BANNED);
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
|
||||
return { customer };
|
||||
}
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Все API запросы автоматически проверяют статус пользователя
|
||||
- Единая точка проверки для всех сервисов
|
||||
- Автоматическое отклонение запросов от забаненных пользователей
|
||||
**Использование в сервисах:**
|
||||
```typescript
|
||||
async someMethod() {
|
||||
await this.checkIsBanned(); // Проверка бана в начале метода
|
||||
// ... остальная логика
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Защита от изменения статуса бана (`packages/graphql/api/customers.ts` и `registration.ts`)
|
||||
**Обновленные сервисы:**
|
||||
- ✅ `CustomersService` - все методы
|
||||
- ✅ `ServicesService` - все методы
|
||||
- ✅ `OrdersService` - все методы
|
||||
- ✅ `SlotsService` - все методы
|
||||
- ✅ `RegistrationService` - добавлена собственная проверка
|
||||
|
||||
**Преимущества:**
|
||||
- Автоматическая проверка во всех сервисах, наследующих от BaseService
|
||||
- Единая точка проверки бана
|
||||
- Работает как в веб-приложении, так и в боте
|
||||
- Простота добавления в новые методы
|
||||
- Защита всех API методов от забаненных пользователей
|
||||
|
||||
## 5. Защита от изменения статуса бана (`packages/graphql/api/customers.ts` и `registration.ts`)
|
||||
|
||||
Пользователи не могут изменять поле `bannedUntil` самостоятельно:
|
||||
|
||||
```typescript
|
||||
// В CustomersService
|
||||
async updateCustomer(variables: Omit<VariablesOf<typeof GQL.UpdateCustomerDocument>, 'documentId'>) {
|
||||
await this.checkBanStatus();
|
||||
|
||||
const { customer } = await this._getUser();
|
||||
|
||||
// Проверяем, что пользователь не пытается изменить поле bannedUntil
|
||||
@ -108,7 +118,19 @@ async updateCustomer(variables: Omit<VariablesOf<typeof GQL.UpdateCustomerDocume
|
||||
|
||||
// В RegistrationService
|
||||
async updateCustomer(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>) {
|
||||
// Проверяем, что пользователь не пытается изменить поле bannedUntil
|
||||
// Проверяем бан для существующего пользователя
|
||||
if (variables.documentId) {
|
||||
const { query } = await getClientWithToken();
|
||||
const result = await query({
|
||||
query: GQL.GetCustomerDocument,
|
||||
variables: { documentId: variables.documentId },
|
||||
});
|
||||
const customer = result.data.customers.at(0);
|
||||
if (customer && isCustomerBanned(customer)) {
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
if (variables.data.bannedUntil) {
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
@ -124,105 +146,95 @@ async updateCustomer(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>)
|
||||
- Защита работает во всех сервисах, которые обновляют данные пользователей
|
||||
- Единая ошибка `NO_PERMISSION` для всех случаев отсутствия доступа
|
||||
|
||||
## 4. Client-side Check (`components/auth/check-banned.tsx`)
|
||||
## 6. Client-side Check (`components/auth/ban-check.tsx`)
|
||||
|
||||
Проверка на клиентской стороне:
|
||||
React компонент для проверки бана на клиенте:
|
||||
|
||||
```typescript
|
||||
import { useIsBanned } from '@/hooks/api/customers';
|
||||
import { redirect, RedirectType } from 'next/navigation';
|
||||
import { type PropsWithChildren, useEffect } from 'react';
|
||||
|
||||
export function CheckBanned({ children }: Readonly<PropsWithChildren>) {
|
||||
export function BanCheck({ children }: Readonly<PropsWithChildren>) {
|
||||
const { data: session } = useSession();
|
||||
const router = useRouter();
|
||||
const isBanned = useIsBanned();
|
||||
|
||||
useEffect(() => {
|
||||
if (isBanned) {
|
||||
redirect('/banned', RedirectType.replace);
|
||||
if (session?.user?.telegramId && isBanned) {
|
||||
router.push('/banned');
|
||||
}
|
||||
}, [isBanned]);
|
||||
}, [session?.user?.telegramId, isBanned, router]);
|
||||
|
||||
if (isBanned) return null;
|
||||
|
||||
return children;
|
||||
if (session?.user?.telegramId && isBanned) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- Мгновенная реакция на изменение статуса
|
||||
- Автоматическое перенаправление на страницу бана
|
||||
- Предотвращение рендеринга контента для забаненных пользователей
|
||||
**Использование в layout:**
|
||||
```typescript
|
||||
export default function Layout({ children }: Readonly<PropsWithChildren>) {
|
||||
return (
|
||||
<Provider>
|
||||
<BanCheck>
|
||||
<UpdateProfile />
|
||||
<main className="grow">{children}</main>
|
||||
<BottomNav />
|
||||
</BanCheck>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Hook для проверки бана (`hooks/api/customers.ts`)
|
||||
## 7. Hook для проверки бана (`hooks/api/customers.ts`)
|
||||
|
||||
```typescript
|
||||
export const useIsBanned = () => {
|
||||
const { data: { customer } = {} } = useCustomerQuery();
|
||||
|
||||
if (!customer) return false;
|
||||
|
||||
return isCustomerBanned(customer);
|
||||
};
|
||||
```
|
||||
|
||||
**Использование:**
|
||||
```typescript
|
||||
const isBanned = useIsBanned();
|
||||
## 8. Страница для забаненных пользователей (`apps/web/app/(auth)/banned/page.tsx`)
|
||||
|
||||
if (isBanned) {
|
||||
// Показать сообщение о бане
|
||||
}
|
||||
```
|
||||
Создана специальная страница с информацией о бане и возможностью выхода из аккаунта.
|
||||
|
||||
## 6. Страница бана (`app/(auth)/banned/page.tsx`)
|
||||
|
||||
Специальная страница для забаненных пользователей с информацией о причинах блокировки и возможностью выхода из аккаунта.
|
||||
|
||||
## Рекомендации по использованию
|
||||
|
||||
1. **Основная защита**: Next Auth configuration - предотвращает вход забаненных пользователей
|
||||
2. **API защита**: BaseService - блокирует все API запросы от забаненных пользователей
|
||||
3. **Защита от изменения**: CustomersService - предотвращает изменение статуса бана пользователями
|
||||
4. **UI защита**: BanCheck компонент - обеспечивает корректное отображение интерфейса
|
||||
5. **Дополнительные проверки**: useIsBanned хук - для условной логики в компонентах
|
||||
|
||||
## Добавление новых проверок
|
||||
|
||||
Для добавления проверки бана в новый компонент:
|
||||
## 9. Централизованные ошибки (`packages/graphql/constants/errors.ts`)
|
||||
|
||||
```typescript
|
||||
import { useIsBanned } from '@/hooks/api/customers';
|
||||
|
||||
function MyComponent() {
|
||||
const isBanned = useIsBanned();
|
||||
|
||||
if (isBanned) {
|
||||
return <div>Вы заблокированы</div>;
|
||||
}
|
||||
|
||||
return <div>Обычный контент</div>;
|
||||
}
|
||||
export const ERRORS = {
|
||||
NO_PERMISSION: 'Нет доступа',
|
||||
} as const;
|
||||
```
|
||||
|
||||
## Автоматическое снятие бана
|
||||
## Архитектура системы
|
||||
|
||||
Система автоматически снимает бан, когда текущая дата превышает `bannedUntil`:
|
||||
|
||||
```typescript
|
||||
// Пример: пользователь забанен до 2024-12-31
|
||||
const customer = {
|
||||
bannedUntil: "2024-12-31T23:59:59Z"
|
||||
};
|
||||
|
||||
// После 2024-12-31 функция вернет false
|
||||
const isBanned = isCustomerBanned(customer); // false
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Next Auth │ │ BaseService │ │ Client-side │
|
||||
│ (авторизация) │ │ (API методы) │ │ (UI) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ isCustomerBanned│ │ checkBanStatus()│ │ BanCheck │
|
||||
│ (утилита) │ │ (метод) │ │ (компонент) │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│ │ │
|
||||
└───────────────────────┼───────────────────────┘
|
||||
▼
|
||||
┌─────────────────────────┐
|
||||
│ bannedUntil (DB) │
|
||||
│ (Strapi/PostgreSQL) │
|
||||
└─────────────────────────┘
|
||||
```
|
||||
|
||||
## Безопасность
|
||||
## Преимущества системы
|
||||
|
||||
- Проверки выполняются на нескольких уровнях для максимальной безопасности
|
||||
- Серверные проверки (Next Auth, BaseService) являются основными
|
||||
- Клиентские проверки обеспечивают UX
|
||||
- Все проверки основаны на поле `bannedUntil` из базы данных
|
||||
- **Поле `bannedUntil` защищено от изменения пользователями** - только администраторы могут изменять статус блокировки
|
||||
✅ **Многоуровневая защита** - проверка на всех уровнях приложения
|
||||
✅ **Универсальность** - работает в веб-приложении и боте
|
||||
✅ **Простота использования** - один вызов `checkBanStatus()` в начале метода
|
||||
✅ **Безопасность** - пользователи не могут обойти бан
|
||||
✅ **UX** - понятные сообщения и страница для забаненных
|
||||
✅ **DRY принцип** - нет дублирования кода
|
||||
✅ **Легкость расширения** - просто добавить новые проверки
|
||||
@ -1,13 +1,13 @@
|
||||
/* eslint-disable canonical/id-match */
|
||||
import { getClientWithToken } from '../apollo/client';
|
||||
import { ERRORS } from '../constants/errors';
|
||||
import * as GQL from '../types';
|
||||
import { isCustomerBanned } from '@repo/utils/customer';
|
||||
|
||||
export const ERRORS = {
|
||||
const BASE_ERRORS = {
|
||||
MISSING_TELEGRAM_ID: 'Не указан Telegram ID',
|
||||
NOT_FOUND_CUSTOMER: 'Пользователь не найден',
|
||||
USER_BANNED: 'Пользователь заблокирован',
|
||||
};
|
||||
} as const;
|
||||
|
||||
type UserProfile = {
|
||||
telegramId: number;
|
||||
@ -18,7 +18,7 @@ export class BaseService {
|
||||
|
||||
constructor(user: UserProfile) {
|
||||
if (!user?.telegramId) {
|
||||
throw new Error(ERRORS.MISSING_TELEGRAM_ID);
|
||||
throw new Error(BASE_ERRORS.MISSING_TELEGRAM_ID);
|
||||
}
|
||||
|
||||
this._user = user;
|
||||
@ -34,10 +34,31 @@ export class BaseService {
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
if (!customer) throw new Error(ERRORS.NOT_FOUND_CUSTOMER);
|
||||
if (!customer) throw new Error(BASE_ERRORS.NOT_FOUND_CUSTOMER);
|
||||
|
||||
if (isCustomerBanned(customer)) {
|
||||
throw new Error(ERRORS.USER_BANNED);
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
|
||||
return { customer };
|
||||
}
|
||||
|
||||
protected async checkIsBanned() {
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
query: GQL.GetCustomerDocument,
|
||||
variables: this._user,
|
||||
});
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
if (!customer) {
|
||||
throw new Error(BASE_ERRORS.NOT_FOUND_CUSTOMER);
|
||||
}
|
||||
|
||||
if (isCustomerBanned(customer)) {
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
|
||||
return { customer };
|
||||
|
||||
@ -6,6 +6,8 @@ import { type VariablesOf } from '@graphql-typed-document-node/core';
|
||||
|
||||
export class CustomersService extends BaseService {
|
||||
async addMasters(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const newMasterIds = variables.data.masters;
|
||||
|
||||
// Проверяем, что пользователь не пытается изменить поле bannedUntil
|
||||
@ -40,6 +42,8 @@ export class CustomersService extends BaseService {
|
||||
}
|
||||
|
||||
async getClients(variables?: VariablesOf<typeof GQL.GetClientsDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -53,6 +57,8 @@ export class CustomersService extends BaseService {
|
||||
}
|
||||
|
||||
async getCustomer(variables: VariablesOf<typeof GQL.GetCustomerDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -66,6 +72,8 @@ export class CustomersService extends BaseService {
|
||||
}
|
||||
|
||||
async getMasters(variables?: VariablesOf<typeof GQL.GetMastersDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -81,6 +89,8 @@ export class CustomersService extends BaseService {
|
||||
async updateCustomer(
|
||||
variables: Omit<VariablesOf<typeof GQL.UpdateCustomerDocument>, 'documentId'>,
|
||||
) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { customer } = await this._getUser();
|
||||
|
||||
// Пров еряем, что пользователь не пытается изменить поле bannedUntil
|
||||
|
||||
@ -2,6 +2,7 @@ import { getClientWithToken } from '../apollo/client';
|
||||
import { ERRORS } from '../constants/errors';
|
||||
import * as GQL from '../types';
|
||||
import { type VariablesOf } from '@graphql-typed-document-node/core';
|
||||
import { isCustomerBanned } from '@repo/utils/customer';
|
||||
|
||||
export class RegistrationService {
|
||||
async createCustomer(variables: VariablesOf<typeof GQL.CreateCustomerDocument>) {
|
||||
@ -19,6 +20,10 @@ export class RegistrationService {
|
||||
}
|
||||
|
||||
async getCustomer(variables: VariablesOf<typeof GQL.GetCustomerDocument>) {
|
||||
if (variables.telegramId) {
|
||||
await this.checkBanStatus(variables.telegramId);
|
||||
}
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -32,6 +37,19 @@ export class RegistrationService {
|
||||
}
|
||||
|
||||
async updateCustomer(variables: VariablesOf<typeof GQL.UpdateCustomerDocument>) {
|
||||
// Проверяем бан для существующего пользователя
|
||||
if (variables.documentId) {
|
||||
const { query } = await getClientWithToken();
|
||||
const result = await query({
|
||||
query: GQL.GetCustomerDocument,
|
||||
variables: { documentId: variables.documentId },
|
||||
});
|
||||
const customer = result.data.customers.at(0);
|
||||
if (customer && isCustomerBanned(customer)) {
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
}
|
||||
|
||||
if (variables.data.bannedUntil) {
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
@ -48,4 +66,21 @@ export class RegistrationService {
|
||||
|
||||
return mutationResult.data;
|
||||
}
|
||||
|
||||
private async checkBanStatus(telegramId: number) {
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
query: GQL.GetCustomerDocument,
|
||||
variables: { telegramId },
|
||||
});
|
||||
|
||||
const customer = result.data.customers.at(0);
|
||||
|
||||
if (customer && isCustomerBanned(customer)) {
|
||||
throw new Error(ERRORS.NO_PERMISSION);
|
||||
}
|
||||
|
||||
return { customer };
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,8 @@ import { type VariablesOf } from '@graphql-typed-document-node/core';
|
||||
|
||||
export class ServicesService extends BaseService {
|
||||
async createService(variables: VariablesOf<typeof GQL.CreateServiceDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { customer } = await this._getUser();
|
||||
|
||||
const { mutate } = await getClientWithToken();
|
||||
@ -28,6 +30,8 @@ export class ServicesService extends BaseService {
|
||||
}
|
||||
|
||||
async getService(variables: VariablesOf<typeof GQL.GetServiceDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -39,6 +43,8 @@ export class ServicesService extends BaseService {
|
||||
}
|
||||
|
||||
async getServices(variables: VariablesOf<typeof GQL.GetServicesDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -50,6 +56,8 @@ export class ServicesService extends BaseService {
|
||||
}
|
||||
|
||||
async updateService(variables: VariablesOf<typeof GQL.UpdateServiceDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
await this.checkPermission(variables);
|
||||
|
||||
const { mutate } = await getClientWithToken();
|
||||
|
||||
@ -24,6 +24,8 @@ export const ERRORS = {
|
||||
|
||||
export class SlotsService extends BaseService {
|
||||
async createSlot(variables: VariablesOf<typeof GQL.CreateSlotDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
await this.checkBeforeCreate(variables);
|
||||
|
||||
const { customer } = await this._getUser();
|
||||
@ -48,6 +50,8 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
|
||||
async deleteSlot(variables: VariablesOf<typeof GQL.DeleteSlotDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
await this.checkPermission(variables);
|
||||
|
||||
const { slot } = await this.getSlot({ documentId: variables.documentId });
|
||||
@ -73,6 +77,8 @@ export class SlotsService extends BaseService {
|
||||
variables: VariablesOf<typeof GQL.GetSlotsDocument>,
|
||||
context: { services: string[] },
|
||||
) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
if (!variables.filters?.datetime_start) throw new Error(ERRORS.MISSING_DATETIME_START);
|
||||
if (!context?.services?.length) throw new Error(ERRORS.MISSING_SERVICES_IDS);
|
||||
|
||||
@ -146,6 +152,8 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
|
||||
async getSlot(variables: VariablesOf<typeof GQL.GetSlotDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -157,6 +165,8 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
|
||||
async getSlots(variables: VariablesOf<typeof GQL.GetSlotsDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
const { query } = await getClientWithToken();
|
||||
|
||||
const result = await query({
|
||||
@ -168,6 +178,8 @@ export class SlotsService extends BaseService {
|
||||
}
|
||||
|
||||
async updateSlot(variables: VariablesOf<typeof GQL.UpdateSlotDocument>) {
|
||||
await this.checkIsBanned();
|
||||
|
||||
await this.checkPermission(variables);
|
||||
await this.checkBeforeUpdateDatetime(variables);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user