Compare commits
127 Commits
Author | SHA1 | Date | |
---|---|---|---|
2c53b07c99 | |||
1af59253bf | |||
bfe21bf0c5 | |||
68499826c9 | |||
2635dcfffb | |||
94d7926fd7 | |||
aaa19c0cea | |||
fc0edd3f5e | |||
2d1ebf73e1 | |||
a5eedacf90 | |||
3a2b168c0e | |||
c0c2cb3706 | |||
bc6699bba0 | |||
473c85dddc | |||
24cfc562f7 | |||
25c8c76ed7 | |||
26d8f69c29 | |||
887f064fe7 | |||
b1bfa1cb02 | |||
af1c778c08 | |||
c685a60116 | |||
0958c32f97 | |||
9d64d6d692 | |||
66b99938cc | |||
7ee8a4bdf6 | |||
06070952a2 | |||
bc1c5c3193 | |||
d03621702b | |||
17ca27163e | |||
1ef8a3af77 | |||
22d5d31e54 | |||
243e7968a7 | |||
c3d637dc05 | |||
8c17881fa2 | |||
d8ef717028 | |||
580d5b975a | |||
62cc64b1df | |||
d52e972df9 | |||
a1a88d3eed | |||
47b043136f | |||
562212e6ca | |||
0331e4406e | |||
1423c78af8 | |||
18fab9828e | |||
cf1518cfef | |||
934c3faae1 | |||
e68e652538 | |||
ba8d50c145 | |||
52b4e251f4 | |||
874f4777b5 | |||
af52470196 | |||
82f1d3e96d | |||
efd2997b8f | |||
fe093e124e | |||
8d20ba8bd4 | |||
35c55ca337 | |||
715fbc622e | |||
aaf330f1b3 | |||
94bdd8f0cd | |||
9f627ac60d | |||
3cf0c377ac | |||
4bbeaf905e | |||
c8a78aed34 | |||
781d83935d | |||
5a0f499338 | |||
8f9f437aed | |||
6fe4c25a74 | |||
5ef7156044 | |||
276aa67134 | |||
c4793bcfa9 | |||
e4862eeef5 | |||
c174357896 | |||
6fee9a4943 | |||
f8650d27e9 | |||
4add7b4fee | |||
4b8d32bf88 | |||
e30966bfb1 | |||
acfce9ce1c | |||
bbfc351f48 | |||
b23687df30 | |||
27c13f75e2 | |||
d9d01120c5 | |||
ba74f8b6db | |||
68b8ea9327 | |||
03e7bc9419 | |||
489768bcdf | |||
0db50cd19e | |||
3622e5fbce | |||
7ff7b315b6 | |||
c5353870f6 | |||
9325e0adee | |||
bdc2d1dfce | |||
60be28c2be | |||
0264a58cb6 | |||
11d46ada2a | |||
e71a8b8ccf | |||
5512bfb0c4 | |||
d1e3e7a485 | |||
beaaf8ef08 | |||
22776dd9e7 | |||
65f43fd26c | |||
a180888b2f | |||
af9aca41b4 | |||
5b079a5872 | |||
20f24be349 | |||
36e6a0f986 | |||
6f0d84aa87 | |||
a4d4cee6cd | |||
f7be57f9dd | |||
57491651d2 | |||
5ee7a22486 | |||
cfd1bb66bd | |||
6e368518c5 | |||
3e4851555d | |||
5f73c5ab5a | |||
e730068834 | |||
7a43213aa4 | |||
d9db1b406a | |||
2d295bb384 | |||
20b6d055bb | |||
545366564f | |||
aee5d92d8d | |||
756272d064 | |||
dfa0f2daff | |||
124ee61e1d | |||
55f710cbbb | |||
9fe660aa71 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.idea/
|
19
README.md
19
README.md
@ -1,6 +1,25 @@
|
||||
# telegram
|
||||
|
||||
`go2v` — утилита для транслирования кода на Go в V. В текущий момент доступна только старая версия утилиты.
|
||||
|
||||
Для установки `go2v`, нужно клонировать репозиторий в данный момент с ветки `old_implementation` и запустить команду `v run .` для компиляции файлов:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/vlang/go2v.git -b old_implementation
|
||||
cd go2v
|
||||
v run .
|
||||
```
|
||||
|
||||
После установки утилиты, можно использовать go2v для трансляции Go-файлов в V:
|
||||
|
||||
```sh
|
||||
v run . path/to/file.go
|
||||
```
|
||||
|
||||
Транслированный файл будет создан в том же каталоге с именем v.
|
||||
Может потребоваться проверка синтаксиса, так как транслирование может содержать ошибки.
|
||||
|
||||
Дополнительную информацию и документацию можно найти в репозитории [go2v на GitHub](https://github.com/vlang/go2v/tree/old_implementation).
|
||||
|
||||
## Getting started
|
||||
|
||||
|
60
bot_manager/manager.v
Normal file
60
bot_manager/manager.v
Normal file
@ -0,0 +1,60 @@
|
||||
module bot_manager
|
||||
|
||||
import repository
|
||||
import tg_handle
|
||||
import models
|
||||
|
||||
pub struct BotManager {
|
||||
pub mut:
|
||||
active_bots map[i64]&tg_handle.TgBot
|
||||
repo repository.Repo
|
||||
}
|
||||
|
||||
pub fn new_bot_manager(repo repository.Repo) BotManager {
|
||||
return BotManager{
|
||||
active_bots: map[i64]&tg_handle.TgBot{}
|
||||
repo: repo
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut bm BotManager) manage_bots(new_bots map[i64]models.TelegramIntegration) ! {
|
||||
// перебираем переданную мапку, сраавниваем то что работает с тем что пришло, дублей быть не должно
|
||||
for bot_id, bot_data in new_bots {
|
||||
if bot_id !in bm.active_bots {
|
||||
bm.start_bot(bot_data)!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut bm BotManager) start_bot(bot models.TelegramIntegration) ! {
|
||||
// доп проверка ну а вдруг
|
||||
if bot.id in bm.active_bots {
|
||||
eprintln('already started: $bot.id')
|
||||
return
|
||||
}
|
||||
tg_bot := tg_handle.new_tg_bot( bm.repo,bot)
|
||||
bm.active_bots[bot.id] = tg_bot
|
||||
println('started: $bot.id')
|
||||
}
|
||||
|
||||
pub fn (mut bm BotManager)deleted_bot(bots []models.TelegramIntegration)!{
|
||||
for bot in bots {
|
||||
if bot.id in bm.active_bots {
|
||||
handle := bm.active_bots[bot.id]or {
|
||||
println('Error get bot handle for: $bot.id')
|
||||
continue
|
||||
}
|
||||
handle.stop_ch <- 1
|
||||
bm.active_bots.delete(bot.id)
|
||||
println('stopped and deleted: $bot.id')
|
||||
} else {
|
||||
println('bot not found: $bot.id')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut bm BotManager) stop_all_bots() {
|
||||
for _,bot in bm.active_bots{
|
||||
bot.stop_ch <-1
|
||||
}
|
||||
}
|
60
client/answerer.v
Normal file
60
client/answerer.v
Normal file
@ -0,0 +1,60 @@
|
||||
module client
|
||||
|
||||
import json
|
||||
import net.http
|
||||
|
||||
pub const answerer_url = 'http://answerer_url'
|
||||
|
||||
struct GetQuizData {
|
||||
quiz_id string
|
||||
limit int
|
||||
need_config bool
|
||||
}
|
||||
|
||||
pub fn get_quiz_data(quiz_id string,session string) ! {
|
||||
url := answerer_url+'/settings'
|
||||
|
||||
req_data := json.encode(GetQuizData{
|
||||
quiz_id: quiz_id
|
||||
limit: 10
|
||||
need_config: false
|
||||
})
|
||||
|
||||
mut req := http.new_request(.post, url, req_data)
|
||||
req.add_custom_header('X-SessionKey', session)or{
|
||||
println('failed create custom header: $err ')
|
||||
}
|
||||
|
||||
response := req.do() or {
|
||||
return error('Failed to create http get_quiz_data request: $err')
|
||||
}
|
||||
|
||||
if response.status_code != 200 {
|
||||
return error('Failed to get http quiz data: $response.status_code')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn put_answers(session string, qid string,answers_json string, files map[string][]http.FileData) ! {
|
||||
url := answerer_url+'/answer'
|
||||
mut header := http.Header{}
|
||||
header.add_custom('X-SessionKey', session)or{
|
||||
println('failed create custom header: $err ')
|
||||
}
|
||||
conf := http.PostMultipartFormConfig{
|
||||
header: header ,
|
||||
form: {
|
||||
'answers': answers_json
|
||||
'qid' : qid
|
||||
},
|
||||
files: files
|
||||
}
|
||||
|
||||
response := http.post_multipart_form(url, conf) or {
|
||||
return error('Failed to send multipart form request: $err')
|
||||
}
|
||||
|
||||
if response.status_code != 200 {
|
||||
return error('Failed to get valid response: $response.status_code')
|
||||
}
|
||||
}
|
||||
|
203
client/tg_files.v
Normal file
203
client/tg_files.v
Normal file
@ -0,0 +1,203 @@
|
||||
module client
|
||||
|
||||
import net.http
|
||||
import json
|
||||
|
||||
pub struct DepsLoadSendVideo {
|
||||
pub mut:
|
||||
video_url string
|
||||
bot_token string
|
||||
caption string
|
||||
reply_markup string
|
||||
chat_id string
|
||||
}
|
||||
|
||||
pub fn load_and_send_video(deps DepsLoadSendVideo) ! {
|
||||
response_s3 := http.get(deps.video_url) or { return error('Failed to load video: $err') }
|
||||
if response_s3.status_code != 200 {
|
||||
return error('Failed to load video, status code: $response_s3.status_code')
|
||||
}
|
||||
|
||||
max_size := 50 * 1024 * 1024 // 50 MB в байтах
|
||||
if response_s3.body.len > max_size {
|
||||
return error('Video file size exceeds 50 MB limit, url - ${deps.video_url}')
|
||||
}
|
||||
|
||||
mut files := map[string][]http.FileData{}
|
||||
file_to_upload := http.FileData{
|
||||
filename: '${deps.video_url.split('/').last()}.mp4'
|
||||
content_type: 'application/octet-stream'
|
||||
data: response_s3.body
|
||||
}
|
||||
|
||||
files['video']=[file_to_upload]
|
||||
|
||||
conf := http.PostMultipartFormConfig{
|
||||
form: {
|
||||
'chat_id':deps.chat_id
|
||||
'caption':deps.caption
|
||||
'reply_markup':deps.reply_markup
|
||||
'parse_mode':'Markdown'
|
||||
}
|
||||
files: files
|
||||
}
|
||||
|
||||
response_tg := http.post_multipart_form('https://api.telegram.org/bot${deps.bot_token}/sendVideo', conf) or {
|
||||
return error('Failed to send multipart form request: $err')
|
||||
}
|
||||
if response_tg.status_code != 200 {
|
||||
return error('Failed to get valid response: $response_tg.status_code, body: $response_tg.body')
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DepsLoadSendPhoto {
|
||||
pub mut:
|
||||
photo_url string
|
||||
bot_token string
|
||||
caption string
|
||||
reply_markup string
|
||||
chat_id string
|
||||
}
|
||||
|
||||
pub fn load_and_send_photo(deps DepsLoadSendPhoto) ! {
|
||||
response_s3 := http.get(deps.photo_url) or { return error('Failed to load photo: $err') }
|
||||
if response_s3.status_code != 200 {
|
||||
return error('Failed to load photo, status code: $response_s3.status_code')
|
||||
}
|
||||
|
||||
mut files := map[string][]http.FileData{}
|
||||
file_to_upload := http.FileData{
|
||||
filename: '${deps.photo_url.split('/').last()}.png'
|
||||
content_type: 'application/octet-stream'
|
||||
data: response_s3.body
|
||||
}
|
||||
|
||||
files['photo']=[file_to_upload]
|
||||
|
||||
conf := http.PostMultipartFormConfig{
|
||||
form: {
|
||||
'chat_id':deps.chat_id
|
||||
'caption':deps.caption
|
||||
'reply_markup':deps.reply_markup
|
||||
'parse_mode':'Markdown'
|
||||
}
|
||||
files: files
|
||||
}
|
||||
|
||||
response_tg := http.post_multipart_form('https://api.telegram.org/bot${deps.bot_token}/sendPhoto', conf) or {
|
||||
return error('Failed to send multipart form request: $err')
|
||||
}
|
||||
if response_tg.status_code != 200 {
|
||||
return error('Failed to get valid response: $response_tg.status_code, body: $response_tg.body')
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DepsEditMessageMedia {
|
||||
pub mut:
|
||||
media_url string
|
||||
bot_token string
|
||||
chat_id string
|
||||
message_id string
|
||||
caption string
|
||||
reply_markup string
|
||||
}
|
||||
|
||||
pub fn edit_message_media(deps DepsEditMessageMedia) ! {
|
||||
response_s3 := http.get(deps.media_url) or { return error('Failed to load media: $err') }
|
||||
if response_s3.status_code != 200 {
|
||||
return error('Failed to load media, status code: $response_s3.status_code')
|
||||
}
|
||||
|
||||
mut files := map[string][]http.FileData{}
|
||||
file_to_upload := http.FileData{
|
||||
filename: '${deps.media_url.split('/').last()}.png'
|
||||
content_type: 'application/octet-stream'
|
||||
data: response_s3.body
|
||||
}
|
||||
|
||||
files['media'] = [file_to_upload]
|
||||
|
||||
media := json.encode({
|
||||
'type': 'photo',
|
||||
'media': 'attach://media',
|
||||
'caption':deps.caption
|
||||
})
|
||||
|
||||
conf := http.PostMultipartFormConfig{
|
||||
form: {
|
||||
'chat_id': deps.chat_id
|
||||
'message_id': deps.message_id
|
||||
'media': media
|
||||
'reply_markup':deps.reply_markup
|
||||
}
|
||||
files: files
|
||||
}
|
||||
|
||||
response_tg := http.post_multipart_form('https://api.telegram.org/bot${deps.bot_token}/editMessageMedia', conf) or {
|
||||
return error('Failed to send multipart form request: $err')
|
||||
}
|
||||
|
||||
if response_tg.status_code != 200 {
|
||||
return error('Failed to get valid response: $response_tg.status_code, body: $response_tg.body')
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DepsSendMediaGroup {
|
||||
pub mut:
|
||||
bot_token string
|
||||
chat_id string
|
||||
input_media []DepsInputMedia
|
||||
}
|
||||
|
||||
pub struct DepsInputMedia {
|
||||
pub mut:
|
||||
media_url string
|
||||
caption string
|
||||
}
|
||||
|
||||
pub fn send_media_group(deps DepsSendMediaGroup) ! {
|
||||
mut files := map[string][]http.FileData{}
|
||||
mut arr_input_media := []map[string]string{}
|
||||
|
||||
for input_media in deps.input_media {
|
||||
response_s3 := http.get(input_media.media_url) or {
|
||||
return error('Failed to load media: $err')
|
||||
}
|
||||
if response_s3.status_code != 200 {
|
||||
return error('Failed to load media, status code: $response_s3.status_code')
|
||||
}
|
||||
|
||||
file_to_upload := http.FileData{
|
||||
filename: '${input_media.media_url.split('/').last()}.png'
|
||||
content_type: 'application/octet-stream'
|
||||
data: response_s3.body
|
||||
}
|
||||
|
||||
files[input_media.media_url.split('/').last()] = [file_to_upload]
|
||||
|
||||
media := {
|
||||
'type': 'photo'
|
||||
'media': 'attach://$input_media.media_url.split('/').last()'
|
||||
'caption': input_media.caption
|
||||
}
|
||||
|
||||
arr_input_media << media
|
||||
}
|
||||
|
||||
media_json := json.encode(arr_input_media)
|
||||
conf := http.PostMultipartFormConfig{
|
||||
form: {
|
||||
'chat_id': deps.chat_id
|
||||
'media': media_json
|
||||
}
|
||||
files: files
|
||||
}
|
||||
|
||||
response_tg := http.post_multipart_form('https://api.telegram.org/bot${deps.bot_token}/sendMediaGroup', conf) or {
|
||||
return error('Failed to send multipart form request: $err')
|
||||
}
|
||||
|
||||
if response_tg.status_code != 200 {
|
||||
return error('Failed to send media group, status code: $response_tg.status_code, body: $response_tg.body')
|
||||
}
|
||||
}
|
96
controllers/integration.v
Normal file
96
controllers/integration.v
Normal file
@ -0,0 +1,96 @@
|
||||
module controllers
|
||||
|
||||
import repository
|
||||
import veb
|
||||
import models
|
||||
import json
|
||||
import utils
|
||||
|
||||
pub struct IntegrationControllers {
|
||||
pub mut:
|
||||
repo repository.Repo
|
||||
}
|
||||
|
||||
@['/:quizID'; get]
|
||||
fn (mut c IntegrationControllers) get(mut ctx veb.Context,quizID i64) veb.Result {
|
||||
account_id := utils.jwt_auth(mut ctx)
|
||||
if account_id == ""{
|
||||
return ctx.request_error("empty jwt")
|
||||
}
|
||||
|
||||
integration := c.repo.get_integration_by_id(quizID) or {
|
||||
return ctx.server_error('Error getting integration: $err')
|
||||
}
|
||||
return ctx.json(integration)
|
||||
}
|
||||
|
||||
@['/:quizID'; post]
|
||||
fn (mut c IntegrationControllers) create(mut ctx veb.Context,quizID i64) veb.Result {
|
||||
account_id := utils.jwt_auth(mut ctx)
|
||||
if account_id==""{
|
||||
return ctx.request_error("empty jwt")
|
||||
}
|
||||
|
||||
req := json.decode(models.TelegramIntegrationCreateReq,ctx.req.data)or {
|
||||
return ctx.request_error('Failed parse request body: $err')
|
||||
}
|
||||
|
||||
mut integration := models.TelegramIntegration{
|
||||
account_id: account_id
|
||||
quiz_id: quizID
|
||||
bot_token: req.bot_token
|
||||
repeatable: false
|
||||
bot_name: ''
|
||||
deleted: false
|
||||
status: models.active_status
|
||||
}
|
||||
|
||||
if integration.account_id == "" || integration.bot_token == "" || integration.quiz_id==0{
|
||||
return ctx.request_error('Missing required fields')
|
||||
}
|
||||
|
||||
id := c.repo.create_integration(integration) or {
|
||||
return ctx.server_error('Failed create integration: $err')
|
||||
}
|
||||
|
||||
integration.id = id
|
||||
|
||||
return ctx.json(integration)
|
||||
}
|
||||
|
||||
@['/:quizID'; patch]
|
||||
fn (mut c IntegrationControllers) update(mut ctx veb.Context,quizID int) veb.Result {
|
||||
account_id := utils.jwt_auth(mut ctx)
|
||||
if account_id == ""{
|
||||
return ctx.request_error("empty jwt")
|
||||
}
|
||||
|
||||
req := json.decode(models.TelegramIntegrationUpdateReq,ctx.req.data)or {
|
||||
return ctx.request_error('Failed parse request body: $err')
|
||||
}
|
||||
if req.id== 0 || req.bot_token == ""{
|
||||
return ctx.request_error('Missing required fields')
|
||||
}
|
||||
|
||||
c.repo.update_bot_token(req.id,req.bot_token) or {
|
||||
return ctx.server_error('Failed update integration: $err')
|
||||
}
|
||||
|
||||
return ctx.ok("OK")
|
||||
}
|
||||
|
||||
@['/:quizID'; delete]
|
||||
fn (mut c IntegrationControllers) delete(mut ctx veb.Context,quizID i64) veb.Result {
|
||||
account_id := utils.jwt_auth(mut ctx)
|
||||
if account_id==""{
|
||||
return ctx.request_error("empty jwt")
|
||||
}
|
||||
|
||||
if quizID == 0{
|
||||
return ctx.request_error('quizID dont be 0')
|
||||
}
|
||||
c.repo.soft_delete_integration(quizID)or {
|
||||
return ctx.server_error('Failed soft delete integration: $err')
|
||||
}
|
||||
return ctx.ok("OK")
|
||||
}
|
300
instance_worker/worker.v
Normal file
300
instance_worker/worker.v
Normal file
@ -0,0 +1,300 @@
|
||||
module instance_worker
|
||||
|
||||
import time
|
||||
import models
|
||||
import dariotarantini.vgram
|
||||
import repository
|
||||
import bot_manager
|
||||
import tools
|
||||
|
||||
pub struct InstanceWorker {
|
||||
pub mut:
|
||||
instance_id int
|
||||
tg_sender vgram.Bot
|
||||
repo repository.Repo
|
||||
bot_manager bot_manager.BotManager
|
||||
stop_ch chan int
|
||||
}
|
||||
|
||||
pub fn (mut worker InstanceWorker) start() {
|
||||
mut max_checkout := worker.get_max_checkout()or {
|
||||
eprintln(err)
|
||||
return
|
||||
}
|
||||
|
||||
now := time.now().unix()
|
||||
if now>max_checkout{
|
||||
d := now - max_checkout
|
||||
if d >=10{
|
||||
max_checkout = now
|
||||
}else{
|
||||
max_checkout = now+10
|
||||
}
|
||||
}else{
|
||||
max_checkout +=10
|
||||
}
|
||||
|
||||
max_checkout += 5*60
|
||||
|
||||
new_instance := models.TelegramIntegrationInstance{
|
||||
checkout: max_checkout
|
||||
limit: models.bot_limit
|
||||
amount: 0
|
||||
}
|
||||
|
||||
worker.register_instance(new_instance) or {
|
||||
eprintln(err)
|
||||
return
|
||||
}
|
||||
|
||||
mut count := 0
|
||||
mut ticker := tools.new_ticker(models.check_interval)
|
||||
defer {
|
||||
ticker.stop()
|
||||
}
|
||||
|
||||
wait_time := max_checkout - time.now().unix()
|
||||
if wait_time > 0 {
|
||||
time.sleep(wait_time * time.second)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
_ := <-ticker.c{
|
||||
// начинаем транзакцию
|
||||
worker.repo.pg_db.exec('BEGIN') or { panic(err) }
|
||||
// блокируем таблицы
|
||||
worker.repo.pg_db.exec('LOCK TABLE telegram_integration IN ACCESS EXCLUSIVE MODE') or { panic(err) }
|
||||
worker.repo.pg_db.exec('LOCK TABLE telegram_integration_instance IN ACCESS EXCLUSIVE MODE') or { panic(err) }
|
||||
|
||||
// обновляем только тогда когда прошла хотябы 1 итерация
|
||||
if count>0{
|
||||
worker.update_instance_checkout() or {
|
||||
eprintln(err)
|
||||
worker.repo.pg_db.exec('ROLLBACK') or { panic(err)}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ищем и удаляем устаревшие инстансы
|
||||
worker.remove_stale_instances() or {
|
||||
eprintln(err)
|
||||
worker.repo.pg_db.exec('ROLLBACK') or { panic(err)}
|
||||
return
|
||||
}
|
||||
|
||||
current_bots := worker.manage_bots()or{
|
||||
eprintln(err)
|
||||
worker.repo.pg_db.exec('ROLLBACK') or { panic(err)}
|
||||
return
|
||||
}
|
||||
|
||||
// если все успешно комитим транзакцию
|
||||
worker.repo.pg_db.exec('COMMIT') or { panic(err) }
|
||||
// обновляем количество итераций
|
||||
count += 1
|
||||
// запускаем ботов через манагера
|
||||
worker.bot_manager.manage_bots(current_bots)or{
|
||||
eprintln(err)
|
||||
return
|
||||
}
|
||||
|
||||
// ищем удаленных или стопнутых
|
||||
deleted_stopped := worker.get_stopped_deleted() or{
|
||||
eprintln(err)
|
||||
return
|
||||
}
|
||||
// удаляем удаленных или стопнутых
|
||||
worker.bot_manager.deleted_bot(deleted_stopped)or{
|
||||
eprintln(err)
|
||||
return
|
||||
}
|
||||
// оновляем инстансы у удаленных на 0
|
||||
worker.update_instance(deleted_stopped)or{
|
||||
eprintln(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_ := <-worker.stop_ch{
|
||||
worker.cleanup() or {
|
||||
println('$err')
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker) register_instance(data models.TelegramIntegrationInstance)! {
|
||||
instance_id := sql worker.repo.pg_db {
|
||||
insert data into models.TelegramIntegrationInstance
|
||||
} or {
|
||||
return error('Failed register telegram integration instance: $err')
|
||||
}
|
||||
worker.instance_id = instance_id
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker) get_max_checkout() !i64 {
|
||||
selected_members := sql worker.repo.pg_db {
|
||||
select from models.TelegramIntegrationInstance order by checkout desc
|
||||
}or {
|
||||
return error('Failed to get max checkout ${err}')
|
||||
}
|
||||
|
||||
if selected_members.len == 0{
|
||||
return 0
|
||||
}
|
||||
|
||||
return selected_members[0].checkout
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker)update_instance_checkout()!{
|
||||
now := time.now().unix()
|
||||
sql worker.repo.pg_db {
|
||||
update models.TelegramIntegrationInstance
|
||||
set checkout = now where id == worker.instance_id
|
||||
}or {
|
||||
return error('Failed update instance checkout: $err')
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker) remove_stale_instances() ! {
|
||||
expired := time.now().unix() - 10 * 60 // 10 минут назад
|
||||
|
||||
data_expired := sql worker.repo.pg_db {
|
||||
select from models.TelegramIntegrationInstance where checkout < expired
|
||||
}or{
|
||||
return error('Failed get expired data: $err')
|
||||
}
|
||||
|
||||
sql worker.repo.pg_db {
|
||||
delete from models.TelegramIntegrationInstance where checkout < expired
|
||||
}or{
|
||||
return error('Failed remove stale instances: $err')
|
||||
}
|
||||
|
||||
for _,data in data_expired{
|
||||
sql worker.repo.pg_db{
|
||||
update models.TelegramIntegration set instance_id = 0 where instance_id == data.id
|
||||
} or{
|
||||
return error('Failed update integration table: $err')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker) manage_bots() !map[i64]models.TelegramIntegration {
|
||||
mut current_instance_bots := map[i64]models.TelegramIntegration{}
|
||||
// получаем количество ботов которые еще не назначены ни одному инстансу
|
||||
mut free_bots := sql worker.repo.pg_db {
|
||||
select from models.TelegramIntegration where status == models.active_status.str() && deleted == false && instance_id == 0
|
||||
}or{
|
||||
return error('Failed fetch free bots: $err')
|
||||
}
|
||||
|
||||
// получаем все "живые" инстансы
|
||||
all_active_instance:= sql worker.repo.pg_db{
|
||||
select from models.TelegramIntegrationInstance
|
||||
}or {
|
||||
return error('Failed get current instance: $err')
|
||||
}
|
||||
|
||||
// считаем общее количество слотов
|
||||
mut total_slots := 0
|
||||
for instance in all_active_instance {
|
||||
total_slots += instance.limit - instance.amount
|
||||
}
|
||||
|
||||
// количество свободных ботов
|
||||
bots_assign := free_bots.len
|
||||
// если количество ботов больше чем суммарное количество слотов всех инстансов отправляем оповещение
|
||||
if bots_assign > total_slots {
|
||||
msg := 'Осторожно: Свободных ботов больше чем инстансы могут взять в работу.'
|
||||
worker.tg_sender.send_message(vgram.ConfigSendMessage{
|
||||
chat_id: models.telegram_channel_id,
|
||||
text: msg
|
||||
})
|
||||
}
|
||||
|
||||
// распределяем ботов по инстансам
|
||||
mut bot_index := 0
|
||||
mut assigned_bots := 0
|
||||
for instance in all_active_instance {
|
||||
free_slots := instance.limit - instance.amount
|
||||
mut bots_for_instance := bots_assign / all_active_instance.len
|
||||
|
||||
// если доступных слотов больше то используем минимальное значение
|
||||
if bots_for_instance > free_slots {
|
||||
bots_for_instance = free_slots
|
||||
}
|
||||
|
||||
sql worker.repo.pg_db {
|
||||
update models.TelegramIntegrationInstance set amount = amount + bots_for_instance where id == instance.id
|
||||
} or {
|
||||
return error('Failed update instance amount: $err')
|
||||
}
|
||||
|
||||
// назначаем ботов инстансу
|
||||
for i := 0; i < bots_for_instance; i++ {
|
||||
if bot_index >= free_bots.len {
|
||||
break
|
||||
}
|
||||
bot := free_bots[bot_index]
|
||||
if instance.id == worker.instance_id{
|
||||
current_instance_bots[bot.id] = bot
|
||||
}
|
||||
sql worker.repo.pg_db {
|
||||
update models.TelegramIntegration set instance_id = instance.id where id == bot.id
|
||||
} or {
|
||||
return error('Failed update bot instance_id: $err')
|
||||
}
|
||||
bot_index++
|
||||
assigned_bots++
|
||||
}
|
||||
|
||||
// если все боты распределены выходим
|
||||
if assigned_bots >= bots_assign {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return current_instance_bots
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker) get_stopped_deleted() ![]models.TelegramIntegration {
|
||||
mut results := sql worker.repo.pg_db {
|
||||
select from models.TelegramIntegration where (status == models.stopped_status.str() || deleted == true) && instance_id == worker.instance_id
|
||||
}or{
|
||||
return error('Failed fetch stopped deleted bots: $err')
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker)update_instance(bots []models.TelegramIntegration)!{
|
||||
for bot in bots{
|
||||
sql worker.repo.pg_db{
|
||||
update models.TelegramIntegration set instance_id = 0 where id == bot.id
|
||||
}or{
|
||||
return error('Failed update instance for bots: $err')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut worker InstanceWorker) stop(){
|
||||
worker.stop_ch <- 1
|
||||
}
|
||||
|
||||
fn (mut worker InstanceWorker) cleanup() !{
|
||||
worker.bot_manager.stop_all_bots()
|
||||
sql worker.repo.pg_db {
|
||||
delete from models.TelegramIntegrationInstance where id == worker.instance_id
|
||||
} or {
|
||||
return error('Error deleting instance in cleanup: $err')
|
||||
}
|
||||
sql worker.repo.pg_db {
|
||||
update models.TelegramIntegration set instance_id = 0 where instance_id == worker.instance_id
|
||||
}or{
|
||||
return error('Error updating instance_id in cleanup: $err')
|
||||
}
|
||||
}
|
107
main.v
Normal file
107
main.v
Normal file
@ -0,0 +1,107 @@
|
||||
module main
|
||||
import json
|
||||
import log
|
||||
import os
|
||||
import models
|
||||
import repository
|
||||
import context
|
||||
import db.pg
|
||||
import veb
|
||||
import controllers
|
||||
import instance_worker
|
||||
import dariotarantini.vgram
|
||||
import bot_manager
|
||||
import time
|
||||
import tg_handle
|
||||
|
||||
//v -enable-globals -cc gcc -cflags "-IC:/PostgreSQL/16/include -LC:/PostgreSQL/16/lib" run main.v
|
||||
__global (
|
||||
data_set_map map[string]models.Question
|
||||
)
|
||||
|
||||
fn init() {
|
||||
f := os.read_file('treedata.json') or {
|
||||
log.error('err read json file: $err')
|
||||
return
|
||||
}
|
||||
|
||||
data_set := json.decode([]models.Question, f) or {
|
||||
log.error('err unmarshall json: $err')
|
||||
return
|
||||
}
|
||||
|
||||
for data in data_set {
|
||||
data_set_map[data.title] = data
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
mut c := context.background()
|
||||
mut ctx, cancel := context.with_cancel(mut c)
|
||||
|
||||
go fn [cancel] () {
|
||||
shutdown(cancel)
|
||||
}()
|
||||
|
||||
mut repo := repository.Repo{db: map[string]string{},data_set_map: data_set_map}
|
||||
repo.connect_to_db(pg.Config{
|
||||
host: 'localhost'
|
||||
port: 35432
|
||||
user: 'squiz'
|
||||
password: 'Redalert2'
|
||||
dbname: 'squiz'
|
||||
})or {
|
||||
eprintln("err conect to db")
|
||||
}
|
||||
|
||||
mut wc := &instance_worker.InstanceWorker{
|
||||
repo: repo
|
||||
tg_sender: vgram.new_bot(models.telegram_bot_token)
|
||||
bot_manager: bot_manager.new_bot_manager(repo)
|
||||
stop_ch: chan int{}
|
||||
}
|
||||
|
||||
spawn wc.start()
|
||||
|
||||
|
||||
// tg_handle.new_tg_bot(repo,models.TelegramIntegration{
|
||||
// id: 5
|
||||
// quiz_id: 26357
|
||||
// bot_token: '7127966184:AAG1steOCH4wDvHRe9QcsXJPS4dWRyRYsqg'
|
||||
// })
|
||||
// tg_handle.new_tg_bot(repo,models.TelegramIntegration{
|
||||
// id: 4
|
||||
// quiz_id: 26384
|
||||
// bot_token: '6712573453:AAFqTOsgwe_j48ZQ1GzWKQDT5Nwr-SAWjz8'
|
||||
// })
|
||||
|
||||
mut app := &models.App{}
|
||||
mut integration_controllers := &controllers.IntegrationControllers{repo:repo}
|
||||
app.register_controller[controllers.IntegrationControllers, models.Context]('/integration', mut integration_controllers)!
|
||||
|
||||
spawn fn (mut app models.App) {
|
||||
veb.run[models.App, models.Context](mut app, 8000)
|
||||
}(mut app)
|
||||
|
||||
_ = <-ctx.done()
|
||||
|
||||
wc.stop()
|
||||
time.sleep(2 * time.second)
|
||||
repo.stop_db()
|
||||
}
|
||||
|
||||
fn shutdown(cancel context.CancelFn) {
|
||||
os.signal_opt(.int, fn [cancel] (sig os.Signal) {
|
||||
println('signal: $sig')
|
||||
cancel()
|
||||
}) or {
|
||||
eprintln('$err')
|
||||
}
|
||||
|
||||
os.signal_opt(.term, fn [cancel] (sig os.Signal) {
|
||||
println('signal signal: $sig')
|
||||
cancel()
|
||||
}) or {
|
||||
eprintln('$err')
|
||||
}
|
||||
}
|
140
models/models.v
Normal file
140
models/models.v
Normal file
@ -0,0 +1,140 @@
|
||||
module models
|
||||
// структуры объявляются в принципе как в гошке, заисключением того что видимо взято из С происходит деление на публичные и приватные
|
||||
// поэтому для того чтобы реализовать инкапсуляцию, если указать ключевое слово pub - будет все доступно
|
||||
import time
|
||||
pub struct Button {
|
||||
pub mut:
|
||||
text string @[json: 'Text']
|
||||
state string @[json: 'State']
|
||||
}
|
||||
|
||||
pub struct Question {
|
||||
pub mut:
|
||||
title string @[json: 'Title']
|
||||
description string @[json: 'Description']
|
||||
buttons []Button @[json: 'Buttons']
|
||||
}
|
||||
|
||||
@[table: 'question']
|
||||
pub struct QuizQuestion {
|
||||
pub mut:
|
||||
id i64 @[primary; sql: serial]
|
||||
quiz_id i64 @[sql: 'quiz_id']
|
||||
title string @[sql: 'title']
|
||||
description string @[sql: 'description']
|
||||
question_type string @[sql: 'questiontype']
|
||||
content string @[sql: 'content']
|
||||
required bool @[sql: 'required']
|
||||
deleted bool @[sql: 'deleted']
|
||||
page i64 @[sql:'page']
|
||||
}
|
||||
|
||||
@[table: 'telegram_integration']
|
||||
pub struct TelegramIntegration {
|
||||
pub mut:
|
||||
id i64 @[primary; sql: serial]
|
||||
account_id string @[sql: 'accountid']
|
||||
quiz_id i64 @[sql: 'quizid']
|
||||
bot_token string @[sql: 'bot_token']
|
||||
repeatable bool @[sql: 'repeatable']
|
||||
bot_name string @[sql: 'bot_name']
|
||||
deleted bool @[sql: 'deleted']
|
||||
status BotStatus @[sql:'status']
|
||||
instance_id int @[sql:'instance_id']
|
||||
}
|
||||
|
||||
@[table: 'respondent_state']
|
||||
pub struct RespondentState {
|
||||
pub mut:
|
||||
id i64 @[primary; sql: serial]
|
||||
telegram_id int @[sql: 'telegram_id']
|
||||
quiz_id i64 @[sql: 'quizid']
|
||||
state i64 @[sql: 'state']
|
||||
lang string @[sql: 'lang']
|
||||
contact string @[sql: 'contact']
|
||||
finish bool @[sql: 'finish']
|
||||
session string @[sql:'session']
|
||||
}
|
||||
|
||||
@[table: 'telegram_integration_instance']
|
||||
pub struct TelegramIntegrationInstance {
|
||||
pub mut:
|
||||
id i64 @[primary; sql: serial]
|
||||
checkout i64 @[sql: 'checkout']
|
||||
limit int @[sql: 'limited']
|
||||
amount int @[sql: 'amount']
|
||||
}
|
||||
|
||||
type BotStatus = string
|
||||
pub const (
|
||||
active_status = BotStatus('active')
|
||||
stopped_status = BotStatus('stopped')
|
||||
banned_status = BotStatus('banned')
|
||||
)
|
||||
|
||||
type QuestionType = string
|
||||
pub const(
|
||||
question_type_variant = QuestionType('variant')
|
||||
question_type_images = QuestionType('images')
|
||||
question_type_varimg = QuestionType('varimg')
|
||||
question_type_emoji = QuestionType('emoji')
|
||||
question_type_text = QuestionType('text')
|
||||
question_type_select = QuestionType('select')
|
||||
question_type_date = QuestionType('date')
|
||||
question_type_number = QuestionType('number')
|
||||
question_type_file = QuestionType('file')
|
||||
question_type_page = QuestionType('page')
|
||||
question_type_rating = QuestionType('rating')
|
||||
question_type_result = QuestionType('result')
|
||||
)
|
||||
|
||||
|
||||
pub const (
|
||||
check_interval = 5 * time.minute
|
||||
bot_limit = 10 // лимит ботов на инстанс
|
||||
telegram_channel_id = '-1002217604546'
|
||||
telegram_bot_token = '6712573453:AAFqTOsgwe_j48ZQ1GzWKQDT5Nwr-SAWjz8'
|
||||
buttons_per_row = 5 // количество кнопок в одном ряду
|
||||
)
|
||||
|
||||
pub struct CallbackData {
|
||||
pub mut:
|
||||
answer_id string
|
||||
state_id i64
|
||||
}
|
||||
|
||||
[table: 'telegram_user_quiz_result']
|
||||
pub struct TelegramUserQuizResult {
|
||||
pub mut:
|
||||
id i64 [primary; sql: serial]
|
||||
bot_id i64 @[sql: 'bot_id']
|
||||
quiz_id i64 @[sql: 'quiz_id']
|
||||
current_field string @[sql: 'current_field']
|
||||
user_answer string @[sql: 'user_answer']
|
||||
state string @[sql: 'state']
|
||||
session string @[sql:'session']
|
||||
question_id i64 @[sql:"question_id"]
|
||||
iter int @[sql: "iter"]
|
||||
}
|
||||
|
||||
type UserResultTgStatus = string
|
||||
pub const(
|
||||
user_result_tg_status_in_progress = UserResultTgStatus('in_progress')
|
||||
user_result_tg_status_completed = UserResultTgStatus('completed')
|
||||
)
|
||||
|
||||
pub struct ResultContent {
|
||||
pub mut:
|
||||
text string [json: 'text']
|
||||
name string [json: 'name']
|
||||
email string [json: 'email']
|
||||
phone string [json: 'phone']
|
||||
address string [json: 'address']
|
||||
}
|
||||
|
||||
pub struct ImageContent{
|
||||
pub mut:
|
||||
image string [json: 'Image']
|
||||
description string [json: 'Description']
|
||||
}
|
||||
|
296
models/question_types.v
Normal file
296
models/question_types.v
Normal file
@ -0,0 +1,296 @@
|
||||
module models
|
||||
|
||||
pub struct QuizQuestionDate {
|
||||
pub mut:
|
||||
id i64 @[json: 'id']
|
||||
required bool @[json: 'required']
|
||||
inner_name_check bool @[json: 'innerNameCheck']
|
||||
inner_name string @[json: 'innerName']
|
||||
date_range bool @[json: 'dateRange']
|
||||
time bool @[json: 'time']
|
||||
hint QuestionHint @[json: 'hint']
|
||||
rule QuestionBranchingRule @[json: 'rule']
|
||||
back string @[json: 'back']
|
||||
original_back string @[json: 'originalBack']
|
||||
autofill bool @[json: 'autofill']
|
||||
}
|
||||
|
||||
pub struct QuizQuestionEmoji {
|
||||
pub mut:
|
||||
id i64 @[json: 'id']
|
||||
multi bool @[json: 'multi']
|
||||
own bool @[json: 'own']
|
||||
required bool @[json: 'required']
|
||||
inner_name_check bool @[json: 'innerNameCheck']
|
||||
inner_name string @[json: 'innerName']
|
||||
variants []QuestionVariant @[json: 'variants']
|
||||
hint QuestionHint @[json: 'hint']
|
||||
rule QuestionBranchingRule @[json: 'rule']
|
||||
back string @[json: 'back']
|
||||
original_back string @[json: 'originalBack']
|
||||
autofill bool @[json: 'autofill']
|
||||
}
|
||||
|
||||
type UploadFileType = string
|
||||
pub const(
|
||||
upload_file_type_picture = UploadFileType('picture')
|
||||
upload_file_type_video = UploadFileType('video')
|
||||
upload_file_type_audio = UploadFileType('audio')
|
||||
upload_file_type_document = UploadFileType('document')
|
||||
)
|
||||
|
||||
pub struct QuizQuestionFile {
|
||||
pub mut:
|
||||
id i64 @[json: 'id']
|
||||
required bool @[json: 'required']
|
||||
inner_name_check bool @[json: 'innerNameCheck']
|
||||
inner_name string @[json: 'innerName']
|
||||
date_range bool @[json: 'dateRange']
|
||||
tipe UploadFileType @[json: 'type']
|
||||
hint QuestionHint @[json: 'hint']
|
||||
rule QuestionBranchingRule @[json: 'rule']
|
||||
back string @[json: 'back']
|
||||
original_back string @[json: 'originalBack']
|
||||
autofill bool @[json: 'autofill']
|
||||
}
|
||||
|
||||
type ImageRatio = string
|
||||
pub const(
|
||||
image_ratio_one_to_one = ImageRatio('1:1')
|
||||
image_ratio_one_to_two = ImageRatio('1:2')
|
||||
image_ratio_two_to_one = ImageRatio('2:1')
|
||||
)
|
||||
|
||||
type ImageFormat = string
|
||||
pub const(
|
||||
image_format_carousel = ImageFormat('carousel')
|
||||
image_format_masonry = ImageFormat('masonry')
|
||||
)
|
||||
|
||||
pub struct QuizQuestionImages {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
own bool @[json: "own"]
|
||||
multi bool @[json: "multi"]
|
||||
xy ImageRatio @[json: "xy"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
large bool @[json: "large"]
|
||||
format ImageFormat @[json: "format"]
|
||||
required bool @[json: "required"]
|
||||
variants []QuestionVariant @[json: "variants"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
large_check bool @[json: "largeCheck"]
|
||||
}
|
||||
|
||||
type NumberForm = string
|
||||
pub const (
|
||||
number_form_star = NumberForm('star')
|
||||
number_form_trophie = NumberForm('trophie')
|
||||
number_form_flag = NumberForm('flag')
|
||||
number_form_heart = NumberForm('heart')
|
||||
number_form_like = NumberForm('like')
|
||||
number_form_bubble = NumberForm('bubble')
|
||||
number_form_hashtag = NumberForm('hashtag')
|
||||
)
|
||||
|
||||
pub struct QuizQuestionNumber {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
required bool @[json: "required"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
range string @[json: "range"]
|
||||
start int @[json: "start"]
|
||||
default_value int @[json: "defaultValue"]
|
||||
step int @[json: "step"]
|
||||
steps int @[json: "steps"]
|
||||
choose_range bool @[json: "chooseRange"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
form NumberForm @[json: "form"]
|
||||
}
|
||||
|
||||
pub struct QuizQuestionPage {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
text string @[json: "text"]
|
||||
picture string @[json: "picture"]
|
||||
original_picture string @[json: "originalPicture"]
|
||||
use_image bool @[json: "useImage"]
|
||||
video string @[json: "video"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
}
|
||||
|
||||
pub struct QuizQuestionRating {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
required bool @[json: "required"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
steps int @[json: "steps"]
|
||||
rating_expanded bool @[json: "ratingExpanded"]
|
||||
form string @[json: "form"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
rating_positive_description string @[json: "ratingPositiveDescription"]
|
||||
rating_negative_description string @[json: "ratingNegativeDescription"]
|
||||
}
|
||||
|
||||
pub struct QuizQuestionResult {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
video string @[json: "video"]
|
||||
inner_name string @[json: "innerName"]
|
||||
text string @[json: "text"]
|
||||
price []int @[json: "price"]
|
||||
use_image bool @[json: "useImage"]
|
||||
rule ResultQuestionBranchingRule @[json: "rule"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
autofill bool @[json: "autofill"]
|
||||
usage bool @[json: "usage"]
|
||||
redirect string @[json: "redirect"]
|
||||
}
|
||||
|
||||
pub struct QuizQuestionSelect {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
multi bool @[json: "multi"]
|
||||
required bool @[json: "required"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
default string @[json: "default"]
|
||||
variants []QuestionVariant @[json: "variants"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
}
|
||||
|
||||
type AnswerType = string
|
||||
pub const(
|
||||
answer_type_single = AnswerType('single')
|
||||
answer_type_multi = AnswerType('multi')
|
||||
answer_type_number_only = AnswerType('numberOnly')
|
||||
)
|
||||
|
||||
pub struct QuizQuestionText {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
placeholder string @[json: "placeholder"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
required bool @[json: "required"]
|
||||
autofill bool @[json: "autofill"]
|
||||
answer_type AnswerType @[json: "answerType"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
only_numbers bool @[json: "onlyNumbers"]
|
||||
}
|
||||
|
||||
pub struct QuizQuestionVariant{
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
large_check bool @[json: "largeCheck"]
|
||||
multi bool @[json: "multi"]
|
||||
own bool @[json: "own"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
required bool @[json: "required"]
|
||||
inner_name string @[json: "innerName"]
|
||||
variants []QuestionVariant @[json: "variants"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
}
|
||||
|
||||
pub struct QuizQuestionVarImg {
|
||||
pub mut:
|
||||
id i64 @[json: "id"]
|
||||
own bool @[json: "own"]
|
||||
inner_name_check bool @[json: "innerNameCheck"]
|
||||
inner_name string @[json: "innerName"]
|
||||
required bool @[json: "required"]
|
||||
variants []QuestionVariant @[json: "variants"]
|
||||
hint QuestionHint @[json: "hint"]
|
||||
rule QuestionBranchingRule @[json: "rule"]
|
||||
back string @[json: "back"]
|
||||
original_back string @[json: "originalBack"]
|
||||
autofill bool @[json: "autofill"]
|
||||
large_check bool @[json: "largeCheck"]
|
||||
repl_text string @[json: "replText"]
|
||||
}
|
||||
|
||||
pub struct QuestionHint {
|
||||
pub mut:
|
||||
text string @[json: 'text'] // Текст подсказки
|
||||
video string @[json: 'video'] // URL видео подсказки
|
||||
}
|
||||
|
||||
pub struct QuestionBranchingRule {
|
||||
pub mut:
|
||||
children []int @[json: 'children']
|
||||
main []QuestionBranchingRuleMain @[json: 'main']
|
||||
parent_id int @[json: 'parentId']
|
||||
default int @[json: 'default']
|
||||
}
|
||||
|
||||
pub struct QuestionBranchingRuleMain {
|
||||
pub mut:
|
||||
next i64 @[json: 'next']
|
||||
orr bool @[json: 'or']
|
||||
rules []RuleCondition @[json: 'rules']
|
||||
}
|
||||
|
||||
pub struct RuleCondition {
|
||||
pub mut:
|
||||
question i64 @[json: 'question']
|
||||
answers []string @[json: 'answers']
|
||||
}
|
||||
|
||||
pub struct QuestionVariant {
|
||||
pub mut:
|
||||
id string @[json: 'id']
|
||||
answer string @[json: 'answer']
|
||||
hints string @[json: 'hints']
|
||||
extended_text string @[json: 'extendedText']
|
||||
original_image_url string @[json: 'originalImageUrl']
|
||||
points int @[json: 'points']
|
||||
}
|
||||
|
||||
pub struct ResultQuestionBranchingRule {
|
||||
pub mut:
|
||||
children []i64 @[json: 'children']
|
||||
main []QuestionBranchingRuleMain @[json: 'main']
|
||||
parent_id i64 @[json: 'parentId']
|
||||
default i64 @[json: 'default']
|
||||
min_score int @[json: 'minScore']
|
||||
}
|
||||
|
||||
type ResultForm = string
|
||||
pub const(
|
||||
result_form_after = ResultForm('after')
|
||||
result_form_before = ResultForm('before')
|
||||
)
|
101
models/quiz.v
Normal file
101
models/quiz.v
Normal file
@ -0,0 +1,101 @@
|
||||
module models
|
||||
|
||||
import time
|
||||
|
||||
@[table: 'quiz']
|
||||
pub struct Quiz {
|
||||
pub mut:
|
||||
id i64 @[primary; sql: serial]
|
||||
qid string @[sql: 'qid']
|
||||
account_id string @[sql: 'accountid']
|
||||
deleted bool @[sql: 'deleted']
|
||||
archived bool @[sql: 'archived']
|
||||
repeatable bool @[sql: 'repeatable']
|
||||
name string @[sql: 'name']
|
||||
description string @[sql: 'description']
|
||||
config string @[sql: 'config']
|
||||
}
|
||||
|
||||
type ShowResultType = string
|
||||
pub const(
|
||||
show_result_type_after = ShowResultType('after')
|
||||
show_result_type_before = ShowResultType('before')
|
||||
show_result_type_email = ShowResultType('email')
|
||||
)
|
||||
|
||||
pub struct QuizConfig {
|
||||
pub mut:
|
||||
have_root string @[json: 'haveRoot']
|
||||
form_contact FormContact @[json: 'formContact']
|
||||
result_info ResultInfo @[json: 'resultInfo']
|
||||
show_fc bool @[json:'showfc']
|
||||
antifraud bool @[json:'antifraud']
|
||||
info QuizConfigInfo @[json:'info']
|
||||
startpage QuizConfigStartPage @[json:'startpage']
|
||||
}
|
||||
|
||||
pub struct FormContact {
|
||||
pub mut:
|
||||
title string @[json: 'title']
|
||||
desc string @[json: 'desc']
|
||||
fields FormFields @[json: 'fields']
|
||||
button string @[json: 'button']
|
||||
}
|
||||
|
||||
pub struct FormFields {
|
||||
pub mut:
|
||||
name ContactField @[json: 'name']
|
||||
email ContactField @[json: 'email']
|
||||
phone ContactField @[json: 'phone']
|
||||
text ContactField @[json: 'text']
|
||||
address ContactField @[json: 'address']
|
||||
}
|
||||
|
||||
pub struct ContactField {
|
||||
pub mut:
|
||||
text string @[json: 'text']
|
||||
inner_text string @[json: 'innerText']
|
||||
key string @[json: 'key']
|
||||
required bool @[json: 'required']
|
||||
used bool @[json: 'used']
|
||||
}
|
||||
|
||||
pub struct ResultInfo {
|
||||
pub mut:
|
||||
when string @[json: 'when']
|
||||
share bool @[json: 'share']
|
||||
replay bool @[json: 'replay']
|
||||
theme string @[json: 'theme']
|
||||
reply string @[json: 'reply']
|
||||
repl_name string @[json: 'replname']
|
||||
show_result_form string @[json: 'showResultForm']
|
||||
}
|
||||
|
||||
pub struct QuizConfigInfo {
|
||||
pub mut:
|
||||
orgname string @[json:'orgname'] // имя компании
|
||||
phonenumber string @[json:'phonenumber'] // телефон компании
|
||||
site string @[json:'site'] // сайт компании
|
||||
law string @[json:'law'] // юр инфа
|
||||
}
|
||||
|
||||
pub struct QuizConfigStartPage {
|
||||
pub mut:
|
||||
description string @[json:'description'] // описание
|
||||
background Background @[json:'background']
|
||||
}
|
||||
|
||||
pub struct Background {
|
||||
pub mut:
|
||||
bg_type string @[json:'type'] //video или image
|
||||
desktop string @[json:'desktop'] // URL фона для десктопа
|
||||
video string @[json:'video'] // Видео URL
|
||||
}
|
||||
|
||||
@[table: 'answer']
|
||||
pub struct Answer {
|
||||
pub mut:
|
||||
content string @[sql: 'content']
|
||||
question_id i64 @[sql: 'question_id']
|
||||
result bool
|
||||
}
|
12
models/reqresp.v
Normal file
12
models/reqresp.v
Normal file
@ -0,0 +1,12 @@
|
||||
module models
|
||||
|
||||
pub struct TelegramIntegrationCreateReq {
|
||||
pub :
|
||||
bot_token string
|
||||
}
|
||||
|
||||
pub struct TelegramIntegrationUpdateReq {
|
||||
pub:
|
||||
bot_token string
|
||||
id i64
|
||||
}
|
13
models/srv.v
Normal file
13
models/srv.v
Normal file
@ -0,0 +1,13 @@
|
||||
module models
|
||||
|
||||
import veb
|
||||
|
||||
|
||||
pub struct Context {
|
||||
veb.Context
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
veb.Controller
|
||||
veb.Middleware[Context]
|
||||
}
|
81
models/variants.v
Normal file
81
models/variants.v
Normal file
@ -0,0 +1,81 @@
|
||||
module models
|
||||
|
||||
pub const data_set = [
|
||||
{
|
||||
'end': Question{
|
||||
title: 'Last question'
|
||||
description: 'This is last question'
|
||||
buttons: []
|
||||
}
|
||||
'q1': Question{
|
||||
title: 'Question 1'
|
||||
description: 'This question 1 after start question'
|
||||
buttons: [
|
||||
Button{
|
||||
text: 'q2'
|
||||
state: 'q2'
|
||||
},
|
||||
Button{
|
||||
text: 'q3'
|
||||
state: 'q3'
|
||||
},
|
||||
Button{
|
||||
text: 'q4'
|
||||
state: 'q4'
|
||||
},
|
||||
]
|
||||
}
|
||||
'q2': Question{
|
||||
title: 'Question 2'
|
||||
description: 'This question 2 after question 1'
|
||||
buttons: [
|
||||
Button{
|
||||
text: 'Go end'
|
||||
state: 'end'
|
||||
},
|
||||
Button{
|
||||
text: 'Need end'
|
||||
state: 'end'
|
||||
},
|
||||
]
|
||||
}
|
||||
'q3': Question{
|
||||
title: 'Question 3'
|
||||
description: 'This question 3 after question 1'
|
||||
buttons: [
|
||||
Button{
|
||||
text: 'Want end'
|
||||
state: 'end'
|
||||
},
|
||||
Button{
|
||||
text: 'Go dota 2'
|
||||
state: 'end'
|
||||
},
|
||||
]
|
||||
}
|
||||
'q4': Question{
|
||||
title: 'Question 4'
|
||||
description: 'This question 4 after question 1'
|
||||
buttons: [
|
||||
Button{
|
||||
text: 'Run end'
|
||||
state: 'end'
|
||||
},
|
||||
Button{
|
||||
text: 'Swimming to end'
|
||||
state: 'end'
|
||||
},
|
||||
]
|
||||
}
|
||||
'start': Question{
|
||||
title: 'Start question'
|
||||
description: 'This first question, start'
|
||||
buttons: [
|
||||
Button{
|
||||
text: 'start'
|
||||
state: 'q1'
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
]
|
262
repository/repository.v
Normal file
262
repository/repository.v
Normal file
@ -0,0 +1,262 @@
|
||||
module repository
|
||||
|
||||
import models
|
||||
import db.pg
|
||||
|
||||
// todo дообработать ошибки сейчас что то не возвращаются они
|
||||
|
||||
pub struct Repo {
|
||||
pub mut:
|
||||
db map[string]string
|
||||
pg_db pg.DB
|
||||
data_set_map map[string]models.Question
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) connect_to_db(cfg pg.Config) ! {
|
||||
r.pg_db = pg.connect(cfg)!
|
||||
println('Connected to the database successfully.')
|
||||
}
|
||||
|
||||
|
||||
pub fn (mut r Repo)save_state(chat_id string,state string) {
|
||||
r.db[chat_id]=state
|
||||
println(r.db)
|
||||
}
|
||||
|
||||
// pub fn (mut r Repo)get_state(chat_id string)!string{
|
||||
// if chat_id in r.db {
|
||||
// return r.db[chat_id]
|
||||
// }
|
||||
// return error('empty key')
|
||||
// }
|
||||
|
||||
pub fn (mut r Repo)get_next_que_data(state string)!models.Question{
|
||||
if state in r.data_set_map {
|
||||
return r.data_set_map[state]
|
||||
}
|
||||
return error('empty')
|
||||
}
|
||||
|
||||
// todo допилить работу со стейтами, надо понять как без telegram id стейт получать и обновлять
|
||||
// методы QuizRespondentState
|
||||
// вызывается при /start, создает запись
|
||||
pub fn (mut r Repo) create_state(state models.RespondentState) !i64 {
|
||||
state_id := sql r.pg_db {
|
||||
insert state into models.RespondentState
|
||||
} or {
|
||||
return error('Failed insert state: $err')
|
||||
}
|
||||
return state_id
|
||||
}
|
||||
|
||||
// вызывать при любом другом, кроме вопроса с типом result
|
||||
pub fn (mut r Repo) update_state(id i64, new_state i64) ! {
|
||||
sql r.pg_db {
|
||||
update models.RespondentState set state = new_state where id == id
|
||||
} or {
|
||||
return error('Failed update state: $err')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) get_state(state_id i64)!models.RespondentState{
|
||||
result := sql r.pg_db {
|
||||
select from models.RespondentState where id == state_id
|
||||
}or{
|
||||
return error('error getting state from db')
|
||||
}
|
||||
|
||||
if result.len == 0{
|
||||
return error('state not found')
|
||||
}
|
||||
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// выставляется при типе вопроса result = true
|
||||
pub fn (mut r Repo) finish_state(id i64) ! {
|
||||
sql r.pg_db {
|
||||
update models.RespondentState set finish = true where id == id
|
||||
} or {
|
||||
return error('Failed finish state: $err')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) get_state_by_tg_quiz_id(tg_id int,quiz_id i64)!models.RespondentState {
|
||||
result := sql r.pg_db {
|
||||
select from models.RespondentState where quiz_id == quiz_id && telegram_id == tg_id && finish == false
|
||||
}or{
|
||||
return error('error getting state from db')
|
||||
}
|
||||
|
||||
if result.len == 0{
|
||||
return error('state not found')
|
||||
}
|
||||
|
||||
return result[0]
|
||||
}
|
||||
|
||||
pub fn (mut r Repo)get_state_by_tg_quiz_id_finish_true(tg_id int,quiz_id i64)!int {
|
||||
result := sql r.pg_db {
|
||||
select from models.RespondentState where quiz_id == quiz_id && telegram_id == tg_id && finish == true
|
||||
}or{
|
||||
return error('error getting state from db')
|
||||
}
|
||||
|
||||
if result.len == 0{
|
||||
return 0
|
||||
}
|
||||
|
||||
return result.len
|
||||
}
|
||||
|
||||
// методы с question
|
||||
// получение вопроса по id
|
||||
pub fn (mut r Repo) get_question_by_id(id i64) !models.QuizQuestion {
|
||||
question := sql r.pg_db {
|
||||
select from models.QuizQuestion where id == id && deleted == false
|
||||
} or {
|
||||
return error('Question not found')
|
||||
}
|
||||
|
||||
if question.len==0{
|
||||
return error('Question not found')
|
||||
}
|
||||
|
||||
return question[0]
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) get_questions_by_quiz_id(quiz_id i64) ![]models.QuizQuestion {
|
||||
question := sql r.pg_db {
|
||||
select from models.QuizQuestion where quiz_id == quiz_id && deleted == false order by page
|
||||
} or {
|
||||
return error('question not found for quiz_id')
|
||||
}
|
||||
|
||||
if question.len==0{
|
||||
return error("not found")
|
||||
}
|
||||
|
||||
return question
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) get_all_results(id i64)![]models.QuizQuestion {
|
||||
result := sql r.pg_db {
|
||||
select from models.QuizQuestion where quiz_id == id && question_type == models.question_type_result.str() && deleted ==false
|
||||
} or {
|
||||
return error('question with type result not found for quiz_id')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// методы Quiz
|
||||
|
||||
pub fn (mut r Repo) get_quiz(quiz_id i64)!models.Quiz {
|
||||
result := sql r.pg_db{
|
||||
select from models.Quiz where id == quiz_id && deleted == false && archived == false
|
||||
}or{
|
||||
return error('QUIZ not found')
|
||||
}
|
||||
|
||||
if result.len == 0{
|
||||
return error('QUIZ not found')
|
||||
}
|
||||
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// методы TelegramIntegration
|
||||
// получение интеграции по id
|
||||
pub fn (mut r Repo) get_integration_by_id(id i64) !models.TelegramIntegration {
|
||||
integration := sql r.pg_db {
|
||||
select from models.TelegramIntegration where id == id && deleted == false limit 1
|
||||
} or {
|
||||
return error('Integration not found')
|
||||
}
|
||||
|
||||
if integration.len == 0 {
|
||||
return error('Integration not found')
|
||||
}
|
||||
|
||||
return integration[0]
|
||||
}
|
||||
|
||||
// создает запись о интеграции
|
||||
pub fn (mut r Repo) create_integration(integration models.TelegramIntegration) !int {
|
||||
integration_id := sql r.pg_db {
|
||||
insert integration into models.TelegramIntegration
|
||||
} or {
|
||||
return error('Failed create integration: $err')
|
||||
}
|
||||
return integration_id
|
||||
}
|
||||
|
||||
// обновляет токен бота
|
||||
pub fn (mut r Repo) update_bot_token(id i64, new_token string) ! {
|
||||
sql r.pg_db {
|
||||
update models.TelegramIntegration set bot_token = new_token where id == id && deleted == false
|
||||
} or {
|
||||
return error('Failed to update bot token: $err')
|
||||
}
|
||||
}
|
||||
|
||||
// обновляет поля ниже токена
|
||||
pub fn (mut r Repo) update_integration_fields(id i64, integration models.TelegramIntegration) ! {
|
||||
sql r.pg_db {
|
||||
update models.TelegramIntegration set repeatable = integration.repeatable, bot_name = integration.bot_name, deleted = integration.deleted where id == id && deleted == false
|
||||
} or {
|
||||
return error('Failed update integration fields: $err')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut r Repo)soft_delete_integration(quiz_id i64)!{
|
||||
sql r.pg_db{
|
||||
update models.TelegramIntegration set deleted = true where quiz_id == quiz_id
|
||||
}or{
|
||||
return error('Failed soft delete integration: $err')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) update_bot_name(bot_id i64,name string)!{
|
||||
sql r.pg_db {
|
||||
update models.TelegramIntegration set bot_name = name where id == bot_id
|
||||
}or{
|
||||
return error('Failed update bot name: $err')
|
||||
}
|
||||
}
|
||||
|
||||
// методы telegram_user_quiz_result
|
||||
pub fn (mut r Repo) create_record_user_result(data models.TelegramUserQuizResult) ! {
|
||||
sql r.pg_db {
|
||||
insert data into models.TelegramUserQuizResult
|
||||
} or {
|
||||
return error('Failed create record user result: $err')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut r Repo) update_record_user_result(data models.TelegramUserQuizResult)!{
|
||||
sql r.pg_db {
|
||||
update models.TelegramUserQuizResult set current_field = data.current_field,user_answer = data.user_answer, state = data.state,iter = data.iter where session == data.session
|
||||
}or{
|
||||
return error('Failed update bot name: $err')
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (r Repo) get_record_user_result(ses string)!models.TelegramUserQuizResult {
|
||||
result := sql r.pg_db{
|
||||
select from models.TelegramUserQuizResult where session == ses && state == models.user_result_tg_status_in_progress.str()
|
||||
}or{
|
||||
return error('record user result not found')
|
||||
}
|
||||
|
||||
if result.len == 0{
|
||||
return error('record user result not found')
|
||||
}
|
||||
|
||||
return result[0]
|
||||
}
|
||||
|
||||
// закрываем соединение с бд
|
||||
pub fn (mut r Repo)stop_db(){
|
||||
r.pg_db.close()
|
||||
}
|
86
repository/repository_test.v
Normal file
86
repository/repository_test.v
Normal file
@ -0,0 +1,86 @@
|
||||
module repository
|
||||
|
||||
import db.pg
|
||||
import models
|
||||
|
||||
//v -enable-globals -cc gcc -cflags "-IC:/PostgreSQL/16/include -LC:/PostgreSQL/16/lib" run repository/repository_test.v
|
||||
|
||||
fn test_init(){
|
||||
mut repo := Repo{}
|
||||
repo.connect_to_db(pg.Config{
|
||||
host: 'localhost'
|
||||
port: 35432
|
||||
user: 'squiz'
|
||||
password: 'Redalert2'
|
||||
dbname: 'squiz'
|
||||
})or {
|
||||
eprintln("err conect to db")
|
||||
}
|
||||
|
||||
state := models.RespondentState{
|
||||
state: 'initial'
|
||||
finish: false
|
||||
quiz_id: 1,
|
||||
telegram_id: 1,
|
||||
}
|
||||
id_create_state := repo.create_state(state)or {
|
||||
eprintln("Failed to create state: $err")
|
||||
return
|
||||
}
|
||||
|
||||
repo.update_state(id_create_state, 'updated_state')or{
|
||||
eprintln("Failed to update state: $err")
|
||||
return
|
||||
}
|
||||
|
||||
repo.finish_state(id_create_state) or{
|
||||
eprintln("Failed to finish state: $err")
|
||||
return
|
||||
}
|
||||
|
||||
question_result := repo.get_question_by_id(2721) or {
|
||||
eprintln("Failed to get question by id: $err")
|
||||
return
|
||||
}
|
||||
|
||||
println(question_result)
|
||||
|
||||
integration := models.TelegramIntegration{
|
||||
account_id: 'account1'
|
||||
quiz_id: 1
|
||||
bot_token: 'token'
|
||||
repeatable: true
|
||||
bot_name: 'integration_name'
|
||||
deleted: false
|
||||
}
|
||||
id_create_integration := repo.create_integration(integration)or {
|
||||
eprintln("Failed to create integration: $err")
|
||||
return
|
||||
}
|
||||
|
||||
println("iddddddddddddddd create_integration ${id_create_integration}")
|
||||
|
||||
integration_result := repo.get_integration_by_id(id_create_integration)or {
|
||||
eprintln("Failed to get integration by id: $err")
|
||||
return
|
||||
}
|
||||
println(integration_result)
|
||||
|
||||
repo.update_bot_token(id_create_integration, 'new_token')or {
|
||||
eprintln("Failed to update bot token: $err")
|
||||
return
|
||||
}
|
||||
|
||||
updated_integration := models.TelegramIntegration{
|
||||
account_id: 'account1'
|
||||
quiz_id: 1
|
||||
bot_token: 'old_token'
|
||||
repeatable: false
|
||||
bot_name: 'new_name'
|
||||
deleted: false
|
||||
}
|
||||
repo.update_integration_fields(id_create_integration, updated_integration)or {
|
||||
eprintln("Failed to update integration fields: $err")
|
||||
return
|
||||
}
|
||||
}
|
23
state_manager/stateManager.v
Normal file
23
state_manager/stateManager.v
Normal file
@ -0,0 +1,23 @@
|
||||
module state_manager
|
||||
|
||||
|
||||
// pub struct StateManager {
|
||||
// pub mut:
|
||||
// questions map[string]models.Question
|
||||
// state string
|
||||
// client &client.Client
|
||||
// }
|
||||
//
|
||||
// pub fn (mut sm StateManager) listener(state string) {
|
||||
// sm.state = state
|
||||
// sm.send_question()
|
||||
// }
|
||||
//
|
||||
// pub fn (mut sm StateManager) send_question() {
|
||||
// mut question := sm.questions[sm.state]
|
||||
// mut message := unsafe { strconv.v_sprintf('%s\n%s', question.title, question.description)}
|
||||
// for _, button in question.buttons {
|
||||
// message += unsafe { strconv.v_sprintf('\n%s', button.text)}
|
||||
// }
|
||||
// sm.client.send_message(message)
|
||||
// }
|
1621
tg_handle/tg.v
Normal file
1621
tg_handle/tg.v
Normal file
File diff suppressed because it is too large
Load Diff
21
tg_handle/tg_test.v
Normal file
21
tg_handle/tg_test.v
Normal file
@ -0,0 +1,21 @@
|
||||
module tg_handle
|
||||
|
||||
import json
|
||||
import models
|
||||
|
||||
fn test_find_next_question_id() {
|
||||
json_content := '{"id":1,"hint":{"text":" ","video":" "},"rule":{"children":[2,3],"main":[{"next":2,"or":"true","rules":[{"question":1,"answers":["AF2rL-7HqZ83Ks6VgrUra"]}]}],"parentId":"root","default":3},"back":" ","originalBack":" ","autofill":false,"own":false,"multi":false,"xy":"1:1","innerNameCheck":false,"innerName":" ","large":false,"format":"carousel","required":false,"variants":[{"id":"hGS4EzG4GppXPoRmKgaDz","answer":"first img","extendedText":" ","originalImageUrl":" ","hints":" "},{"id":"AF2rL-7HqZ83Ks6VgrUra","answer":"second img","extendedText":" ","hints":" ","originalImageUrl":" "},{"id":"aOcmbnORWlJt2Gm7bnZd7","answer":"third img","extendedText":" ","hints":" ","originalImageUrl":" "}],"largeCheck":false}'
|
||||
|
||||
question := json.decode(models.QuizQuestionImages, json_content) or {
|
||||
assert false
|
||||
return
|
||||
}
|
||||
|
||||
mut callback_data := "AF2rL-7HqZ83Ks6VgrUra"
|
||||
mut next_question_id := find_next_question_id(question.rule, callback_data)
|
||||
assert next_question_id == 1
|
||||
|
||||
callback_data = "some_wrong_answer"
|
||||
next_question_id = find_next_question_id(question.rule, callback_data)
|
||||
assert next_question_id == 2
|
||||
}
|
37
tools/ticker.v
Normal file
37
tools/ticker.v
Normal file
@ -0,0 +1,37 @@
|
||||
module tools
|
||||
|
||||
import time
|
||||
|
||||
pub struct Ticker {
|
||||
mut:
|
||||
last_sent time.Time
|
||||
interval time.Duration
|
||||
stopped bool
|
||||
pub:
|
||||
c chan time.Time
|
||||
}
|
||||
|
||||
pub fn new_ticker(interval time.Duration) &Ticker {
|
||||
mut t := &Ticker{
|
||||
last_sent: time.now()
|
||||
interval: interval
|
||||
c: chan time.Time{}
|
||||
stopped: false
|
||||
}
|
||||
spawn t.tick()
|
||||
return t
|
||||
}
|
||||
|
||||
fn (mut t Ticker) tick() {
|
||||
for !t.stopped {
|
||||
now := time.now()
|
||||
if now - t.last_sent >= t.interval {
|
||||
t.last_sent = now
|
||||
t.c <- now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn (mut t Ticker) stop() {
|
||||
t.stopped = true
|
||||
}
|
7184
treedata.json
Normal file
7184
treedata.json
Normal file
File diff suppressed because it is too large
Load Diff
54
utils/middleware.v
Normal file
54
utils/middleware.v
Normal file
@ -0,0 +1,54 @@
|
||||
module utils
|
||||
import net.http
|
||||
import encoding.base64
|
||||
import veb
|
||||
import json
|
||||
|
||||
// todo надо rs 256 реализовать
|
||||
pub fn jwt_auth(mut ctx veb.Context) (string) {
|
||||
auth_header := ctx.get_header(http.CommonHeader.authorization) or {
|
||||
println('Authorization missing ')
|
||||
ctx.res.set_status(http.Status.unauthorized)
|
||||
return ""
|
||||
}
|
||||
|
||||
token_string := auth_header.trim_string_left("Bearer ")
|
||||
if token_string == auth_header {
|
||||
ctx.res.set_status(http.Status.unauthorized)
|
||||
return ""
|
||||
}
|
||||
|
||||
public_key := "MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm69
|
||||
80fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6B
|
||||
dA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y
|
||||
+3GyaOY536H47qyXAgMBAAE="
|
||||
|
||||
if public_key == ""{
|
||||
ctx.res.set_status(http.Status.internal_server_error)
|
||||
return ""
|
||||
}
|
||||
|
||||
parts := token_string.split('.')
|
||||
if parts.len != 3 {
|
||||
ctx.res.set_status(http.Status.unauthorized)
|
||||
return ""
|
||||
}
|
||||
|
||||
header := base64.url_decode_str(parts[0])
|
||||
payload := base64.url_decode_str(parts[1])
|
||||
signature := [parts[2]]
|
||||
|
||||
println(header)
|
||||
println(payload)
|
||||
println(signature)
|
||||
|
||||
data := json.decode(map[string]string,payload)or{
|
||||
ctx.res.set_status(http.Status.unauthorized)
|
||||
return ""
|
||||
}
|
||||
|
||||
id := data['id']
|
||||
println(id)
|
||||
|
||||
return id
|
||||
}
|
11
utils/session_generate.v
Normal file
11
utils/session_generate.v
Normal file
@ -0,0 +1,11 @@
|
||||
module utils
|
||||
|
||||
import crypto.md5
|
||||
import time
|
||||
|
||||
pub fn generate_signature(quizID string,respondent_id string) string {
|
||||
mut u := time.now().unix().str()
|
||||
u += quizID + respondent_id
|
||||
hash := md5.hexhash(u)
|
||||
return hash[..20]
|
||||
}
|
5
utils/session_generate_test.v
Normal file
5
utils/session_generate_test.v
Normal file
@ -0,0 +1,5 @@
|
||||
module utils
|
||||
|
||||
fn test_generate_signature(){
|
||||
println(generate_signature(100,20))
|
||||
}
|
Loading…
Reference in New Issue
Block a user