Skip to content

Commit

Permalink
Merge pull request #29 from TogetherCrew/temporal
Browse files Browse the repository at this point in the history
verification workflow
  • Loading branch information
cyri113 authored Dec 20, 2024
2 parents 3490ac8 + 1ba8d81 commit d6f7d68
Show file tree
Hide file tree
Showing 7 changed files with 970 additions and 246 deletions.
26 changes: 23 additions & 3 deletions apps/bot/src/bot.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Module } from '@nestjs/common';
import { BotService } from './bot.service';
import { ConfigModule } from '@nestjs/config';
// import { BotUpdate } from './bot.update';
import { ConfigModule, ConfigService } from '@nestjs/config';

import {
schemaConfig,
telegramConfig,
Expand All @@ -10,17 +10,37 @@ import {
} from '@app/common';
import { Services } from '@app/common';
import { BotController } from './bot.controller';
import { TemporalModule } from 'nestjs-temporal';
import { temporalConfig } from '@app/common/config/temporal.config';
import { Connection } from '@temporalio/client';

@Module({
imports: [
ConfigModule.forRoot({
validationSchema: schemaConfig,
load: [telegramConfig, rmqConfig],
load: [telegramConfig, rmqConfig, temporalConfig],
isGlobal: true,
}),
RmqModule.register(Services.EventStore),
RmqModule.register(Services.GraphStore),
RmqModule.register(Services.TGQuestionService),
TemporalModule.registerClientAsync({
inject: [ConfigService],
useFactory: async (config: ConfigService) => {
const uri = config.get('temporal.uri');
console.log('uri', uri);
let connection: Connection;
try {
connection = await Connection.connect({
address: uri,
});
} catch (error) {
console.error('Failed to connect to Temporal:', error);
throw error;
}
return { connection };
},
}),
],
controllers: [BotController],
providers: [BotService],
Expand Down
6 changes: 6 additions & 0 deletions apps/bot/src/bot.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ describe('BotService', () => {
}),
},
},
{
provide: 'TemporalQueue_default',
useValue: {
start: jest.fn(),
},
},
],
}).compile();

Expand Down
99 changes: 83 additions & 16 deletions apps/bot/src/bot.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { IgnoreEvent, Services, UpdateEvent } from '@app/common';
import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ClientProxy } from '@nestjs/microservices';
import { API_CONSTANTS, Bot, RawApi } from 'grammy';
import { WorkflowClient } from '@temporalio/client';
import { API_CONSTANTS, Bot, Context, RawApi } from 'grammy';
import { Other } from 'grammy/out/core/api';
import { Message } from 'grammy/types';
import { InjectTemporalClient } from 'nestjs-temporal';

@Injectable()
export class BotService implements OnModuleInit {
Expand All @@ -17,19 +19,13 @@ export class BotService implements OnModuleInit {
@Inject(Services.TGQuestionService.name)
private readonly tgQuestionClient: ClientProxy,
private readonly configService: ConfigService,
@InjectTemporalClient()
private readonly temporalClient: WorkflowClient,
) {
this.bot = new Bot(configService.get<string>('telegram.token'));

this.bot.command('start', async (ctx) => {
await ctx.reply(
'<b>Welcome!</b>\n\n<u><b>Get Started</b></u>\n\nTo get you up and running, there are two simple steps:\n\n<b>1. Add me to your group or channel</b>\n\nTo add me to your group or channel, simply tap on my username (at the top) and select <i>"Add to Group or Channel"</i>.\n\nSelect the group or channel you want.\n\nI will need <b>Admin Rights</b>, but you can disable all additional privileges.\n\n<b>2. Verify me on <a href="https://app.togethercrew.com">app.togethercrew.com</a></b>\n\nHead over to <a href="https://app.togethercrew.com">https://app.togethercrew.com</a>.\n\nIn your community settings, add a Telegram platform.\n\nCopy the verify command into your group and send it.\n\nI will take care of the rest.',
{ parse_mode: 'HTML' },
);
});

this.bot.command('verify', async (ctx) => {
await ctx.reply('[Coming soon] This will blow your mind.');
});
this.bot.command('start', this.start);
this.bot.command('verify', this.isAdmin, this.verify);

Object.values(IgnoreEvent).map((event) => {
this.bot.on(event, () => {
Expand Down Expand Up @@ -57,17 +53,16 @@ export class BotService implements OnModuleInit {
return;
});
});

this.bot.api.setMyCommands([
{ command: 'start', description: 'Get started with the TogetherCrewBot' },
{ command: 'verify', description: 'Verify your group.' },
]);
}

async onModuleInit() {
await this.bot.start({
allowed_updates: API_CONSTANTS.ALL_UPDATE_TYPES,
});
await this.bot.api.setMyCommands([
{ command: 'start', description: 'Get started with the TogetherCrewBot' },
{ command: 'verify', description: 'Verify your group.' },
]);
}

async sendMessage(
Expand All @@ -79,4 +74,76 @@ export class BotService implements OnModuleInit {
this.logger.log(`Message ${receipt.message_id} sent to ${receipt.chat.id}`);
return receipt;
}

// Middleware to check if a user is an admin
protected isAdmin = async (ctx: Context, next: () => Promise<void>) => {
if (!ctx.from || !ctx.chat) {
return ctx.reply('This command can only be used in a group or channel.');
}

try {
const chatMember = await ctx.api.getChatMember(ctx.chat.id, ctx.from.id);
if (['administrator', 'creator'].includes(chatMember.status)) {
// Proceed to the next middleware or handler
return next();
} else {
// If not an admin, send an error message
return ctx.reply('You must be an admin to use this command.');
}
} catch (error) {
console.error('Error fetching chat member info:', error);
return ctx.reply('An error occurred while checking permissions.');
}
};

protected start = async (ctx: Context) => {
const text = [
'<b>Welcome!</b>',
'<u><b>Get Started</b></u>',
'<b>1. Add me to your group or channel</b>',
[
'Tap on my username (top of the screen) and select <i>Add to Group or Channel</i>.',
'Select the group or channel you want and enable <b>Admin Rights</b>.',
'You can disable all additional privileges.',
].join('\n'),
'<b>2. Verify me</b>',
[
'Head over to <a href="https://app.togethercrew.com">https://app.togethercrew.com</a>.',
'In your community settings, add Telegram.',
'Copy-paste the verify command into your group and send it.',
].join('\n'),
'I will take care of the rest.',
].join('\n\n');

await ctx.reply(text, { parse_mode: 'HTML' });
};

protected verify = async (ctx: Context) => {
const token = ctx.match;
const chat = ctx.chat;
const from = ctx.from;

if (!token || !chat || !from) {
await ctx.reply(
'Not enough data to complete this request. Reach out to our support team.',
);
return;
}

try {
const handle = await this.temporalClient.start('TelegramVerifyWorkflow', {
taskQueue: this.configService.get<string>('temporal.queue'),
args: [{ token, chat, from }],
workflowId: `telegram:verify:${token}`,
});

const result = await handle.result();
await ctx.reply(result);
} catch (error) {
console.error(error);
await ctx.reply(
'Something unexpected happened. Reach out to our support team.',
);
}
};
}
3 changes: 3 additions & 0 deletions libs/common/src/config/schema.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ export const schemaConfig = Joi.object({
NEO4J_PORT: Joi.number().default(7687),
NEO4J_USER: Joi.string().required(),
NEO4J_PASS: Joi.string().required(),

TEMPORAL_URI: Joi.string().default('localhost:7233'),
TEMPORAL_QUEUE: Joi.string().default('TEMPORAL_QUEUE_LIGHT'),
});
6 changes: 6 additions & 0 deletions libs/common/src/config/temporal.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const temporalConfig = (): Record<string, unknown> => ({
temporal: {
uri: process.env.TEMPORAL_URI,
queue: process.env.TEMPORAL_QUEUE,
},
});
Loading

0 comments on commit d6f7d68

Please sign in to comment.