Skip to content

Commit

Permalink
Подготовка к полноценной подписке на уведомления
Browse files Browse the repository at this point in the history
  • Loading branch information
Zamelane committed Nov 27, 2024
1 parent e8539fb commit ee9ad0d
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 29 deletions.
2 changes: 1 addition & 1 deletion apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ DATABASE_PORT=5432
DATABASE_NAME='databaseName'
DATABASE_USERNAME='user'
DATABASE_PASSWORD='password'
BOT_TOKEN=''
BOT_TOKEN='IGNORE'

POSTGRES_USER=postgres
POSTGRES_PW=postgres
Expand Down
12 changes: 9 additions & 3 deletions apps/api/src/helpers/generateToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@ import { API_CODES, ApiError } from '@api'
import { formatDate } from '@utils'
import { suid } from 'rand-token'
import { AuthModel } from '../models/Auth'
import type { TokenInfo } from '../types'

/**
* Генерирует токен и вставляет в базу
* В случае успеха возвращает токен, иначе выбрасывает ошибку
* @param diaryUserId
* @returns {string} token
*/
export const generateToken = async (diaryUserId: bigint): Promise<string> => {
export const generateToken = async (
diaryUserId: bigint
): Promise<TokenInfo> => {
// Генерируем токен
const token = suid(16)

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

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

return token
return {
token,
tokenId: auth.id
}
}
10 changes: 8 additions & 2 deletions apps/api/src/models/Auth/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ import { DataTypes } from 'sequelize'
import { sequelize } from '@db'

import { DiaryUserModel } from '../DiaryUser'
import type { IModelPrototypeNoId } from '../types'
import type { IModelPrototype, IModelPrototypeNoId } from '../types'

export type AuthModelType = {
id: bigint
diaryUserId: bigint
token: string
lastUsedDate: string
}

export type IAuthModel = IModelPrototypeNoId<AuthModelType>
export type IAuthModel = IModelPrototype<AuthModelType, 'id'>

export const AuthModel = sequelize.define<IAuthModel>('auth', {
id: {
type: DataTypes.BIGINT,
autoIncrement: true,
primaryKey: true
},
diaryUserId: {
type: DataTypes.BIGINT,
allowNull: false,
Expand Down
6 changes: 4 additions & 2 deletions apps/api/src/models/DiaryUser/methods.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ResponseLogin } from '@diary-spo/shared'
import type { TokenInfo } from '../../types'
import type { IGroupModel } from '../Group'
import type { ISPOModel } from '../SPO'
import type { IDiaryUserModel } from './model'
Expand All @@ -7,7 +8,7 @@ export const getFormattedDiaryUserData = (
diaryUser: IDiaryUserModel,
spo: ISPOModel,
group: IGroupModel,
token: string
token: TokenInfo
): ResponseLogin => ({
id: diaryUser.id,
groupId: diaryUser.groupId,
Expand All @@ -23,5 +24,6 @@ export const getFormattedDiaryUserData = (
lastName: diaryUser.lastName,
middleName: diaryUser.middleName,
spoId: spo.id,
token
token: token.token,
tokenId: token.tokenId
})
4 changes: 4 additions & 0 deletions apps/api/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ export interface Token {
}

export type WithToken<T> = With<T, Token>

export interface TokenInfo extends Token {
tokenId: bigint
}
80 changes: 62 additions & 18 deletions apps/api/src/worker/notificator/bot/botLogic/registerLogic.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { getCookieFromToken } from '@helpers'
// @ts-ignore
import { b64 } from '@diary-spo/crypto'
import type TelegramBot from 'node-telegram-bot-api'
import { AuthModel } from '../../../../models/Auth'
import { DiaryUserModel } from '../../../../models/DiaryUser'
import { SubscribeModel } from '../../../../models/Subscribe'
import {INTERVAL_RUN} from "../../config";
import { INTERVAL_RUN } from '../../config'

export const registerLogic = (bot: TelegramBot | null) => {
if (!bot) return
Expand All @@ -22,12 +24,56 @@ export const registerLogic = (bot: TelegramBot | null) => {
)
return
}
const token = command[1]

const auth = await getCookieFromToken(token).catch(() => null)
// TODO: потом по-нормальному вынести
let tokenSecure = ''
try {
tokenSecure = atob(command[1])
} catch {
bot.sendMessage(
chatId,
'Вы что-то не то шлёте и всё ломаете. В бан захотели?'
)
return
}
const secureTokenParams = tokenSecure.split(':')

if (secureTokenParams.length !== 2 && !Number(secureTokenParams[0])) {
bot.sendMessage(
chatId,
'У вашего токена неверная структура. В бан захотел(-а)?'
)
return
}

const auth = await AuthModel.findOne({
where: {
id: secureTokenParams[0]
}
})

if (!auth) {
bot.sendMessage(chatId, 'Токен не найден ...')
bot.sendMessage(chatId, 'Переданная авторизация не найдена ...')
return
}

// Проверяем переданную пользователем авторизацию
const tokenObject = {
token: auth.token,
date: new Date().toISOString().substring(0, 10)
}
const secureToken = await b64(JSON.stringify(tokenObject))

if (secureToken !== secureTokenParams[1]) {
bot.sendMessage(
chatId,
`Ваш токен какой-то не такой. Если вы ничего не трогали, то проблема у нас.\nПожалуйста, покажите это сообщение разработчикам.\nDebug info: ${btoa(
JSON.stringify({
tokenSecure,
currServerDate: new Date().toISOString()
})
)}`
)
return
}

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

await SubscribeModel.create({
diaryUserId: auth.localUserId,
diaryUserId: auth.diaryUserId,
tgId: BigInt(chatId),
preActionsIsSuccess: false
})

const user = await DiaryUserModel.findOne({
where: {
id: auth.localUserId
id: auth.diaryUserId
}
})

bot.sendMessage(
chatId,
`<b><i>${user?.firstName} ${user?.lastName}!</i></b> Вы успешно подписались на уведомления.`
+ `\nПрежде чем Вы начнёте получать уведомления, нам нужно извлечь все ваши оценки (это просиходит примерно каждые <b>${INTERVAL_RUN} секунд</b>).`
+ `\nПо окончанию подготовительных процедур, мы уведомим Вас о готовности принимать уведомления.`
+ `\nСпасибо, что выбираете нас!`,
{parse_mode: 'HTML'}
`<b><i>${user?.firstName} ${user?.lastName}!</i></b> Вы успешно подписались на уведомления.\nПрежде чем Вы начнёте получать уведомления, нам нужно извлечь все ваши оценки (это просиходит примерно каждые <b>${INTERVAL_RUN} секунд</b>).\nПо окончанию подготовительных процедур, мы уведомим Вас о готовности принимать уведомления.\nСпасибо, что выбираете нас!`,
{ parse_mode: 'HTML' }
)
break
}
Expand All @@ -79,11 +122,12 @@ export const registerLogic = (bot: TelegramBot | null) => {
)
break
default:
bot.sendMessage(chatId,
`Этой команды нету, но есть такие:`
+ `\n/subscribe <code>[token]</code> — подписаться на уведомления по токену`
+ `\n/unsubscribe — отписаться от уведомлений`,
{parse_mode:"HTML"}
bot.sendMessage(
chatId,
'Этой команды нету, но есть такие:' +
'\n/subscribe <code>[token]</code> — подписаться на уведомления по токену' +
'\n/unsubscribe — отписаться от уведомлений',
{ parse_mode: 'HTML' }
)
break
}
Expand Down
1 change: 1 addition & 0 deletions apps/shared/src/api/self/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ export type ResponseLogin = Person & {
birthday: string
groupName: string
token: string
tokenId: bigint
}
1 change: 1 addition & 0 deletions apps/web/.env.development
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_SERVER_URLS=http://localhost:3003,http://127.0.0.1:3003
VITE_SERVER_URL=http://localhost:3003
# http://localhost:3003,http://127.0.0.1:3003,
VITE_TG_BOT_URL=https://t.me/diary_notifications_bot
VITE_MODE=dev
VITE_NODE_ENV=development
NODE_ENV=development
Expand Down
1 change: 1 addition & 0 deletions apps/web/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
VITE_SERVER_URLS=http://localhost:3003,http://127.0.0.1:3003
VITE_SERVER_URL=http://localhost:3003
VITE_TG_BOT_URL=https://t.me/contact

VITE_MODE=dev
VITE_NODE_ENV=development
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/pages/LoginForm/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { setToken } from '../../../shared/api/client.ts'
export const saveData = (basePath: ResponseLogin) => {
const userId = String(basePath.id)
const token = basePath.token
const tokenId = basePath.tokenId
const name = `${String(basePath.lastName)} ${String(
basePath.firstName
)} ${String(basePath.middleName)}`
Expand All @@ -18,7 +19,8 @@ export const saveData = (basePath: ResponseLogin) => {
name,
org,
city,
group
group,
tokenId
}

setToken(token)
Expand Down
16 changes: 15 additions & 1 deletion apps/web/src/pages/Settings/Actions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import {
Icon28DoorArrowRightOutline,
Icon28HomeArrowDownOutline,
Icon28IncognitoOutline
Icon28IncognitoOutline,
Icon28Notifications
} from '@vkontakte/icons'
import bridge from '@vkontakte/vk-bridge'
import { useRouteNavigator } from '@vkontakte/vk-mini-apps-router'
Expand All @@ -11,6 +12,8 @@ import { useEffect, useRef, useState } from 'react'
import { logOut } from '../../../shared'
import { useSnackbar } from '../../../shared/hooks'

import { getSecureToken } from '../../../shared/api/client.ts'
import { TG_BOT_URL } from '../../../shared/config'
import TechInfo from './TechInfo.tsx'

const Actions = () => {
Expand Down Expand Up @@ -106,6 +109,17 @@ const Actions = () => {
>
Показывать тех. информацию
</CellButton>
<CellButton
before={<Icon28Notifications />}
onClick={async () =>
window.open(
`${TG_BOT_URL}?text=/subscribe ${await getSecureToken()}`,
'_blank'
)
}
>
Подключить уведомления
</CellButton>
<CellButton
before={<Icon28DoorArrowRightOutline />}
onClick={() => routeNavigator.showPopout(logOutPopup)}
Expand Down
20 changes: 19 additions & 1 deletion apps/web/src/shared/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { treaty } from '@elysiajs/eden'

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

import { b64 } from '@diary-spo/crypto'
import { API_URL } from '../config'

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

export const getToken = (): string | null => {
return globalToken
return globalToken ?? localStorage.getItem('token')
}

export const getSecureToken = async () => {
const data = localStorage.getItem('data')

if (!data) return

const tokenId = (await JSON.parse(data)).tokenId
const token = getToken()

const tokenObject = {
token,
date: new Date().toISOString().substring(0, 10)
}
const secureToken = await b64(JSON.stringify(tokenObject))

return btoa(`${tokenId}:${secureToken}`)
}

// @TODO: move to config
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/shared/config/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const MODE = import.meta.env.VITE_MODE
export const ADMIN_PAGE = import.meta.env.VITE_ADMIN_PAGE_URL
export const BETA_VERSION = import.meta.env.VITE_BETA_VERSION
export const API_URL = import.meta.env.VITE_SERVER_URL
export const TG_BOT_URL = import.meta.env.VITE_TG_BOT_URL

export const Mode = {
DEV: 'dev',
Expand Down

0 comments on commit ee9ad0d

Please sign in to comment.