diff --git a/readme.md b/readme.md index 90326fb..b6b36cd 100644 --- a/readme.md +++ b/readme.md @@ -5,9 +5,11 @@ To run admin panel, run docker image: ```sh -docker run --env-file= -it --network tribalizm admin:1.0 +docker run --env-file= -it --network admin:1.0 ``` +Network host is when developing locally. + ## Development NOTE: you have to read all environment variables, eg. with `set-env ./.env.develop` where `set-env` diff --git a/src/main.ts b/src/main.ts index ef6d8eb..0ff8d58 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,27 +1,79 @@ import express from 'express' +import { MongoClient } from 'mongodb' +import { createMongoContext, createMongoTelegramContext } from './mongo-context' import { makeBot } from './plugins/ui/telegram/bot' +import { makeTribalizm } from './use-cases/tribalism' +import { Scheduler } from './use-cases/utils/scheduler' +import { TaskDispatcher } from './use-cases/utils/task-dispatcher' -const app = express() -app.use(express.json()) +async function getDb() { + const { DB_USER, DB_HOST, DB_PASS } = process.env + const url = `mongodb://${DB_USER}:${DB_PASS}@${[DB_HOST]}:27017` + const client = new MongoClient(url, { useUnifiedTopology: true }) + try { + await client.connect() -/** this one used to check server is running */ -// TODO replace with smth form here: https://expressjs.com/en/advanced/healthcheck-graceful-shutdown.html -app.get('/ping', (req, res) => { - console.log('ping recieved') - res.end('pong') -}) + const db = client.db('tribalizm') + await db.command({ ping: 1 }) + return { db, client } + } catch (e) { + await client.close() + throw e + } +} + +async function main() { + const { db, client } = await getDb() + + const context = createMongoContext(db) + + const { tgUsersAdapter, messageStore } = createMongoTelegramContext( + db, + context.stores.userStore + ) + + const tribalizm = makeTribalizm(context) -makeBot({ - telegramUsersAdapter: null as any, - webHook: { - domain: 'tribalizm.rblab.net', - }, - tribalism: null as any, - token: process.env.BOT_TOKEN, - notificationBus: null as any, - messageStore: null as any, -}).then((bot) => { + const bot = await makeBot({ + telegramUsersAdapter: tgUsersAdapter, + messageStore: messageStore, + tribalizm: tribalizm, + token: process.env.BOT_TOKEN, + notificationBus: context.async.notificationBus, + }) + + const scheduler = new Scheduler(context.stores.taskStore) + const taskDispatcher = new TaskDispatcher(tribalizm, scheduler) + + const app = express() + app.use(express.json()) + + /** this one used to check server is running */ + // TODO replace with something form here: + // https://expressjs.com/en/advanced/healthcheck-graceful-shutdown.html + app.get('/ping', (req, res) => { + console.log('ping received') + res.end('pong') + }) app.use(bot.webhookCallback('/tg-hook')) + app.get('/check-queue', async (req, res) => { + await taskDispatcher.run() + res.end('ok') + }) + + const server = app.listen(3000, () => { + console.log('listening on 3000') + bot.telegram.setWebhook('https://tribalizm-1.rblab.net/tg-hook') + }) - app.listen(3000) + process.once('SIGINT', stop) + process.once('SIGTERM', stop) + async function stop() { + server.close() + await client.close() + } +} +main().catch((e) => { + console.error(e) + process.exit(1) }) diff --git a/src/plugins/ui/i18n/en/index.ts b/src/plugins/ui/i18n/en/index.ts index 4e08657..e22aebf 100644 --- a/src/plugins/ui/i18n/en/index.ts +++ b/src/plugins/ui/i18n/en/index.ts @@ -37,13 +37,15 @@ const en: BaseTranslation = { requestLocation: '🌍 Share my location', apply: '🚀 Send application', count: 'Members count:', - searchIn: 'Searchin in {city}: ', + searchIn: 'Trines in {city}: ', applyText: 'Please, write tribe\'s chief and shaman about yourself and why do you want to join "{tribe}" tribe?', applicationSent: '📨 Your application has been sent. Tribe chief will propose a meeting soon.', applicationSentShort: '☑️ Applied!', - cantFindCity: "Can't find your city, please type it's name." + cantFindCity: "Can't find your city, please type it's name.", + unknownCity: 'Cannot find such a city', + nothingFound: 'No tribes in {city} so far...', }, elders: { chief: 'chief', @@ -182,35 +184,38 @@ const en: BaseTranslation = { errors: { // TODO fill other errors texts!! - UpdateFinishedBrainstormError: '', - SelfVotingIdeaError: '', - DoubleVotingError: '', - UpdateFinishedIdeaError: '', - InvalidTimeProposal: '', - InvalidAcceptanceTime: '', - NotYourQuest: '', - IndeclinableError: '', - QuestIncompleteError: '', - NotParticipated: '', - VoteRangeError: '', - SelfVotingError: '', - ApplicationTransitionError: '', - NoChiefTribeError: '', - EntityNotFound: '', - NotYourTribe: '', - NoIdeaError: '', - AlreadyHaveChief: '', - ElderMismatchError: '', - WrongQuestError: '', + UpdateFinishedBrainstormError: '🚫 This brainstorm is already over.', + SelfVotingIdeaError: '🚫 You cannot vote for your own idea.', + DoubleVotingError: 'Cannot vote twice.', + UpdateFinishedIdeaError: + 'This idea already in implementation (or implemented).', + InvalidTimeProposal: 'It is impossible to meet at that time!', + InvalidAcceptanceTime: '🤬 InvalidAcceptanceTime? 🤨', + NotYourQuest: '🚫 Nope. This is not your quest.', + IndeclinableError: 'You cannot decline this type of quests.', + QuestIncompleteError: + 'Cannot agree on quest with incomplete information.', + NotParticipated: "🚫 Nope. You cannot vote for gathering you' declined", + VoteRangeError: 'This vot is out of available range.', + SelfVotingError: '🚫 Nope. You cannot rate yourself', + ApplicationTransitionError: '🤬 ApplicationTransitionError? 🤨', + NoChiefTribeError: '🤨 Hmm... this tribe has no chief.', + EntityNotFound: '🤨 404. Not found. What did you try to find?', + NotYourTribe: '🚫 Nope. You are not from this tribe (any more?).', + NoIdeaError: '🤬 NoIdeaError? 🤨', + AlreadyHaveChief: '🤨 Hmm... this tribe already has a chief.', + ElderMismatchError: '🤬 ElderMismatchError? 🤨', + WrongQuestError: '🤬 WrongQuestError? 🤨', NoChiefSetError: '🤨 Hmm... this tribe has no chief.', - NoShamanSetError: '', + NoShamanSetError: '🤨 Hmm... this tribe has no shaman.', WrongPhaseError: '🚫 Sorry, you cannot change application process now.', - VotingNotStartedError: '', - ExternalMemberVoteError: '', - NotEnoughMembers: '', + VotingNotStartedError: '🚫 Voting is not started yet', + ExternalMemberVoteError: + '🚫 Nope. You are not from this tribe (any more?).', + NotEnoughMembers: '🤬 NotEnoughMembers? 🤨', NotAChiefError: '🚫 Sorry, you cannot start a brainstorm, only tribe chief can', - FinalyzeBeforeVotingError: + FinalizeBeforeVotingError: '🤬 System error! Cannot finalize storm before voting', StormNotStarted: '🤬 System error! Cannot add idea: brainstorm is not raging', diff --git a/src/plugins/ui/i18n/i18n-types.ts b/src/plugins/ui/i18n/i18n-types.ts index 3d07761..53e90e4 100644 --- a/src/plugins/ui/i18n/i18n-types.ts +++ b/src/plugins/ui/i18n/i18n-types.ts @@ -101,7 +101,7 @@ export type Translation = { */ 'count': string /** - * Searchin in {city}: + * Trines in {city}: * @param {unknown} city */ 'searchIn': RequiredParams1<'city'> @@ -122,6 +122,15 @@ export type Translation = { * Can't find your city, please type it's name. */ 'cantFindCity': string + /** + * Cannot find such a city + */ + 'unknownCity': string + /** + * No tribes in {city} so far... + * @param {unknown} city + */ + 'nothingFound': RequiredParams1<'city'> } 'elders': { /** @@ -638,37 +647,109 @@ export type Translation = { 'end': string } 'errors': { + /** + * 🚫 This brainstorm is already over. + */ 'UpdateFinishedBrainstormError': string + /** + * 🚫 You cannot vote for your own idea. + */ 'SelfVotingIdeaError': string + /** + * Cannot vote twice. + */ 'DoubleVotingError': string + /** + * This idea already in implementation (or implemented). + */ 'UpdateFinishedIdeaError': string + /** + * It is impossible to meet at that time! + */ 'InvalidTimeProposal': string + /** + * 🤬 InvalidAcceptanceTime? 🤨 + */ 'InvalidAcceptanceTime': string + /** + * 🚫 Nope. This is not your quest. + */ 'NotYourQuest': string + /** + * You cannot decline this type of quests. + */ 'IndeclinableError': string + /** + * Cannot agree on quest with incomplete information. + */ 'QuestIncompleteError': string + /** + * 🚫 Nope. You cannot vote for gathering you' declined + */ 'NotParticipated': string + /** + * This vot is out of available range. + */ 'VoteRangeError': string + /** + * 🚫 Nope. You cannot rate yourself + */ 'SelfVotingError': string + /** + * 🤬 ApplicationTransitionError? 🤨 + */ 'ApplicationTransitionError': string + /** + * 🤨 Hmm... this tribe has no chief. + */ 'NoChiefTribeError': string + /** + * 🤨 404. Not found. What did you try to find? + */ 'EntityNotFound': string + /** + * 🚫 Nope. You are not from this tribe (any more?). + */ 'NotYourTribe': string + /** + * 🤬 NoIdeaError? 🤨 + */ 'NoIdeaError': string + /** + * 🤨 Hmm... this tribe already has a chief. + */ 'AlreadyHaveChief': string + /** + * 🤬 ElderMismatchError? 🤨 + */ 'ElderMismatchError': string + /** + * 🤬 WrongQuestError? 🤨 + */ 'WrongQuestError': string /** * 🤨 Hmm... this tribe has no chief. */ 'NoChiefSetError': string + /** + * 🤨 Hmm... this tribe has no shaman. + */ 'NoShamanSetError': string /** * 🚫 Sorry, you cannot change application process now. */ 'WrongPhaseError': string + /** + * 🚫 Voting is not started yet + */ 'VotingNotStartedError': string + /** + * 🚫 Nope. You are not from this tribe (any more?). + */ 'ExternalMemberVoteError': string + /** + * 🤬 NotEnoughMembers? 🤨 + */ 'NotEnoughMembers': string /** * 🚫 Sorry, you cannot start a brainstorm, only tribe chief can @@ -677,7 +758,7 @@ export type Translation = { /** * 🤬 System error! Cannot finalize storm before voting */ - 'FinalyzeBeforeVotingError': string + 'FinalizeBeforeVotingError': string /** * 🤬 System error! Cannot add idea: brainstorm is not raging */ @@ -784,7 +865,7 @@ export type TranslationFunctions = { */ 'count': () => LocalizedString /** - * Searchin in {city}: + * Trines in {city}: */ 'searchIn': (arg: { city: unknown }) => LocalizedString /** @@ -803,6 +884,14 @@ export type TranslationFunctions = { * Can't find your city, please type it's name. */ 'cantFindCity': () => LocalizedString + /** + * Cannot find such a city + */ + 'unknownCity': () => LocalizedString + /** + * No tribes in {city} so far... + */ + 'nothingFound': (arg: { city: unknown }) => LocalizedString } 'elders': { /** @@ -1269,37 +1358,109 @@ export type TranslationFunctions = { 'end': () => LocalizedString } 'errors': { + /** + * 🚫 This brainstorm is already over. + */ 'UpdateFinishedBrainstormError': () => LocalizedString + /** + * 🚫 You cannot vote for your own idea. + */ 'SelfVotingIdeaError': () => LocalizedString + /** + * Cannot vote twice. + */ 'DoubleVotingError': () => LocalizedString + /** + * This idea already in implementation (or implemented). + */ 'UpdateFinishedIdeaError': () => LocalizedString + /** + * It is impossible to meet at that time! + */ 'InvalidTimeProposal': () => LocalizedString + /** + * 🤬 InvalidAcceptanceTime? 🤨 + */ 'InvalidAcceptanceTime': () => LocalizedString + /** + * 🚫 Nope. This is not your quest. + */ 'NotYourQuest': () => LocalizedString + /** + * You cannot decline this type of quests. + */ 'IndeclinableError': () => LocalizedString + /** + * Cannot agree on quest with incomplete information. + */ 'QuestIncompleteError': () => LocalizedString + /** + * 🚫 Nope. You cannot vote for gathering you' declined + */ 'NotParticipated': () => LocalizedString + /** + * This vot is out of available range. + */ 'VoteRangeError': () => LocalizedString + /** + * 🚫 Nope. You cannot rate yourself + */ 'SelfVotingError': () => LocalizedString + /** + * 🤬 ApplicationTransitionError? 🤨 + */ 'ApplicationTransitionError': () => LocalizedString + /** + * 🤨 Hmm... this tribe has no chief. + */ 'NoChiefTribeError': () => LocalizedString + /** + * 🤨 404. Not found. What did you try to find? + */ 'EntityNotFound': () => LocalizedString + /** + * 🚫 Nope. You are not from this tribe (any more?). + */ 'NotYourTribe': () => LocalizedString + /** + * 🤬 NoIdeaError? 🤨 + */ 'NoIdeaError': () => LocalizedString + /** + * 🤨 Hmm... this tribe already has a chief. + */ 'AlreadyHaveChief': () => LocalizedString + /** + * 🤬 ElderMismatchError? 🤨 + */ 'ElderMismatchError': () => LocalizedString + /** + * 🤬 WrongQuestError? 🤨 + */ 'WrongQuestError': () => LocalizedString /** * 🤨 Hmm... this tribe has no chief. */ 'NoChiefSetError': () => LocalizedString + /** + * 🤨 Hmm... this tribe has no shaman. + */ 'NoShamanSetError': () => LocalizedString /** * 🚫 Sorry, you cannot change application process now. */ 'WrongPhaseError': () => LocalizedString + /** + * 🚫 Voting is not started yet + */ 'VotingNotStartedError': () => LocalizedString + /** + * 🚫 Nope. You are not from this tribe (any more?). + */ 'ExternalMemberVoteError': () => LocalizedString + /** + * 🤬 NotEnoughMembers? 🤨 + */ 'NotEnoughMembers': () => LocalizedString /** * 🚫 Sorry, you cannot start a brainstorm, only tribe chief can @@ -1308,7 +1469,7 @@ export type TranslationFunctions = { /** * 🤬 System error! Cannot finalize storm before voting */ - 'FinalyzeBeforeVotingError': () => LocalizedString + 'FinalizeBeforeVotingError': () => LocalizedString /** * 🤬 System error! Cannot add idea: brainstorm is not raging */ diff --git a/src/plugins/ui/telegram/bot.ts b/src/plugins/ui/telegram/bot.ts index 3578f27..2ebad66 100644 --- a/src/plugins/ui/telegram/bot.ts +++ b/src/plugins/ui/telegram/bot.ts @@ -33,8 +33,8 @@ interface PublicHookConfig { interface BotConfig { reportError?: (err: unknown) => Promise | void telegramUsersAdapter: TelegramUsersAdapter - webHook: PublicHookConfig - tribalism: Tribalizm + webHook?: PublicHookConfig + tribalizm: Tribalizm token: string | undefined notificationBus: NotificationBus messageStore: TelegramMessageStore @@ -75,7 +75,7 @@ export async function makeBot(config: BotConfig) { await reportError(err) } ctx.tribalizm = wrapWithErrorHandler( - config.tribalism, + config.tribalizm, reportContextError ) @@ -141,25 +141,13 @@ export async function makeBot(config: BotConfig) { coordinationScreen(bot, config.notificationBus, config.telegramUsersAdapter) gatheringScreen(bot, config.notificationBus, config.telegramUsersAdapter) - if ('domain' in config.webHook) { + if (config.webHook) { await bot.launch({ webhook: { ...config.webHook, hookPath: config.webHook.path }, }) - } else if ('path' in config.webHook) { - await bot.telegram.setWebhook( - `http://localhost:${config.webHook.port}${config.webHook.path}` - ) - await bot.launch({ - webhook: { - hookPath: config.webHook.path, - port: config.webHook.port, - }, - }) + // Enable graceful stop + process.once('SIGINT', () => bot.stop('SIGINT')) + process.once('SIGTERM', () => bot.stop('SIGTERM')) } - - // Enable graceful stop - process.once('SIGINT', () => bot.stop('SIGINT')) - process.once('SIGTERM', () => bot.stop('SIGTERM')) - return bot } diff --git a/src/plugins/ui/telegram/screens/tribes-list.ts b/src/plugins/ui/telegram/screens/tribes-list.ts index 99f37ae..16f3647 100644 --- a/src/plugins/ui/telegram/screens/tribes-list.ts +++ b/src/plugins/ui/telegram/screens/tribes-list.ts @@ -1,4 +1,5 @@ import { Markup, Telegraf } from 'telegraf' +import { text } from 'telegraf/typings/button' import { Maybe, notEmpty } from '../../../../ts-utils' import { TribeInfo } from '../../../../use-cases/tribes-show' import { i18n } from '../../i18n/i18n-ctx' @@ -24,7 +25,8 @@ function isApplyState(state: Maybe): state is ApplyState { export function tribesListScreen(bot: Telegraf) { bot.action('list-tribes', async (ctx) => { const texts = i18n(ctx).tribesList - ctx.user.setState({ type: 'locate-state' }) + // TODO check if city already set + await ctx.user.setState({ type: 'locate-state' }) const keyboard = Markup.keyboard([ Markup.button.locationRequest(texts.requestLocation()), @@ -38,6 +40,7 @@ export function tribesListScreen(bot: Telegraf) { bot.on('location', async (ctx, next) => { if (isLocateState(ctx.user.state)) { + const texts = i18n(ctx).tribesList const city = await ctx.tribalizm.locateUser.locateUserByCoordinates( { latitude: ctx.message.location.latitude, @@ -46,13 +49,19 @@ export function tribesListScreen(bot: Telegraf) { } ) if (!city) { - ctx.reply(i18n(ctx).tribesList.cantFindCity()) + ctx.reply(texts.cantFindCity()) + return } + await ctx.reply(texts.searchIn({ city }), Markup.removeKeyboard()) const tribes = await ctx.tribalizm.tribesShow.getLocalTribes({ limit: 3, userId: ctx.user.userId, }) - showTribesList(ctx, tribes) + if (tribes.length) { + showTribesList(ctx, tribes) + } else { + ctx.reply(texts.nothingFound({ city })) + } ctx.user.setState(null) } else { next() @@ -62,20 +71,32 @@ export function tribesListScreen(bot: Telegraf) { bot.on('text', async (ctx, next) => { const state = ctx.user.state if (isLocateState(state)) { - await ctx.tribalizm.locateUser.locateUserByCityName({ + const texts = i18n(ctx).tribesList + const city = await ctx.tribalizm.locateUser.locateUserByCityName({ cityName: ctx.message.text, userId: ctx.user.userId, }) - const tribes = await ctx.tribalizm.tribesShow.getLocalTribes({ - limit: 3, - userId: ctx.user.userId, - }) + if (!city) { + ctx.reply(texts.unknownCity()) + } // remove "share location" button await ctx.reply( - i18n(ctx).tribesList.searchIn({ city: ctx.message.text }), + texts.searchIn({ city: ctx.message.text }), Markup.removeKeyboard() ) - showTribesList(ctx, tribes) + const tribes = await ctx.tribalizm.tribesShow.getLocalTribes({ + limit: 3, + userId: ctx.user.userId, + }) + if (tribes.length) { + showTribesList(ctx, tribes) + } else { + ctx.reply( + texts.nothingFound({ + city: ctx.message.text, + }) + ) + } ctx.user.setState(null) } else if (isApplyState(state)) { const texts = i18n(ctx).tribesList diff --git a/src/plugins/ui/telegram/ui-tests-mock-bot.ts b/src/plugins/ui/telegram/ui-tests-mock-bot.ts index 4f20e4d..2ae284d 100644 --- a/src/plugins/ui/telegram/ui-tests-mock-bot.ts +++ b/src/plugins/ui/telegram/ui-tests-mock-bot.ts @@ -94,7 +94,7 @@ async function run() { port: 3000, domain: 'tribalizm-1.rblab.net', }, - tribalism: context.tribalism, + tribalizm: context.tribalizm, token: process.env.BOT_TOKEN, notificationBus: context.async.notificationBus, messageStore: new TelegramMessageInMemoryStore(), diff --git a/src/plugins/ui/telegram/users-adapter.ts b/src/plugins/ui/telegram/users-adapter.ts index 77cae89..3eb770b 100644 --- a/src/plugins/ui/telegram/users-adapter.ts +++ b/src/plugins/ui/telegram/users-adapter.ts @@ -50,7 +50,14 @@ export class TelegramUser implements SavedTelegramUser { } else { delete this.state } - await this.store.save({ ...this }) + await this.store.save({ + id: this.id, + chatId: this.chatId, + userId: this.userId, + locale: this.locale, + state: this.state, + username: this.username, + }) } } diff --git a/src/specs/gathering-finale.spec.ts b/src/specs/gathering-finale.spec.ts index 054d255..ca70fd5 100644 --- a/src/specs/gathering-finale.spec.ts +++ b/src/specs/gathering-finale.spec.ts @@ -238,7 +238,7 @@ async function setUp() { const gatheringFinale = new GatheringFinale(context) async function makeGathering() { - await context.tribalism.gatheringDeclare.declare({ + await context.tribalizm.gatheringDeclare.declare({ description: 'lets OLOLO together!', place: 'the Foo Bar', time: 100500200500, @@ -251,13 +251,13 @@ async function setUp() { const declined = [10, 7] for (let m of members) { if (accepted.includes(members.indexOf(m))) { - await context.tribalism.gatheringAcknowledge.accept({ + await context.tribalizm.gatheringAcknowledge.accept({ gatheringId: gathering.id, memberId: m.id, }) } if (declined.includes(members.indexOf(m))) { - await context.tribalism.gatheringAcknowledge.decline({ + await context.tribalizm.gatheringAcknowledge.decline({ gatheringId: gathering.id, memberId: m.id, }) diff --git a/src/specs/test-context.ts b/src/specs/test-context.ts index 4a09df4..8f1c548 100644 --- a/src/specs/test-context.ts +++ b/src/specs/test-context.ts @@ -38,6 +38,7 @@ import { LocateUser } from '../use-cases/locate-user' import { QuestNegotiation } from '../use-cases/negotiate-quest' import { QuestFinale } from '../use-cases/quest-finale' import { SpawnQuest } from '../use-cases/spawn-quest' +import { makeTribalizm } from '../use-cases/tribalism' import { TribeShow } from '../use-cases/tribes-show' import { Message } from '../use-cases/utils/message' import { NotificationBus } from '../use-cases/utils/notification-bus' @@ -217,25 +218,9 @@ export async function createContext() { context.stores.memberStore.save(member) } - const tribalism = { - addIdea: new AddIdea(context), - brainstormLifecycle: new BrainstormLifecycle(context), - gatheringAcknowledge: new GatheringAcknowledge(context), - gatheringDeclare: new GatheringDeclare(context), - gatheringFinale: new GatheringFinale(context), - initiation: new Initiation(context), - introductionQuests: new IntroductionQuests(context), - ideasIncarnation: new IdeasIncarnation(context), - tribeApplication: new TribeApplication(context), - tribesShow: new TribeShow(context), - questNegotiation: new QuestNegotiation(context), - questFinale: new QuestFinale(context), - spawnQuest: new SpawnQuest(context), - voting: new Voting(context), - locateUser: new LocateUser(context), - } + const tribalizm = makeTribalizm(context) const scheduler = new Scheduler(context.stores.taskStore) - const taskDispatcher = new TaskDispatcher(tribalism, scheduler) + const taskDispatcher = new TaskDispatcher(tribalizm, scheduler) async function requestTaskQueue() { if (process.env.chatDebug) { @@ -254,7 +239,7 @@ export async function createContext() { return { ...context, testing, - tribalism, + tribalizm, addVotes, requestTaskQueue, } diff --git a/src/specs/tg-bot-scenarios/bot-utils.ts b/src/specs/tg-bot-scenarios/bot-utils.ts index 39026b2..9b3a8b9 100644 --- a/src/specs/tg-bot-scenarios/bot-utils.ts +++ b/src/specs/tg-bot-scenarios/bot-utils.ts @@ -175,7 +175,7 @@ export async function createTelegramContext( const bot = await makeBot({ notificationBus: context.async.notificationBus, token, - tribalism: context.tribalism, + tribalizm: context.tribalizm, webHook: { port: 9002, path: '/tg-hook', @@ -192,6 +192,7 @@ export async function createTelegramContext( } }, }) + bot.telegram.setWebhook('http://localhost:9002/tg-hook') function makeClient(user: string, chat: string) { makeClient.counter++ diff --git a/src/specs/tg-bot-scenarios/brainstorm.spec.ts b/src/specs/tg-bot-scenarios/brainstorm.spec.ts index 1fe1b56..ecb34ed 100644 --- a/src/specs/tg-bot-scenarios/brainstorm.spec.ts +++ b/src/specs/tg-bot-scenarios/brainstorm.spec.ts @@ -25,11 +25,13 @@ describe('Brainstorm [integration]', () => { it('Main scenario', async () => { // Chief arranges brainstorm await world.admin.notifyBrainstorm({ memberId: world.chief.member.id }) - const chiefBrstmNoteUpd = await world.chief.chat() - expect(chiefBrstmNoteUpd.length).toBe(1) - const brstBtns = getInlineKeyCallbacks(chiefBrstmNoteUpd[0]) - expect(brstBtns.length).toBe(1) - const calendar = await world.chief.chatLast(brstBtns[0]) + const chiefBrainstormNoteUpd = await world.chief.chat() + expect(chiefBrainstormNoteUpd.length).toBe(1) + const brainstormButtons = getInlineKeyCallbacks( + chiefBrainstormNoteUpd[0] + ) + expect(brainstormButtons.length).toBe(1) + const calendar = await world.chief.chatLast(brainstormButtons[0]) const dates = getInlineKeyCallbacks(calendar).filter((cb) => cb.includes('date') ) @@ -169,7 +171,7 @@ describe('Brainstorm [integration]', () => { await world.user3.chat('confirm-proposal', true) // check that quest negotiation works on coordination quests as well - // new user recieves proposal + // new user receives proposal const chiefNotif = await world.chief.chatLast() const userNotifButtons = getInlineKeyCallbacks(chiefNotif) const calendar3 = await world.chief.chatLast(userNotifButtons[1]) @@ -243,7 +245,7 @@ describe('Brainstorm [integration]', () => { const spawnManageBtns = getInlineKeyCallbacks( await userToPropose.chatLast() ) - const spwnHowWasItTask = await world.context.stores.taskStore._last() + const spawnHowWasItTask = await world.context.stores.taskStore._last() // Fast forward to feedback of original quest attenders jasmine.clock().mockDate(new Date(ideaQuestHowWasItTask!.time + 1000)) @@ -294,20 +296,20 @@ describe('Brainstorm [integration]', () => { )[0] await userToPropose.chat(arrMins) const gConfirmUpd = await userToPropose.chatLast('GalaDirilia park') - const gathRepyUpds = await userToPropose.chat( + const gathReplyUpds = await userToPropose.chat( getInlineKeyCallbacks(gConfirmUpd)[0] ) - let propserConfirm: string | undefined - if (gathRepyUpds.length > 1) { - propserConfirm = getInlineKeyCallbacks(gathRepyUpds[1])[0] + let proposeConfirm: string | undefined + if (gathReplyUpds.length > 1) { + proposeConfirm = getInlineKeyCallbacks(gathReplyUpds[1])[0] } const gatheringHowWasItTask = await world.context.stores.taskStore._last() // all other users got notified for (let user of world.users) { - if (user === userToPropose && propserConfirm) { - await user.chat(propserConfirm) + if (user === userToPropose && proposeConfirm) { + await user.chat(proposeConfirm) continue } const upds = await user.chat() @@ -321,7 +323,7 @@ describe('Brainstorm [integration]', () => { } // Fast forward to feedback of spawned task - jasmine.clock().mockDate(new Date(spwnHowWasItTask!.time + 1000)) + jasmine.clock().mockDate(new Date(spawnHowWasItTask!.time + 1000)) await world.context.requestTaskQueue() const uToPrpzFeedbackUpd = await userToPropose.chat() const uToPrpzFdbChButtons = getInlineKeyCallbacks(uToPrpzFeedbackUpd[0]) diff --git a/src/specs/tg-bot-scenarios/get-into-tribe.spec.ts b/src/specs/tg-bot-scenarios/get-into-tribe.spec.ts index 7f6c477..ae77c08 100644 --- a/src/specs/tg-bot-scenarios/get-into-tribe.spec.ts +++ b/src/specs/tg-bot-scenarios/get-into-tribe.spec.ts @@ -354,7 +354,7 @@ describe('Get into tribe [integration]', () => { }) ) const tribesListUpdate = await world.newUser.chat() - expect(tribesListUpdate.length).toBe(world.tribes.length) + expect(tribesListUpdate.length).toBe(world.tribes.length + 1) }) it('Decline application', async () => { await world.newUser.chat('/start') diff --git a/src/use-cases/locate-user.ts b/src/use-cases/locate-user.ts index c9cf3ba..83772f0 100644 --- a/src/use-cases/locate-user.ts +++ b/src/use-cases/locate-user.ts @@ -18,8 +18,9 @@ export class LocateUser extends ContextUser { } async locateUserByCityName(req: LocateByCityNameRequest) { const cities = await this.stores.cityStore.find({ name: req.cityName }) - if (!cities[0]) return + if (!cities[0]) return null await this.locateUser(req.userId, cities[0]) + return cities[0].name } private async locateUser(userId: string, city: StoredCity) { const user = await this.getUser(userId) diff --git a/src/use-cases/tribalism.ts b/src/use-cases/tribalism.ts index 3567ba7..049cdcf 100644 --- a/src/use-cases/tribalism.ts +++ b/src/use-cases/tribalism.ts @@ -12,6 +12,7 @@ import { QuestNegotiation } from './negotiate-quest' import { QuestFinale } from './quest-finale' import { SpawnQuest } from './spawn-quest' import { TribeShow } from './tribes-show' +import { Context } from './utils/context' import { ContextUser } from './utils/context-user' import { Voting } from './vote-idea' @@ -33,6 +34,26 @@ export interface Tribalizm { locateUser: Omit } +export function makeTribalizm(context: Context) { + return { + addIdea: new AddIdea(context), + brainstormLifecycle: new BrainstormLifecycle(context), + gatheringAcknowledge: new GatheringAcknowledge(context), + gatheringDeclare: new GatheringDeclare(context), + gatheringFinale: new GatheringFinale(context), + initiation: new Initiation(context), + introductionQuests: new IntroductionQuests(context), + ideasIncarnation: new IdeasIncarnation(context), + tribeApplication: new TribeApplication(context), + tribesShow: new TribeShow(context), + questNegotiation: new QuestNegotiation(context), + questFinale: new QuestFinale(context), + spawnQuest: new SpawnQuest(context), + voting: new Voting(context), + locateUser: new LocateUser(context), + } +} + export function wrapWithErrorHandler( tribalism: Tribalizm, handler: (error: Error) => void