Hotfix/bot registraion add contact (#99)
* refactor(registration): remove checkBanStatus method and related logic * fix(BaseService): streamline customer permission checks by consolidating conditions * bot: remove logger from context * refactor(bot): update conversation handling and middleware integration - Replaced the previous conversations middleware with the Grammy framework's implementation. - Introduced a loop to register custom conversations, enhancing the bot's conversation management capabilities. - Removed the addContact feature implementation from the add-contact file, streamlining the codebase. * feat(bot): add cancel command to conversation middleware - Implemented a '/cancel' command in the bot's conversation middleware to allow users to exit all conversations gracefully. - Removed the manual cancellation check from the addContact conversation, streamlining the code and improving user experience. * fix(bot): conversations register - disable minification in build configuration
This commit is contained in:
parent
0934417aaf
commit
3d0f74ef62
82
apps/bot/src/bot/conversations/add-contact.ts
Normal file
82
apps/bot/src/bot/conversations/add-contact.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/* eslint-disable id-length */
|
||||||
|
import { type Context } from '@/bot/context';
|
||||||
|
import { KEYBOARD_REMOVE, KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE } from '@/config/keyboards';
|
||||||
|
import { isCustomerMaster } from '@/utils/customer';
|
||||||
|
import { isValidPhoneNumber, normalizePhoneNumber } from '@/utils/phone';
|
||||||
|
import { type Conversation } from '@grammyjs/conversations';
|
||||||
|
import { CustomersService } from '@repo/graphql/api/customers';
|
||||||
|
import { RegistrationService } from '@repo/graphql/api/registration';
|
||||||
|
|
||||||
|
export async function addContact(conversation: Conversation<Context, Context>, ctx: Context) {
|
||||||
|
// Проверяем, что пользователь является мастером
|
||||||
|
const telegramId = ctx.from?.id;
|
||||||
|
if (!telegramId) {
|
||||||
|
return ctx.reply(await conversation.external(({ t }) => t('err-generic')));
|
||||||
|
}
|
||||||
|
|
||||||
|
const customerService = new CustomersService({ telegramId });
|
||||||
|
const { customer } = await customerService.getCustomer({ telegramId });
|
||||||
|
|
||||||
|
if (!customer) {
|
||||||
|
return ctx.reply(
|
||||||
|
await conversation.external(({ t }) => t('msg-need-phone')),
|
||||||
|
KEYBOARD_SHARE_PHONE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isCustomerMaster(customer)) {
|
||||||
|
return ctx.reply(await conversation.external(({ t }) => t('msg-not-master')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Просим отправить контакт клиента
|
||||||
|
await ctx.reply(await conversation.external(({ t }) => t('msg-send-client-contact')));
|
||||||
|
|
||||||
|
// Ждем любое сообщение от пользователя
|
||||||
|
const waitCtx = await conversation.wait();
|
||||||
|
|
||||||
|
// Проверяем, что отправлен контакт
|
||||||
|
if (!waitCtx.message?.contact) {
|
||||||
|
return ctx.reply(await conversation.external(({ t }) => t('msg-send-contact')));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { contact } = waitCtx.message;
|
||||||
|
const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim();
|
||||||
|
const phone = normalizePhoneNumber(contact.phone_number);
|
||||||
|
|
||||||
|
// Проверяем валидность номера телефона
|
||||||
|
if (!isValidPhoneNumber(phone)) {
|
||||||
|
return ctx.reply(await conversation.external(({ t }) => t('msg-invalid-phone')));
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Проверяем, есть ли клиент с таким номером
|
||||||
|
const { customer: existingCustomer } = await customerService.getCustomer({ phone });
|
||||||
|
let documentId = existingCustomer?.documentId;
|
||||||
|
|
||||||
|
// Если клиента нет, создаём нового
|
||||||
|
if (!documentId) {
|
||||||
|
const registrationService = new RegistrationService();
|
||||||
|
const createCustomerResult = await registrationService.createCustomer({ name, phone });
|
||||||
|
|
||||||
|
documentId = createCustomerResult?.createCustomer?.documentId;
|
||||||
|
if (!documentId) throw new Error('Клиент не создан');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем текущего мастера к клиенту
|
||||||
|
const masters = [customer.documentId];
|
||||||
|
await customerService.addMasters({ data: { masters }, documentId });
|
||||||
|
|
||||||
|
// Отправляем подтверждения и инструкции
|
||||||
|
await ctx.reply(await conversation.external(({ t }) => t('msg-contact-added', { name })));
|
||||||
|
await ctx.reply(await conversation.external(({ t }) => t('msg-contact-forward')));
|
||||||
|
await ctx.reply(await conversation.external(({ t }) => t('msg-share-bot')), KEYBOARD_SHARE_BOT);
|
||||||
|
} catch (error) {
|
||||||
|
await ctx.reply(
|
||||||
|
await conversation.external(({ t }) => t('err-with-details', { error: String(error) })),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await ctx.reply(await conversation.external(({ t }) => t('commands-list')), KEYBOARD_REMOVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.reply(await conversation.external(({ t }) => t('err-generic')), KEYBOARD_REMOVE);
|
||||||
|
}
|
||||||
1
apps/bot/src/bot/conversations/index.ts
Normal file
1
apps/bot/src/bot/conversations/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './add-contact';
|
||||||
@ -1,99 +1,11 @@
|
|||||||
/* eslint-disable id-length */
|
|
||||||
import { type Context } from '@/bot/context';
|
import { type Context } from '@/bot/context';
|
||||||
import { logHandle } from '@/bot/helpers/logging';
|
import { logHandle } from '@/bot/helpers/logging';
|
||||||
import { KEYBOARD_REMOVE, KEYBOARD_SHARE_BOT, KEYBOARD_SHARE_PHONE } from '@/config/keyboards';
|
|
||||||
import { isCustomerMaster } from '@/utils/customer';
|
|
||||||
import { isValidPhoneNumber, normalizePhoneNumber } from '@/utils/phone';
|
|
||||||
import { type Conversation, createConversation } from '@grammyjs/conversations';
|
|
||||||
import { CustomersService } from '@repo/graphql/api/customers';
|
|
||||||
import { RegistrationService } from '@repo/graphql/api/registration';
|
|
||||||
import { Composer } from 'grammy';
|
import { Composer } from 'grammy';
|
||||||
|
|
||||||
const composer = new Composer<Context>();
|
const composer = new Composer<Context>();
|
||||||
|
|
||||||
const feature = composer.chatType('private');
|
const feature = composer.chatType('private');
|
||||||
|
|
||||||
async function addContact(conversation: Conversation<Context, Context>, ctx: Context) {
|
|
||||||
// Проверяем, что пользователь является мастером
|
|
||||||
const telegramId = ctx.from?.id;
|
|
||||||
if (!telegramId) {
|
|
||||||
return ctx.reply(await conversation.external(({ t }) => t('err-generic')));
|
|
||||||
}
|
|
||||||
|
|
||||||
const customerService = new CustomersService({ telegramId });
|
|
||||||
const { customer } = await customerService.getCustomer({ telegramId });
|
|
||||||
|
|
||||||
if (!customer) {
|
|
||||||
return ctx.reply(
|
|
||||||
await conversation.external(({ t }) => t('msg-need-phone')),
|
|
||||||
KEYBOARD_SHARE_PHONE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isCustomerMaster(customer)) {
|
|
||||||
return ctx.reply(await conversation.external(({ t }) => t('msg-not-master')));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Просим отправить контакт клиента
|
|
||||||
await ctx.reply(await conversation.external(({ t }) => t('msg-send-client-contact')));
|
|
||||||
|
|
||||||
// Ждем любое сообщение от пользователя
|
|
||||||
const waitCtx = await conversation.wait();
|
|
||||||
|
|
||||||
// Проверяем команду отмены
|
|
||||||
if (waitCtx.message?.text === '/cancel') {
|
|
||||||
return ctx.reply(await conversation.external(({ t }) => t('msg-cancel')));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, что отправлен контакт
|
|
||||||
if (!waitCtx.message?.contact) {
|
|
||||||
return ctx.reply(await conversation.external(({ t }) => t('msg-send-contact')));
|
|
||||||
}
|
|
||||||
|
|
||||||
const { contact } = waitCtx.message;
|
|
||||||
const name = `${contact.first_name || ''} ${contact.last_name || ''}`.trim();
|
|
||||||
const phone = normalizePhoneNumber(contact.phone_number);
|
|
||||||
|
|
||||||
// Проверяем валидность номера телефона
|
|
||||||
if (!isValidPhoneNumber(phone)) {
|
|
||||||
return ctx.reply(await conversation.external(({ t }) => t('msg-invalid-phone')));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Проверяем, есть ли клиент с таким номером
|
|
||||||
const { customer: existingCustomer } = await customerService.getCustomer({ phone });
|
|
||||||
let documentId = existingCustomer?.documentId;
|
|
||||||
|
|
||||||
// Если клиента нет, создаём нового
|
|
||||||
if (!documentId) {
|
|
||||||
const registrationService = new RegistrationService();
|
|
||||||
const createCustomerResult = await registrationService.createCustomer({ name, phone });
|
|
||||||
|
|
||||||
documentId = createCustomerResult?.createCustomer?.documentId;
|
|
||||||
if (!documentId) throw new Error('Клиент не создан');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем текущего мастера к клиенту
|
|
||||||
const masters = [customer.documentId];
|
|
||||||
await customerService.addMasters({ data: { masters }, documentId });
|
|
||||||
|
|
||||||
// Отправляем подтверждения и инструкции
|
|
||||||
await ctx.reply(await conversation.external(({ t }) => t('msg-contact-added', { name })));
|
|
||||||
await ctx.reply(await conversation.external(({ t }) => t('msg-contact-forward')));
|
|
||||||
await ctx.reply(await conversation.external(({ t }) => t('msg-share-bot')), KEYBOARD_SHARE_BOT);
|
|
||||||
} catch (error) {
|
|
||||||
await ctx.reply(
|
|
||||||
await conversation.external(({ t }) => t('err-with-details', { error: String(error) })),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
await ctx.reply(await conversation.external(({ t }) => t('commands-list')), KEYBOARD_REMOVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx.reply(await conversation.external(({ t }) => t('err-generic')), KEYBOARD_REMOVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
feature.use(createConversation(addContact));
|
|
||||||
|
|
||||||
feature.command('addcontact', logHandle('command-add-contact'), async (ctx) => {
|
feature.command('addcontact', logHandle('command-add-contact'), async (ctx) => {
|
||||||
await ctx.conversation.enter('addContact');
|
await ctx.conversation.enter('addContact');
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { type Context } from './context';
|
import { type Context } from './context';
|
||||||
|
import * as conversations from './conversations';
|
||||||
import * as features from './features';
|
import * as features from './features';
|
||||||
import { unhandledFeature } from './features/unhandled';
|
import { unhandledFeature } from './features/unhandled';
|
||||||
import { errorHandler } from './handlers/errors';
|
import { errorHandler } from './handlers/errors';
|
||||||
@ -7,10 +8,9 @@ import * as middlewares from './middlewares';
|
|||||||
import { setCommands } from './settings/commands';
|
import { setCommands } from './settings/commands';
|
||||||
import { setInfo } from './settings/info';
|
import { setInfo } from './settings/info';
|
||||||
import { env } from '@/config/env';
|
import { env } from '@/config/env';
|
||||||
import { logger } from '@/utils/logger';
|
|
||||||
import { getRedisInstance } from '@/utils/redis';
|
import { getRedisInstance } from '@/utils/redis';
|
||||||
import { autoChatAction } from '@grammyjs/auto-chat-action';
|
import { autoChatAction } from '@grammyjs/auto-chat-action';
|
||||||
import { conversations } from '@grammyjs/conversations';
|
import { createConversation, conversations as grammyConversations } from '@grammyjs/conversations';
|
||||||
import { hydrate } from '@grammyjs/hydrate';
|
import { hydrate } from '@grammyjs/hydrate';
|
||||||
import { limit } from '@grammyjs/ratelimiter';
|
import { limit } from '@grammyjs/ratelimiter';
|
||||||
import { Bot } from 'grammy';
|
import { Bot } from 'grammy';
|
||||||
@ -38,14 +38,14 @@ export function createBot({ token }: Parameters_) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
bot.use(async (context, next) => {
|
bot.use(grammyConversations()).command('cancel', async (ctx) => {
|
||||||
context.logger = logger.child({
|
await ctx.conversation.exitAll();
|
||||||
update_id: context.update.update_id,
|
await ctx.reply(ctx.t('msg-cancel'));
|
||||||
});
|
|
||||||
await next();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bot.use(conversations());
|
for (const conversation of Object.values(conversations)) {
|
||||||
|
bot.use(createConversation(conversation));
|
||||||
|
}
|
||||||
|
|
||||||
setInfo(bot);
|
setInfo(bot);
|
||||||
setCommands(bot);
|
setCommands(bot);
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export default defineConfig({
|
|||||||
external: ['telegraf', 'zod'],
|
external: ['telegraf', 'zod'],
|
||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
loader: { '.json': 'copy' },
|
loader: { '.json': 'copy' },
|
||||||
minify: true,
|
minify: false,
|
||||||
noExternal: ['@repo'],
|
noExternal: ['@repo'],
|
||||||
outDir: './dist',
|
outDir: './dist',
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
|
|||||||
@ -53,11 +53,7 @@ export class BaseService {
|
|||||||
|
|
||||||
const customer = result.data.customers.at(0);
|
const customer = result.data.customers.at(0);
|
||||||
|
|
||||||
if (!customer) {
|
if (customer && isCustomerBanned(customer)) {
|
||||||
throw new Error(BASE_ERRORS.NOT_FOUND_CUSTOMER);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCustomerBanned(customer)) {
|
|
||||||
throw new Error(ERRORS.NO_PERMISSION);
|
throw new Error(ERRORS.NO_PERMISSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,10 +20,6 @@ export class RegistrationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getCustomer(variables: VariablesOf<typeof GQL.GetCustomerDocument>) {
|
async getCustomer(variables: VariablesOf<typeof GQL.GetCustomerDocument>) {
|
||||||
if (variables.telegramId) {
|
|
||||||
await this.checkBanStatus(variables.telegramId);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { query } = await getClientWithToken();
|
const { query } = await getClientWithToken();
|
||||||
|
|
||||||
const result = await query({
|
const result = await query({
|
||||||
@ -66,21 +62,4 @@ export class RegistrationService {
|
|||||||
|
|
||||||
return mutationResult.data;
|
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 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user