Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# Environment (development, production)
NODE_ENV=development
TELEGRAM_SECRET=00000000.xxxxxxxxxxxxxxxxxxx

# Host configuration
HOST=0.0.0.0
Expand All @@ -20,8 +21,8 @@ DATABASE_CLIENT=sqlite

# For production, you should use postgresql database (and configure it below)
# DATABASE_CLIENT=postgres
# DATABASE_HOST=localhost
# DATABASE_PORT=5432
# DATABASE_HOST=localhost
# DATABASE_PORT=5432
# DATABASE_NAME=cow-cms
# DATABASE_USERNAME=cow
# DATABASE_PASSWORD=moooo
Expand All @@ -36,10 +37,10 @@ DATABASE_CLIENT=sqlite
APP_KEYS="toBeModified1,toBeModified2" # The secret keys used to sign session cookies, follows the format: key1, key2, ...

# Salt used to generate API tokens
API_TOKEN_SALT=tobemodified
API_TOKEN_SALT=tobemodified

# JWT secrets
ADMIN_JWT_SECRET=tobemodified
ADMIN_JWT_SECRET=tobemodified
JWT_SECRET=tobemodified

# The salt used to generate a transfer token. Transfer tokens are used to migrate data between Strapi instances.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,15 @@
"push": {
"type": "boolean"
},
"due_date": {
"type": "datetime"
},
"thumbnail": {
"type": "string"
"allowedTypes": [
"images"
],
"type": "media",
"multiple": false
}
}
}
3 changes: 3 additions & 0 deletions src/api/notification/services/notification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default factories.createCoreService(MODULE_ID, ({ strapi }) => {

return notifications.map(notification => ({
id: notification.id,
account: notification.account,
title: notification.notification_template.title,
description: templateNotification(notification.notification_template.description, notification.data),
url: notification.notification_template.url,
Expand All @@ -40,6 +41,8 @@ export default factories.createCoreService(MODULE_ID, ({ strapi }) => {
function templateNotification(description: string, data: {[key: string]: string}): string {
let result = description

if (!data) return result

Object.keys(data).forEach(key => {
result = result.replace(`{{ ${key} }}`, data[key])
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"info": {
"singularName": "telegram-subscription",
"pluralName": "telegram-subscriptions",
"displayName": "Telegram subscription"
"displayName": "Telegram subscription",
"description": ""
},
"options": {
"draftAndPublish": true
Expand All @@ -16,17 +17,23 @@
"required": true,
"unique": false
},
"login": {
"type": "string",
"required": true
"auth_date": {
"type": "biginteger"
},
"first_name": {
"type": "string"
},
"hash": {
"type": "string"
},
"chat_id": {
"type": "biginteger"
},
"verified": {
"type": "boolean",
"default": false,
"required": true
"photo_url": {
"type": "string"
},
"verificationCode": {
"type": "integer"
"username": {
"type": "string"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,41 @@
*/

import { factories } from '@strapi/strapi'
import { errors } from '@strapi/utils'
import { TelegramData } from '../types'

export default factories.createCoreController('api::telegram-subscription.telegram-subscription');
const MODULE_ID = 'api::telegram-subscription.telegram-subscription'

export default factories.createCoreController(MODULE_ID, ({strapi}) => {
return {
async addSubscription(context) {
const {account, data} : { account: string, data: TelegramData } = context.request.body

const service = strapi.service(MODULE_ID)

const existing = await strapi.entityService.findMany(MODULE_ID, { filters: { account, chat_id: data.id } })

if (existing.length > 0) return true

const result = await service.verifyTgAuthentication(data)

if (!result) {
throw new errors.ValidationError('Invalid telegram authentication data')
}

await service.addSubscription(account, data)

return true
},
async getSubscriptions(context) {
const account = context.params.account

return strapi.service(MODULE_ID).getAccountSubscriptions(account)
},
async sendNotifications(context) {
const account = context.params.account

return strapi.service(MODULE_ID).sendNotifications(account)
}
}
});
47 changes: 46 additions & 1 deletion src/api/telegram-subscription/routes/telegram-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,49 @@

import { factories } from '@strapi/strapi';

export default factories.createCoreRouter('api::telegram-subscription.telegram-subscription');
const defaultRouter = factories.createCoreRouter('api::telegram-subscription.telegram-subscription');

const customRouter = (innerRouter, extraRoutes = []) => {
let routes;
return {
get prefix() {
return innerRouter.prefix;
},
get routes() {
if (!routes) routes = innerRouter.routes.concat(extraRoutes);
return routes;
},
};
};

const myExtraRoutes = [
{
method: 'GET',
path: '/tg-subscriptions/:account',
handler: 'telegram-subscription.getSubscriptions',
config: {
policies: [],
middlewares: [],
},
},
{
method: 'POST',
path: '/add-tg-subscription',
handler: 'telegram-subscription.addSubscription',
config: {
policies: [],
middlewares: [],
},
},
{
method: 'GET',
path: '/send-tg-notifications/:account',
handler: 'telegram-subscription.sendNotifications',
config: {
policies: [],
middlewares: [],
},
},
];

export default customRouter(defaultRouter, myExtraRoutes);
81 changes: 79 additions & 2 deletions src/api/telegram-subscription/services/telegram-subscription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,83 @@
* telegram-subscription service
*/

import { factories } from '@strapi/strapi';
import { factories } from '@strapi/strapi'
import { env } from '@strapi/utils'
import fetch from 'node-fetch'
import crypto from 'crypto'
import { TelegramData } from '../types'

export default factories.createCoreService('api::telegram-subscription.telegram-subscription');
const MODULE_ID = 'api::telegram-subscription.telegram-subscription'

const SEND_MESSAGE_URL = `https://api.telegram.org/bot${env('TELEGRAM_SECRET')}/sendMessage`

export default factories.createCoreService(MODULE_ID, ({strapi}) => {
return {
async verifyTgAuthentication(data: TelegramData) {
const dataString = Object.keys(data).reduce((acc, key) => {
if (key === 'hash') return acc

acc.push(`${key}=${data[key]}`)
return acc
}, []).sort().join('\n')

const secretHash = crypto.createHash('sha256').update(env('TELEGRAM_SECRET')).digest('base64')
const result = crypto.createHmac('sha256', new Buffer(secretHash, 'base64')).update(dataString).digest('hex')

return result === data.hash
},
async addSubscription(account: string, data: TelegramData) {
return strapi.entityService.create(
MODULE_ID,
{
data: {
account,
auth_date: data.auth_date,
first_name: data.first_name,
hash: data.hash,
chat_id: data.id,
photo_url: data.photo_url,
username: data.username,
}
})
},
async getAccountSubscriptions(account: string) {
return strapi.entityService.findMany(
MODULE_ID,
{
filters: {
account
},
fields: ['id', 'account', 'chat_id']
}
)
},
// TODO: temporary implementation
async sendNotifications(account: string): Promise<number> {
const notifications = await strapi.service('api::notification.notification').getNotificationList(account, true)

if (notifications.length === 0) return 0

const subscriptions = await this.getAccountSubscriptions(account)

const requests = subscriptions.map(subscription => {
return notifications.map(notification => {
return fetch(SEND_MESSAGE_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
chat_id: subscription.chat_id,
text: notification.description
})
})
})
}).flat()

await Promise.all(requests)

return requests.length
}
}
});
8 changes: 8 additions & 0 deletions src/api/telegram-subscription/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface TelegramData {
auth_date: number
first_name: string
hash: string
id: number
photo_url: string
username: string
}
Loading