Skip to content

Commit

Permalink
fix timezones
Browse files Browse the repository at this point in the history
  • Loading branch information
Roman Bekkiev committed Dec 16, 2021
1 parent d73c9f3 commit 120ed83
Show file tree
Hide file tree
Showing 15 changed files with 208 additions and 86 deletions.
3 changes: 2 additions & 1 deletion deploy/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
- hosts: bot
vars:
version: '{{ lookup("env", "npm_package_version") }}'
version: '{{ lookup("env", "VERSION") }}'
registry: '{{ lookup("env", "REGISTRY") }}'
db_user: '{{ lookup("env", "DB_USER") }}'
db_pass: '{{ lookup("env", "DB_PASS") }}'
Expand Down Expand Up @@ -93,3 +93,4 @@
docker_image:
name: '{{ registry | string }}/tribalizm-admin:{{ version | string }}'
source: pull
force_source: yes
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
- tribalizm
tribalizm-bot:
# NOTE: for tags to work build must be run as yarn script
image: 'tribalizm-bot:${npm_package_version}'
image: 'tribalizm-bot:${VERSION}'
build:
context: .
dockerfile: ./Dockerfile.bot
Expand All @@ -27,7 +27,7 @@ services:
networks:
- tribalizm
admin:
image: 'tribalizm-admin:${npm_package_version}'
image: 'tribalizm-admin:${VERSION}'
build:
context: .
dockerfile: ./Dockerfile.admin
Expand Down
6 changes: 1 addition & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@
"push:tag": "sh -c 'docker tag \"${0}\" ${REGISTRY}/\"${0}\"'",
"push:push": "sh -c 'docker push \"${REGISTRY}\"/\"${0}\"'",
"push": "export $(egrep -v '^#' .env.build | xargs) && sh -c 'yarn push:login && yarn push:tag \"${0}\" && yarn push:push \"${0}\"'",
"push:all": "yarn push tribalizm-bot:\"${npm_package_version}\" && yarn push tribalizm-admin:\"${npm_package_version}\"",
"push:all": "yarn push tribalizm-bot:\"${VERSION}\" && yarn push tribalizm-admin:\"${VERSION}\"",
"deploy": "export $(egrep -v '^#' .env.build | xargs) && pushd deploy && ansible-playbook deploy.yml && popd",
"preversion": "yarn tsc --noEmit && yarn test:full",
"postversion": "yarn build:images",
"postbuild:images": "yarn push:all",
"postpush:all": "yarn deploy",
"test": "NODE_ENV=test ts-node ./node_modules/.bin/jasmine './src/**/*.spec.ts'",
"test:full": "NODE_ENV=test FULL_TEST='true' ts-node ./node_modules/.bin/jasmine './src/**/*.spec.ts'",
"tdd": "NODE_ENV=test tsnd --rs --respawn ./node_modules/.bin/jasmine './src/**/*.spec.ts'",
Expand Down
4 changes: 3 additions & 1 deletion src/plugins/ui/telegram/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,10 @@ export async function makeBot(config: BotConfig) {
brainstormScreen(tgContext)
coordinationScreen(tgContext)
gatheringScreen(tgContext)

// unhandled text (🗿 Spirits may hear you...)
bot.on('text', async (ctx) => {
config.logger.event('unhandled text', { text: ctx.message.text })
ctx.logEvent('unhandled text', { text: ctx.message.text })
return ctx.reply(i18n(ctx).unhandledText())
})

Expand Down
11 changes: 8 additions & 3 deletions src/plugins/ui/telegram/screens/brainstorm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export function brainstormScreen({
bot.action(startBrainstorm.regex, async (ctx) => {
const texts = i18n(ctx).brainstorm
const { memberId } = startBrainstorm.parse(ctx.match[0])

const onDateSet = (date: Date, ctxIn: TribeCtx) => {
ctxIn.editMessageText(
texts.confirmPrompt({ date }),
Expand All @@ -51,7 +52,7 @@ export function brainstormScreen({
texts.confirm(),
stormConfirm.serialize({
memberId,
time: ctx.user.convertTime(date).getTime(),
time: ctx.user.toServerTime(date).getTime(),
})
),
Markup.button.callback(
Expand Down Expand Up @@ -145,7 +146,9 @@ export function brainstormScreen({
const texts = i18n(user).brainstorm
await bot.telegram.sendMessage(
user.chatId,
texts.brainstormDeclared({ date: new Date(payload.time) })
texts.brainstormDeclared({
date: user.toUserTime(new Date(payload.time)),
})
)
}
)
Expand All @@ -158,7 +161,9 @@ export function brainstormScreen({
const texts = i18n(user).brainstorm
await bot.telegram.sendMessage(
user.chatId,
texts.brainstormNotice({ date: new Date(payload.time) })
texts.brainstormNotice({
date: user.toUserTime(new Date(payload.time)),
})
)
}
)
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui/telegram/screens/coordination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export function coordinationScreen({ bot, bus, tgUsers }: TgContext) {
userId: ctx.user.userId,
parentQuestId: state.parentQuestId,
place: state.place,
time: ctx.user.convertTime(state.date).getTime(),
time: ctx.user.toServerTime(state.date).getTime(),
type: state.gatheringType,
})
})
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui/telegram/screens/gathering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function gatheringScreen({ bot, bus, tgUsers }: TgContext) {
),
])
const proposal = texts.proposal({
date: new Date(payload.time),
date: user.toUserTime(new Date(payload.time)),
place: payload.place,
})
const text = texts.declared({
Expand Down
6 changes: 3 additions & 3 deletions src/plugins/ui/telegram/screens/quest-negotiation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function questNegotiationScreen({ bot, bus, tgUsers }: TgContext) {
}
await ctx.tribalizm.questNegotiation.proposeChange({
place: state.place,
time: ctx.user.convertTime(state.date).getTime(),
time: ctx.user.toServerTime(state.date).getTime(),
userId: ctx.user.userId,
questId: state.questId,
})
Expand Down Expand Up @@ -175,7 +175,7 @@ export function questNegotiationScreen({ bot, bus, tgUsers }: TgContext) {
const qnTexts = i18n(user).questNegotiation

const proposal = qnTexts.proposal({
date: new Date(payload.time),
date: user.toUserTime(new Date(payload.time)),
place: payload.place,
})
let text = ''
Expand Down Expand Up @@ -245,7 +245,7 @@ export function questNegotiationScreen({ bot, bus, tgUsers }: TgContext) {
)
const qnTexts = i18n(user).questNegotiation
const proposal = qnTexts.proposal({
date: new Date(payload.time),
date: user.toUserTime(new Date(payload.time)),
place: payload.place,
})
let text: string
Expand Down
2 changes: 1 addition & 1 deletion src/plugins/ui/telegram/screens/tribes-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ export function tribesListScreen({ bot }: TgContext) {
await ctx.replyWithHTML(
`<b>${tribe.name}</b>\n${
tribe.description
}\n\t\t\t<i>${texts.count()} ${tribe.membersCount}</i>`,
}\n<i>${texts.count()} ${tribe.membersCount}</i>`,
keyboard
)
}
Expand Down
30 changes: 22 additions & 8 deletions src/plugins/ui/telegram/users-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,29 @@ export class TelegramUser implements SavedTelegramUser {
}

/**
* Converts from server's date to user's one (if timeZone is set)
* Converts user defined date to server time (if timeZone is set)
* @param date date that is made from user-defined **string**
*/
convertTime(date: Date) {
if (!this.timeZone) return date
return new Date(
Date.parse(
date.toLocaleString('en-US', { timeZone: this.timeZone })
)
)
toServerTime(date: Date) {
return new Date(date.getTime() - this.getTimeDiff(date))
}

/**
* Converts server-created date to user's timezone (if it is set)
* @param date Date that is made from UTC time stamp
* @returns Date that accounts for user time zone and server's offset
*/
toUserTime(date: Date) {
return new Date(date.getTime() + this.getTimeDiff(date))
}

private getTimeDiff(date: Date) {
if (!this.timeZone) return 0
const userTimeString = date.toLocaleString('en-US', {
timeZone: this.timeZone,
})

return Date.parse(userTimeString) - date.getTime()
}

private async save() {
Expand Down
111 changes: 93 additions & 18 deletions src/specs/tg-bot-scenarios/astral.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,33 @@ import { createTelegramContext, getInlineKeyCallbacks } from './bot-utils'
describe('Astral tribes [scenario]:', () => {
let world: Awaited<ReturnType<typeof setup>>
beforeEach(async () => {
jasmine.clock().install()
jasmine.clock().mockDate(new Date('2021-11-16'))
world = await setup()
// process.env.chatDebug = 'true'
})
afterEach(async () => {
jasmine.clock().uninstall()
await world.tearDown()
process.env.chatDebug = ''
purgeGlobalCallbackRegistry()
})
it('Create tribe', async () => {
const update = await world.newUser.chatLast('/start')
const update = await world.user1.chatLast('/start')
const user = await world.context.stores.userStore._last()
const listOrRules = getInlineKeyCallbacks(update)
await world.newUser.chat(listOrRules[0])
const tribesListUpdate = await world.newUser.chat(world.city.name)
await world.user1.chat(listOrRules[0])
const tribesListUpdate = await world.user1.chat(world.baku.name)
expect(tribesListUpdate.length).toBe(2)
const checkAstral = getInlineKeyCallbacks(tribesListUpdate[1])
expect(checkAstral.length).toBe(1)
const astralUpds = await world.newUser.chat(checkAstral[0])
const astralUpds = await world.user1.chat(checkAstral[0])
expect(astralUpds.length).toBe(1)
const create = getInlineKeyCallbacks(astralUpds[0])[0]
expect(create).toEqual(jasmine.any(String))
await world.newUser.chat(create)
await world.newUser.chat('Bar Tribe')
await world.newUser.chat('We really Bar Bar!')
await world.user1.chat(create)
await world.user1.chat('Bar Tribe')
await world.user1.chat('We really Bar Bar!')
const tribes = await world.context.stores.tribeStore.find({})
expect(tribes.length).toBe(1)
expect(tribes[0]).toEqual(
Expand All @@ -49,20 +52,20 @@ describe('Astral tribes [scenario]:', () => {
})
it('List tribes', async () => {
const tribes = await world.create5tribes()
const update = await world.newUser.chatLast('/start')
const update = await world.user1.chatLast('/start')
const listOrRules = getInlineKeyCallbacks(update)
await world.newUser.chat(listOrRules[0])
const tribesListUpdate = await world.newUser.chat(world.city.name)
await world.user1.chat(listOrRules[0])
const tribesListUpdate = await world.user1.chat(world.baku.name)
const checkAstral = getInlineKeyCallbacks(tribesListUpdate[1])
expect(checkAstral.length).toBe(1)
const astralUpds = await world.newUser.chat(checkAstral[0])
const astralUpds = await world.user1.chat(checkAstral[0])
expect(astralUpds.length).toBe(5)
for (let i of [1, 2, 3]) {
expect(astralUpds[i].message.text).toMatch(tribes[i - 1].name)
}
const loadMore = getInlineKeyCallbacks(astralUpds[4])
expect(loadMore.length).toBe(1)
const nextTribesListUpdate = await world.newUser.chat(loadMore[0])
const nextTribesListUpdate = await world.user1.chat(loadMore[0])
expect(nextTribesListUpdate.length).toBe(3)
for (let i of [0, 1]) {
expect(nextTribesListUpdate[i].message.text).toMatch(
Expand All @@ -73,6 +76,68 @@ describe('Astral tribes [scenario]:', () => {
const create = getInlineKeyCallbacks(nextTribesListUpdate[2])[0]
expect(create).toMatch('create')
})

it('Sets correct time for initiation for users in different timezones', async () => {
// first user creates tribe
const update = await world.user1.chatLast('/start')
const listOrRules = getInlineKeyCallbacks(update)
await world.user1.chat(listOrRules[0])
const tribesListUpdate = await world.user1.chat(world.baku.name)
const checkAstral = getInlineKeyCallbacks(tribesListUpdate[1])
const astralUpds = await world.user1.chat(checkAstral[0])
const create = getInlineKeyCallbacks(astralUpds[0])[0]
await world.user1.chat(create)
await world.user1.chat('Bar Tribe')
await world.user1.chat('We really Bar Bar!')

// second user applies to that tribe
const listOrRules2 = getInlineKeyCallbacks(
await world.user2.chatLast('/start')
)
await world.user2.chat(listOrRules2[0])
const checkAstral2 = getInlineKeyCallbacks(
(await world.user2.chat(world.seoul.name))[1]
)
const astralUpds2 = await world.user2.chat(checkAstral2[0])
const applyKeyboard = getInlineKeyCallbacks(astralUpds2[1])
await world.user2.forceCallback(applyKeyboard[0])
await world.user2.chat('Because I BAR')

const meetOrDecline = getInlineKeyCallbacks(
await world.user1.chatLast()
)
const calendar = getInlineKeyCallbacks(
await world.user1.chatLast(meetOrDecline[0])
)

const dates = calendar.filter((cb) => cb.includes('date'))

const hour = getInlineKeyCallbacks(
await world.user1.chatLast(dates[1], true)
)[4]
const minutes = getInlineKeyCallbacks(
await world.user1.chatLast(hour, true)
)[2]
await world.user1.chat(minutes, true)
const proposalConfirmPrompt = await world.user1.chatLast('Bar-Bar bar')
const proposalPromptButtons = getInlineKeyCallbacks(
proposalConfirmPrompt
)
// next day
expect(proposalConfirmPrompt.message.text).toMatch('17')
// time
expect(proposalConfirmPrompt.message.text).toMatch('12:30')

await world.user1.chat(proposalPromptButtons[0], true)

const meetingNotif = await world.user2.chatLast()

// same day
expect(meetingNotif.message.text).toMatch('17')
// Seoul is UTC+9, Baku is UTC+4 ⇒ 12:30 - (4 + 9)
expect(meetingNotif.message.text).toMatch('5:30 PM')
})

it('List list tribes in city and then check Astral', async () => {
pending('Needs implementation')
})
Expand All @@ -82,10 +147,17 @@ async function setup() {
const context = await createContext()
const { server, makeClient, bot } = await createTelegramContext(context)

const city = await context.stores.cityStore.save(
const baku = await context.stores.cityStore.save(
new City({
name: 'Baku',
timeZone: 'Asia/Baku',
})
)

const seoul = await context.stores.cityStore.save(
new City({
name: 'Novosibirsk',
timeZone: 'Asia/Novosibirsk',
name: 'Seoul',
timeZone: 'Asia/Seoul',
})
)
async function create5tribes() {
Expand All @@ -111,12 +183,15 @@ async function setup() {
return tribes
}

const newUser = makeClient('Newbie', 'Applicant')
const user1 = makeClient('User-1', 'User-1')
const user2 = makeClient('User-2', 'User-2')
return {
city,
baku,
seoul,
context,
create5tribes,
newUser,
user1,
user2,
server,
tearDown: async () => {
bot.stop()
Expand Down
Loading

0 comments on commit 120ed83

Please sign in to comment.