Skip to content

Commit ee9ad0d

Browse files
committed
Подготовка к полноценной подписке на уведомления
1 parent e8539fb commit ee9ad0d

File tree

13 files changed

+129
-29
lines changed

13 files changed

+129
-29
lines changed

apps/api/.env.example

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ DATABASE_PORT=5432
1212
DATABASE_NAME='databaseName'
1313
DATABASE_USERNAME='user'
1414
DATABASE_PASSWORD='password'
15-
BOT_TOKEN=''
15+
BOT_TOKEN='IGNORE'
1616

1717
POSTGRES_USER=postgres
1818
POSTGRES_PW=postgres

apps/api/src/helpers/generateToken.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,33 @@ import { API_CODES, ApiError } from '@api'
22
import { formatDate } from '@utils'
33
import { suid } from 'rand-token'
44
import { AuthModel } from '../models/Auth'
5+
import type { TokenInfo } from '../types'
56

67
/**
78
* Генерирует токен и вставляет в базу
89
* В случае успеха возвращает токен, иначе выбрасывает ошибку
910
* @param diaryUserId
1011
* @returns {string} token
1112
*/
12-
export const generateToken = async (diaryUserId: bigint): Promise<string> => {
13+
export const generateToken = async (
14+
diaryUserId: bigint
15+
): Promise<TokenInfo> => {
1316
// Генерируем токен
1417
const token = suid(16)
1518

1619
const formattedDate = formatDate(new Date().toISOString())
1720

1821
// TODO: сделать метод рядом с моделью для создания и использовать тут
19-
await AuthModel.create({
22+
const auth = await AuthModel.create({
2023
diaryUserId,
2124
token,
2225
lastUsedDate: formattedDate
2326
}).catch(() => {
2427
throw new ApiError('Error insert token!', API_CODES.INTERNAL_SERVER_ERROR)
2528
})
2629

27-
return token
30+
return {
31+
token,
32+
tokenId: auth.id
33+
}
2834
}

apps/api/src/models/Auth/model.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,23 @@ import { DataTypes } from 'sequelize'
33
import { sequelize } from '@db'
44

55
import { DiaryUserModel } from '../DiaryUser'
6-
import type { IModelPrototypeNoId } from '../types'
6+
import type { IModelPrototype, IModelPrototypeNoId } from '../types'
77

88
export type AuthModelType = {
9+
id: bigint
910
diaryUserId: bigint
1011
token: string
1112
lastUsedDate: string
1213
}
1314

14-
export type IAuthModel = IModelPrototypeNoId<AuthModelType>
15+
export type IAuthModel = IModelPrototype<AuthModelType, 'id'>
1516

1617
export const AuthModel = sequelize.define<IAuthModel>('auth', {
18+
id: {
19+
type: DataTypes.BIGINT,
20+
autoIncrement: true,
21+
primaryKey: true
22+
},
1723
diaryUserId: {
1824
type: DataTypes.BIGINT,
1925
allowNull: false,

apps/api/src/models/DiaryUser/methods.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ResponseLogin } from '@diary-spo/shared'
2+
import type { TokenInfo } from '../../types'
23
import type { IGroupModel } from '../Group'
34
import type { ISPOModel } from '../SPO'
45
import type { IDiaryUserModel } from './model'
@@ -7,7 +8,7 @@ export const getFormattedDiaryUserData = (
78
diaryUser: IDiaryUserModel,
89
spo: ISPOModel,
910
group: IGroupModel,
10-
token: string
11+
token: TokenInfo
1112
): ResponseLogin => ({
1213
id: diaryUser.id,
1314
groupId: diaryUser.groupId,
@@ -23,5 +24,6 @@ export const getFormattedDiaryUserData = (
2324
lastName: diaryUser.lastName,
2425
middleName: diaryUser.middleName,
2526
spoId: spo.id,
26-
token
27+
token: token.token,
28+
tokenId: token.tokenId
2729
})

apps/api/src/types/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ export interface Token {
1313
}
1414

1515
export type WithToken<T> = With<T, Token>
16+
17+
export interface TokenInfo extends Token {
18+
tokenId: bigint
19+
}

apps/api/src/worker/notificator/bot/botLogic/registerLogic.ts

+62-18
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { getCookieFromToken } from '@helpers'
1+
// @ts-ignore
2+
import { b64 } from '@diary-spo/crypto'
23
import type TelegramBot from 'node-telegram-bot-api'
4+
import { AuthModel } from '../../../../models/Auth'
35
import { DiaryUserModel } from '../../../../models/DiaryUser'
46
import { SubscribeModel } from '../../../../models/Subscribe'
5-
import {INTERVAL_RUN} from "../../config";
7+
import { INTERVAL_RUN } from '../../config'
68

79
export const registerLogic = (bot: TelegramBot | null) => {
810
if (!bot) return
@@ -22,12 +24,56 @@ export const registerLogic = (bot: TelegramBot | null) => {
2224
)
2325
return
2426
}
25-
const token = command[1]
2627

27-
const auth = await getCookieFromToken(token).catch(() => null)
28+
// TODO: потом по-нормальному вынести
29+
let tokenSecure = ''
30+
try {
31+
tokenSecure = atob(command[1])
32+
} catch {
33+
bot.sendMessage(
34+
chatId,
35+
'Вы что-то не то шлёте и всё ломаете. В бан захотели?'
36+
)
37+
return
38+
}
39+
const secureTokenParams = tokenSecure.split(':')
40+
41+
if (secureTokenParams.length !== 2 && !Number(secureTokenParams[0])) {
42+
bot.sendMessage(
43+
chatId,
44+
'У вашего токена неверная структура. В бан захотел(-а)?'
45+
)
46+
return
47+
}
48+
49+
const auth = await AuthModel.findOne({
50+
where: {
51+
id: secureTokenParams[0]
52+
}
53+
})
2854

2955
if (!auth) {
30-
bot.sendMessage(chatId, 'Токен не найден ...')
56+
bot.sendMessage(chatId, 'Переданная авторизация не найдена ...')
57+
return
58+
}
59+
60+
// Проверяем переданную пользователем авторизацию
61+
const tokenObject = {
62+
token: auth.token,
63+
date: new Date().toISOString().substring(0, 10)
64+
}
65+
const secureToken = await b64(JSON.stringify(tokenObject))
66+
67+
if (secureToken !== secureTokenParams[1]) {
68+
bot.sendMessage(
69+
chatId,
70+
`Ваш токен какой-то не такой. Если вы ничего не трогали, то проблема у нас.\nПожалуйста, покажите это сообщение разработчикам.\nDebug info: ${btoa(
71+
JSON.stringify({
72+
tokenSecure,
73+
currServerDate: new Date().toISOString()
74+
})
75+
)}`
76+
)
3177
return
3278
}
3379

@@ -40,30 +86,27 @@ export const registerLogic = (bot: TelegramBot | null) => {
4086
if (subscribes.length >= 1) {
4187
bot.sendMessage(
4288
chatId,
43-
'Вы уже подписаны для других аккаунтов. Сначала отпишитесь от остальных (/unsubscribe)'
89+
'Вы уже подписаны на уведомления. Сначала отпишитесь (/unsubscribe)'
4490
)
4591
return
4692
}
4793

4894
await SubscribeModel.create({
49-
diaryUserId: auth.localUserId,
95+
diaryUserId: auth.diaryUserId,
5096
tgId: BigInt(chatId),
5197
preActionsIsSuccess: false
5298
})
5399

54100
const user = await DiaryUserModel.findOne({
55101
where: {
56-
id: auth.localUserId
102+
id: auth.diaryUserId
57103
}
58104
})
59105

60106
bot.sendMessage(
61107
chatId,
62-
`<b><i>${user?.firstName} ${user?.lastName}!</i></b> Вы успешно подписались на уведомления.`
63-
+ `\nПрежде чем Вы начнёте получать уведомления, нам нужно извлечь все ваши оценки (это просиходит примерно каждые <b>${INTERVAL_RUN} секунд</b>).`
64-
+ `\nПо окончанию подготовительных процедур, мы уведомим Вас о готовности принимать уведомления.`
65-
+ `\nСпасибо, что выбираете нас!`,
66-
{parse_mode: 'HTML'}
108+
`<b><i>${user?.firstName} ${user?.lastName}!</i></b> Вы успешно подписались на уведомления.\nПрежде чем Вы начнёте получать уведомления, нам нужно извлечь все ваши оценки (это просиходит примерно каждые <b>${INTERVAL_RUN} секунд</b>).\nПо окончанию подготовительных процедур, мы уведомим Вас о готовности принимать уведомления.\nСпасибо, что выбираете нас!`,
109+
{ parse_mode: 'HTML' }
67110
)
68111
break
69112
}
@@ -79,11 +122,12 @@ export const registerLogic = (bot: TelegramBot | null) => {
79122
)
80123
break
81124
default:
82-
bot.sendMessage(chatId,
83-
`Этой команды нету, но есть такие:`
84-
+ `\n/subscribe <code>[token]</code> — подписаться на уведомления по токену`
85-
+ `\n/unsubscribe — отписаться от уведомлений`,
86-
{parse_mode:"HTML"}
125+
bot.sendMessage(
126+
chatId,
127+
'Этой команды нету, но есть такие:' +
128+
'\n/subscribe <code>[token]</code> — подписаться на уведомления по токену' +
129+
'\n/unsubscribe — отписаться от уведомлений',
130+
{ parse_mode: 'HTML' }
87131
)
88132
break
89133
}

apps/shared/src/api/self/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ export type ResponseLogin = Person & {
2828
birthday: string
2929
groupName: string
3030
token: string
31+
tokenId: bigint
3132
}

apps/web/.env.development

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
VITE_SERVER_URLS=http://localhost:3003,http://127.0.0.1:3003
22
VITE_SERVER_URL=http://localhost:3003
33
# http://localhost:3003,http://127.0.0.1:3003,
4+
VITE_TG_BOT_URL=https://t.me/diary_notifications_bot
45
VITE_MODE=dev
56
VITE_NODE_ENV=development
67
NODE_ENV=development

apps/web/.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
VITE_SERVER_URLS=http://localhost:3003,http://127.0.0.1:3003
22
VITE_SERVER_URL=http://localhost:3003
3+
VITE_TG_BOT_URL=https://t.me/contact
34

45
VITE_MODE=dev
56
VITE_NODE_ENV=development

apps/web/src/pages/LoginForm/helpers/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { setToken } from '../../../shared/api/client.ts'
44
export const saveData = (basePath: ResponseLogin) => {
55
const userId = String(basePath.id)
66
const token = basePath.token
7+
const tokenId = basePath.tokenId
78
const name = `${String(basePath.lastName)} ${String(
89
basePath.firstName
910
)} ${String(basePath.middleName)}`
@@ -18,7 +19,8 @@ export const saveData = (basePath: ResponseLogin) => {
1819
name,
1920
org,
2021
city,
21-
group
22+
group,
23+
tokenId
2224
}
2325

2426
setToken(token)

apps/web/src/pages/Settings/Actions/index.tsx

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {
22
Icon28DoorArrowRightOutline,
33
Icon28HomeArrowDownOutline,
4-
Icon28IncognitoOutline
4+
Icon28IncognitoOutline,
5+
Icon28Notifications
56
} from '@vkontakte/icons'
67
import bridge from '@vkontakte/vk-bridge'
78
import { useRouteNavigator } from '@vkontakte/vk-mini-apps-router'
@@ -11,6 +12,8 @@ import { useEffect, useRef, useState } from 'react'
1112
import { logOut } from '../../../shared'
1213
import { useSnackbar } from '../../../shared/hooks'
1314

15+
import { getSecureToken } from '../../../shared/api/client.ts'
16+
import { TG_BOT_URL } from '../../../shared/config'
1417
import TechInfo from './TechInfo.tsx'
1518

1619
const Actions = () => {
@@ -106,6 +109,17 @@ const Actions = () => {
106109
>
107110
Показывать тех. информацию
108111
</CellButton>
112+
<CellButton
113+
before={<Icon28Notifications />}
114+
onClick={async () =>
115+
window.open(
116+
`${TG_BOT_URL}?text=/subscribe ${await getSecureToken()}`,
117+
'_blank'
118+
)
119+
}
120+
>
121+
Подключить уведомления
122+
</CellButton>
109123
<CellButton
110124
before={<Icon28DoorArrowRightOutline />}
111125
onClick={() => routeNavigator.showPopout(logOutPopup)}

apps/web/src/shared/api/client.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { treaty } from '@elysiajs/eden'
22

33
import type { App } from '@diary-spo/api/src/main.ts'
44

5+
import { b64 } from '@diary-spo/crypto'
56
import { API_URL } from '../config'
67

78
/**
@@ -13,7 +14,24 @@ export const setToken = (token: string) => {
1314
}
1415

1516
export const getToken = (): string | null => {
16-
return globalToken
17+
return globalToken ?? localStorage.getItem('token')
18+
}
19+
20+
export const getSecureToken = async () => {
21+
const data = localStorage.getItem('data')
22+
23+
if (!data) return
24+
25+
const tokenId = (await JSON.parse(data)).tokenId
26+
const token = getToken()
27+
28+
const tokenObject = {
29+
token,
30+
date: new Date().toISOString().substring(0, 10)
31+
}
32+
const secureToken = await b64(JSON.stringify(tokenObject))
33+
34+
return btoa(`${tokenId}:${secureToken}`)
1735
}
1836

1937
// @TODO: move to config

apps/web/src/shared/config/env.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const MODE = import.meta.env.VITE_MODE
44
export const ADMIN_PAGE = import.meta.env.VITE_ADMIN_PAGE_URL
55
export const BETA_VERSION = import.meta.env.VITE_BETA_VERSION
66
export const API_URL = import.meta.env.VITE_SERVER_URL
7+
export const TG_BOT_URL = import.meta.env.VITE_TG_BOT_URL
78

89
export const Mode = {
910
DEV: 'dev',

0 commit comments

Comments
 (0)