first adding
This commit is contained in:
parent
e9cfbac2f3
commit
751c74a087
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
squiz
|
||||
.idea/
|
||||
gen
|
||||
worker/worker
|
||||
storer/storer
|
||||
answerer/answerer
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/golang as build
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
ARG GITLAB_TOKEN
|
||||
RUN echo ${GITLAB_TOKEN}
|
||||
ENV GOPRIVATE=penahub.gitlab.yandexcloud.net/backend/penahub_common
|
||||
RUN git config --global url."https://buildToken:glpat-axA8ttckx3aPf_xd2Dym@penahub.gitlab.yandexcloud.net/".insteadOf "https://penahub.gitlab.yandexcloud.net/"
|
||||
RUN go mod download
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o wrkr ./worker/main.go
|
||||
|
||||
FROM penahub.gitlab.yandexcloud.net:5050/devops/dockerhub-backup/alpine as prod
|
||||
COPY --from=build /app/wrkr .
|
||||
ENV IS_PROD_LOG=false
|
||||
ENV IS_PROD=false
|
||||
ENV PG_CRED="host=postgres port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"
|
||||
CMD ["/wrkr"]
|
191
answerwc/mail/reminder.tmpl
Normal file
191
answerwc/mail/reminder.tmpl
Normal file
@ -0,0 +1,191 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Document</title>
|
||||
<style>
|
||||
/* Сброс стилей */
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p,
|
||||
div,
|
||||
img,
|
||||
button,
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f2f2f7;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
h1 {
|
||||
font-size: 25px !important;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
|
||||
.balance {
|
||||
font-size: 15px !important;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: 223px;
|
||||
height: 208px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: #f2f2f7; font-family: Arial, sans-serif">
|
||||
<table style="width: 100%; padding: 16px">
|
||||
<tr>
|
||||
<td>
|
||||
<img class="image" style="width: 103px; height: 40px" src="https://storage.yandexcloud.net/squizimages/logo-email-squiz.png" />
|
||||
</td>
|
||||
<td>
|
||||
<p style="text-align: end; color: #9a9aaf; font-size: 14px">Квиз для вашего бизнеса</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<h1
|
||||
style="
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 13px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-bottom: 16px;
|
||||
margin-top: 50px;
|
||||
"
|
||||
>
|
||||
Поступила новая заявка с квиза “{{ .QuizConfig.Theme }}”!
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<p class="balance" style="color: #4d4d4d; font-size: 20px; margin-bottom: 30px">
|
||||
Но у вас закончились средства на балансе :(
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="text-align: center; padding: 0">
|
||||
<img class="image" style="width: 100%; max-width: 440px; height: 280px; margin-bottom: 40px" src="https://storage.yandexcloud.net/squizimages/img_wallet.png" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<h1
|
||||
style="font-size: 25px; font-weight: 600; margin-bottom: 15px; width: 100%; margin: 0; margin-bottom: 13px"
|
||||
>
|
||||
Аккаунт
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="padding: 0">
|
||||
<table
|
||||
style="
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
margin-bottom: 40px;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Email
|
||||
</th>
|
||||
<td style="word-break: break-word">
|
||||
<p
|
||||
style="
|
||||
text-align: start;
|
||||
color: #7e2aea;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
{{ .QuizConfig.Reply }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<p class="balance" style="color: #9a9aaf; font-size: 20px; margin-bottom: 30px; text-align: center">
|
||||
Пополните баланс и посмотрите заявку в личном кабинете:
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%; text-align: center">
|
||||
<a
|
||||
style="
|
||||
max-width: 312px;
|
||||
color: #f2f3f7;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #7e2aea;
|
||||
background: #7e2aea;
|
||||
padding: 10px 43px;
|
||||
"
|
||||
>
|
||||
Посмотреть в личном кабинете
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="text-align: center; padding: 30px 0 0 0">
|
||||
<hr style="border-top: 2px solid rgba(126, 42, 234, 0.2); margin: 0 0 10px" />
|
||||
<a style="color: #7e2aea; font-size: 20px; font-style: normal; font-weight: 400; line-height: normal">
|
||||
quiz.pena.digital
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
537
answerwc/mail/to_client.tmpl
Normal file
537
answerwc/mail/to_client.tmpl
Normal file
@ -0,0 +1,537 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Document</title>
|
||||
<style>
|
||||
/* Сброс стилей */
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p,
|
||||
div,
|
||||
img,
|
||||
button,
|
||||
table,
|
||||
th,
|
||||
td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f2f2f7;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
h1 {
|
||||
font-size: 25px !important;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: #f2f2f7; font-family: Arial, sans-serif">
|
||||
<table style="width: 100%; padding: 16px">
|
||||
<tr>
|
||||
<td>
|
||||
<img class="image" style="width: 103px; height: 40px" src="https://storage.yandexcloud.net/squizimages/logo-email-squiz.png" />
|
||||
</td>
|
||||
<td>
|
||||
<p style="text-align: end; color: #9a9aaf; font-size: 14px">Квиз для вашего бизнеса</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<h1
|
||||
style="
|
||||
font-size: 30px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 13px;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
margin-bottom: 13px;
|
||||
margin-top: 50px;
|
||||
"
|
||||
>
|
||||
Поступила новая заявка с квиза “{{.QuizConfig.Theme}}”!
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<p style="color: #9a9aaf; font-size: 20px; margin-bottom: 50px">
|
||||
Время заявки: {{ .AnswerTime }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<a
|
||||
style="
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
color: #f2f3f7;
|
||||
text-align: center;
|
||||
font-size: 18px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #7e2aea;
|
||||
background: #7e2aea;
|
||||
padding: 10px 43px;
|
||||
max-height: 63px;
|
||||
margin-bottom: 50px;
|
||||
"
|
||||
>
|
||||
Посмотреть в личном кабинете
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<h1
|
||||
style="font-size: 25px; font-weight: 600; margin-bottom: 15px; width: 100%; margin: 0; margin-bottom: 13px"
|
||||
>
|
||||
Контакты
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="padding: 0">
|
||||
<table
|
||||
style="
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
margin-bottom: 30px;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Имя
|
||||
</th>
|
||||
<td>
|
||||
<p
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Name}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
{{ if .AnswerContent.Email }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Email
|
||||
</th>
|
||||
<td style="word-break: break-word">
|
||||
<p
|
||||
style="
|
||||
text-align: start;
|
||||
color: #7e2aea;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Email }}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Phone }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Телефон
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Phone }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
|
||||
{{ if .AnswerContent.Telegram }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Telegram
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Telegram }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Wechat }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Wechat
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Wechat }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Viber }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Viber
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Viber }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Vk }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Vk
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Vk }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Skype }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Skype
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Skype }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Whatsup }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Whatsup
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Whatsup }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Messenger }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Messenger
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Messenger }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ if .AnswerContent.Address }}
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
Адрес
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ .AnswerContent.Address }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ range $key, $value := .AnswerContent.Custom }}
|
||||
|
||||
<tr>
|
||||
<th
|
||||
style="
|
||||
text-align: start;
|
||||
color: #9a9aaf;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ $key }}
|
||||
</th>
|
||||
<td
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
"
|
||||
>
|
||||
{{ $value }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="height: 100%">
|
||||
<h1
|
||||
style="font-size: 25px; font-weight: 600; margin-bottom: 15px; width: 100%; margin: 0; margin-bottom: 13px"
|
||||
>
|
||||
Ответы
|
||||
</h1>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{{ range .AllAnswers }}
|
||||
{{ if index $.QuestionsMap .AnswerID }}
|
||||
<tr>
|
||||
<td colspan="2" style="padding: 0">
|
||||
<table
|
||||
style="
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
text-align: left;
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
margin-bottom: 15px;
|
||||
"
|
||||
>
|
||||
<tr>
|
||||
<th colspan="2">
|
||||
<p
|
||||
style="
|
||||
text-align: start;
|
||||
color: #4d4d4d;
|
||||
font-size: 20px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
margin-bottom: 10px;
|
||||
"
|
||||
>
|
||||
{{ index $.QuestionsMap .AnswerID }}
|
||||
</p>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="color: #9a9aaf; font-size: 20px; font-style: normal; font-weight: 400; line-height: normal">
|
||||
{{ .Content }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{end}}
|
||||
|
||||
<tr>
|
||||
<td colspan="2" style="text-align: center; padding: 0">
|
||||
<a style="color: #7e2aea; font-size: 20px; font-style: normal; font-weight: 400; line-height: normal">
|
||||
quiz.pena.digital
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
170
answerwc/respondent.go
Normal file
170
answerwc/respondent.go
Normal file
@ -0,0 +1,170 @@
|
||||
package answerwc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/themakers/hlog"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/mailclient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/wctools"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DepsRespWorker struct {
|
||||
Redis *redis.Client
|
||||
Dal *dal.DAL
|
||||
MailClient *mailclient.Client
|
||||
}
|
||||
|
||||
type RespWorker struct {
|
||||
deps DepsRespWorker
|
||||
logger hlog.Logger
|
||||
errChan chan<- error
|
||||
}
|
||||
|
||||
func NewRespWorker(deps DepsRespWorker, logger hlog.Logger, errChan chan<- error) *RespWorker {
|
||||
return &RespWorker{
|
||||
deps: deps,
|
||||
logger: logger,
|
||||
errChan: errChan,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RespWorker) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
w.processPendingAnswer(ctx)
|
||||
|
||||
case <-ctx.Done():
|
||||
w.logger.Module("To respondent worker terminated")
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RespWorker) processPendingAnswer(ctx context.Context) {
|
||||
keys, err := w.deps.Redis.Keys(ctx, "toRespondent:*").Result()
|
||||
if err != nil {
|
||||
w.reportError(err, "Error retrieving keys from Redis")
|
||||
return
|
||||
}
|
||||
|
||||
for _, key := range keys {
|
||||
func() {
|
||||
answerJSON, err := w.deps.Redis.GetDel(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return
|
||||
} else if err != nil {
|
||||
w.reportError(err, "Error getting and deleting data from Redis")
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
w.reportError(nil, fmt.Sprintf("recovering from panic or error setting redis value %v", r))
|
||||
_ = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err()
|
||||
}
|
||||
}()
|
||||
|
||||
var answer model.Answer
|
||||
err = json.Unmarshal([]byte(answerJSON), &answer)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error unmarshalling answer")
|
||||
return
|
||||
}
|
||||
|
||||
answerContent, err := wctools.ProcessAnswer(answer.Content)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error processing answer content")
|
||||
return
|
||||
}
|
||||
|
||||
quizConfig, accountId, err := w.deps.Dal.QuizRepo.GetQuizConfig(ctx, answer.QuizId)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting quiz config")
|
||||
return
|
||||
}
|
||||
|
||||
quiz, err := w.deps.Dal.QuizRepo.GetQuizById(ctx, accountId, answer.QuizId)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting quiz")
|
||||
return
|
||||
}
|
||||
|
||||
quizConfig.Mailing.Reply = quiz.Name
|
||||
|
||||
if quizConfig.Mailing.Theme == "" {
|
||||
quizConfig.Mailing.Theme = quiz.Name
|
||||
}
|
||||
|
||||
allAnswers, err := w.deps.Dal.AnswerRepo.GetAllAnswersByQuizID(ctx, answer.Session)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting all answers by quizID")
|
||||
return
|
||||
}
|
||||
|
||||
questionsMap, err := w.deps.Dal.QuestionRepo.GetMapQuestions(ctx, allAnswers)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting questionsMap")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("ATATATA", questionsMap, allAnswers)
|
||||
|
||||
err = w.processMessageToSMTP(quizConfig, questionsMap, allAnswers, answerContent, answer.CreatedAt)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error sending message to SMTP")
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *RespWorker) processMessageToSMTP(quizConfig model.QuizConfig, questionsMap map[uint64]string,
|
||||
allAnswers []model.ResultAnswer, answerContent model.ResultContent, answerTime time.Time) error {
|
||||
theme := quizConfig.Mailing.Theme
|
||||
quizConfig.Mailing.Theme = quizConfig.Mailing.Reply
|
||||
|
||||
data := mailclient.EmailTemplateData{
|
||||
QuizConfig: quizConfig.Mailing,
|
||||
AnswerContent: answerContent,
|
||||
AllAnswers: allAnswers,
|
||||
QuestionsMap: questionsMap,
|
||||
}
|
||||
|
||||
dayOfWeek := wctools.DaysOfWeek[answerTime.Format("Monday")]
|
||||
monthOfYear := wctools.MonthsOfYear[answerTime.Format("January")]
|
||||
|
||||
formattedTime := fmt.Sprintf("%s, %d %s %d г., %02d:%02d (UTC%s)",
|
||||
dayOfWeek,
|
||||
answerTime.Day(),
|
||||
monthOfYear,
|
||||
answerTime.Year(),
|
||||
answerTime.Hour(),
|
||||
answerTime.Minute(),
|
||||
answerTime.Format("-07:00"),
|
||||
)
|
||||
|
||||
data.AnswerTime = formattedTime
|
||||
|
||||
err := w.deps.MailClient.SendMailWithAttachment(answerContent.Email, theme, toClientTemplate, data, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *RespWorker) reportError(err error, message string) {
|
||||
if err != nil {
|
||||
fmt.Println(message + ": " + err.Error())
|
||||
w.errChan <- err
|
||||
}
|
||||
}
|
373
answerwc/to_client.go
Normal file
373
answerwc/to_client.go
Normal file
@ -0,0 +1,373 @@
|
||||
package answerwc
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/themakers/hlog"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/customer"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/mailclient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/wctools"
|
||||
"time"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
)
|
||||
|
||||
type DepsSendToClient struct {
|
||||
Redis *redis.Client
|
||||
Dal *dal.DAL
|
||||
MailClient *mailclient.Client
|
||||
CustomerService customer.CustomerServiceClient
|
||||
}
|
||||
|
||||
type SendToClient struct {
|
||||
deps DepsSendToClient
|
||||
logger hlog.Logger
|
||||
errChan chan<- error
|
||||
}
|
||||
|
||||
type PendingTasks struct {
|
||||
Count int64
|
||||
QuizConfig model.QuizConfig
|
||||
}
|
||||
|
||||
//go:embed mail/to_client.tmpl
|
||||
var toClientTemplate string
|
||||
|
||||
//go:embed mail/reminder.tmpl
|
||||
var reminderTemplate string
|
||||
|
||||
func NewSendToClient(deps DepsSendToClient, logger hlog.Logger, errChan chan<- error) *SendToClient {
|
||||
return &SendToClient{
|
||||
deps: deps,
|
||||
logger: logger,
|
||||
errChan: errChan,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendToClient) Start(ctx context.Context) {
|
||||
answerTicker := time.NewTicker(30 * time.Second)
|
||||
defer answerTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-answerTicker.C:
|
||||
w.processPendingAnswer(ctx)
|
||||
|
||||
case <-ctx.Done():
|
||||
w.logger.Module("To client worker terminated")
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendToClient) processPendingAnswer(ctx context.Context) {
|
||||
pendingAnswers, err := w.deps.Redis.Keys(ctx, "answer:*").Result()
|
||||
if err != nil {
|
||||
fmt.Println("Error getting keys from redis")
|
||||
w.errChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("ANS")
|
||||
|
||||
for _, key := range pendingAnswers {
|
||||
func() {
|
||||
fmt.Println("ANS1", key)
|
||||
answerJSON, err := w.deps.Redis.GetDel(ctx, key).Result()
|
||||
if err == redis.Nil {
|
||||
return
|
||||
} else if err != nil {
|
||||
w.reportError(err, "Error getting and deleting data from redis")
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
w.reportError(nil, fmt.Sprintf("recovering from panic or error setting redis value %v", r))
|
||||
fmt.Println("ANS1ERRR", r)
|
||||
_ = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err()
|
||||
}
|
||||
}()
|
||||
|
||||
var answer model.Answer
|
||||
err = json.Unmarshal([]byte(answerJSON), &answer)
|
||||
fmt.Println("ANS2", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error unmarshal answer")
|
||||
return
|
||||
}
|
||||
|
||||
answerContent, err := wctools.ProcessAnswer(answer.Content)
|
||||
fmt.Println("ANS3", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error unmarshal answer content")
|
||||
return
|
||||
}
|
||||
|
||||
allAnswers, err := w.deps.Dal.AnswerRepo.GetAllAnswersByQuizID(ctx, answer.Session)
|
||||
fmt.Println("ANS4", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting all answers by quizID")
|
||||
return
|
||||
}
|
||||
|
||||
questionsMap, err := w.deps.Dal.QuestionRepo.GetMapQuestions(ctx, allAnswers)
|
||||
fmt.Println("ANS5", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting questionsMap")
|
||||
return
|
||||
}
|
||||
|
||||
if answer.QuizId == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
quizConfig, accountId, err := w.deps.Dal.QuizRepo.GetQuizConfig(ctx, answer.QuizId)
|
||||
fmt.Println("ANS6", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting quiz config")
|
||||
return
|
||||
}
|
||||
|
||||
quiz, err := w.deps.Dal.QuizRepo.GetQuizById(ctx, accountId, answer.QuizId)
|
||||
fmt.Println("ANS60", err, accountId, answer.QuizId)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting quiz")
|
||||
return
|
||||
}
|
||||
|
||||
quizConfig.Mailing.Reply = quiz.Name
|
||||
|
||||
if quizConfig.Mailing.Theme == "" {
|
||||
quizConfig.Mailing.Theme = quiz.Name
|
||||
}
|
||||
|
||||
account, privileges, err := w.deps.Dal.AccountRepo.GetAccAndPrivilegeByEmail(ctx, accountId)
|
||||
fmt.Println("ANS7", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error getting account and privileges by email")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := w.processAnswerWithPrivileges(ctx, quiz.Name, quizConfig, questionsMap, privileges, account, allAnswers, answerContent, answer.CreatedAt)
|
||||
fmt.Println("ANS8", err, result, privileges)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error process answer with privileges")
|
||||
return
|
||||
}
|
||||
if !result {
|
||||
err = w.deps.Redis.Set(ctx, fmt.Sprintf("%s:%s", account.ID, key), answerJSON, 0).Err()
|
||||
if err != nil {
|
||||
w.reportError(err, "Error setting redis value")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendToClient) processAnswerWithPrivileges(ctx context.Context, quizName string, quizConfig model.QuizConfig,
|
||||
questionsMap map[uint64]string, privileges []model.ShortPrivilege, account model.Account, allAnswers []model.ResultAnswer,
|
||||
answerContent model.ResultContent, answerTime time.Time) (bool, error) {
|
||||
|
||||
err := w.notificationCustomer(account, privileges)
|
||||
fmt.Println("ANS81", err)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if wctools.HasUnlimitedPrivilege(privileges) {
|
||||
err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
privilege := wctools.HasQuizCntPrivilege(privileges)
|
||||
if privilege != nil {
|
||||
err := w.ProcessMessageToClient(quizConfig, questionsMap, account, allAnswers, answerContent, answerTime)
|
||||
fmt.Println("PMC", err)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
privilege.Amount--
|
||||
err = w.deps.Dal.AccountRepo.UpdatePrivilegeAmount(ctx, privilege.ID, privilege.Amount)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
} else {
|
||||
w.checkAndSendTaskReminders(ctx, sendTaskRemindersDeps{
|
||||
email: account.Email,
|
||||
theme: quizName,
|
||||
config: model.QuizConfig{
|
||||
Mailing: model.ResultInfo{
|
||||
When: "email",
|
||||
Theme: fmt.Sprintf("не удалось отправить заявку по опросу\"%s\"", quizName),
|
||||
Reply: "noreply@pena.digital",
|
||||
ReplName: "Reminder",
|
||||
},
|
||||
},
|
||||
})
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendToClient) recordPendingTasks(ctx context.Context, Email string, quizConfig model.QuizConfig) error {
|
||||
key := fmt.Sprintf("pending_tasks:%s", Email)
|
||||
|
||||
var pendingTasks PendingTasks
|
||||
val, err := w.deps.Redis.HGet(ctx, key, "data").Result()
|
||||
if err == nil {
|
||||
err := json.Unmarshal([]byte(val), &pendingTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pendingTasks.Count++
|
||||
} else {
|
||||
pendingTasks = PendingTasks{
|
||||
Count: 1,
|
||||
QuizConfig: quizConfig,
|
||||
}
|
||||
}
|
||||
|
||||
pendingTasksJSON, err := json.Marshal(pendingTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = w.deps.Redis.HSet(ctx, key, "data", string(pendingTasksJSON)).Err()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type sendTaskRemindersDeps struct {
|
||||
email, theme string
|
||||
config model.QuizConfig
|
||||
}
|
||||
|
||||
func (w *SendToClient) checkAndSendTaskReminders(ctx context.Context, deps sendTaskRemindersDeps) {
|
||||
err := w.processReminderToClient(deps.email, deps.config)
|
||||
fmt.Println("PMC1", err)
|
||||
if err != nil {
|
||||
w.reportError(err, "Error sending tasks reminder email")
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SendToClient) notificationCustomer(account model.Account, privileges []model.ShortPrivilege) error {
|
||||
for _, privilege := range privileges {
|
||||
fmt.Println("NOTIFIC", privilege.PrivilegeID, privilege.Amount, !wctools.IsPrivilegeExpired(privilege))
|
||||
if privilege.PrivilegeID == "quizUnlimTime" && !wctools.IsPrivilegeExpired(privilege) {
|
||||
rawDetail, err := wctools.ToJSON(privilege)
|
||||
historyData := &customer.History{
|
||||
UserID: account.UserID,
|
||||
Comment: fmt.Sprintf("Привилегия %s просрочена", privilege.PrivilegeID),
|
||||
Key: "privilege_expired",
|
||||
RawDetails: rawDetail,
|
||||
}
|
||||
|
||||
_, err = w.deps.CustomerService.InsertHistory(context.Background(), historyData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if privilege.PrivilegeID == "quizCnt" && privilege.Amount == 0 {
|
||||
rawDetail, err := wctools.ToJSON(privilege)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
historyData := &customer.History{
|
||||
UserID: account.UserID,
|
||||
Comment: fmt.Sprintf("У привилегии %s истек amount", privilege.PrivilegeID),
|
||||
Key: "privilege_expired",
|
||||
RawDetails: rawDetail,
|
||||
}
|
||||
|
||||
_, err = w.deps.CustomerService.InsertHistory(context.Background(), historyData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// сделал экспортируемым для теста
|
||||
func (w *SendToClient) ProcessMessageToClient(quizConfig model.QuizConfig, questionsMap map[uint64]string, account model.Account, allAnswers []model.ResultAnswer, answerContent model.ResultContent, answerTime time.Time) error {
|
||||
theme := quizConfig.Mailing.Theme
|
||||
quizConfig.Mailing.Theme = quizConfig.Mailing.Reply
|
||||
|
||||
data := mailclient.EmailTemplateData{
|
||||
QuizConfig: quizConfig.Mailing,
|
||||
AnswerContent: answerContent,
|
||||
AllAnswers: allAnswers,
|
||||
QuestionsMap: questionsMap,
|
||||
}
|
||||
|
||||
dayOfWeek := wctools.DaysOfWeek[answerTime.Format("Monday")]
|
||||
monthOfYear := wctools.MonthsOfYear[answerTime.Format("January")]
|
||||
|
||||
formattedTime := fmt.Sprintf("%s, %d %s %d г., %02d:%02d (UTC%s)",
|
||||
dayOfWeek,
|
||||
answerTime.Day(),
|
||||
monthOfYear,
|
||||
answerTime.Year(),
|
||||
answerTime.Hour(),
|
||||
answerTime.Minute(),
|
||||
answerTime.Format("-07:00"),
|
||||
)
|
||||
|
||||
data.AnswerTime = formattedTime
|
||||
|
||||
fmt.Println("SUBJECT", theme, account.Email)
|
||||
|
||||
err := w.deps.MailClient.SendMailWithAttachment(account.Email, theme, toClientTemplate, data, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *SendToClient) processReminderToClient(email string, quizConfig model.QuizConfig) error {
|
||||
|
||||
data := mailclient.EmailTemplateData{
|
||||
QuizConfig: model.ResultInfo{
|
||||
When: quizConfig.Mailing.When,
|
||||
Theme: quizConfig.Mailing.Theme,
|
||||
Reply: email,
|
||||
ReplName: quizConfig.Mailing.ReplName,
|
||||
},
|
||||
AnswerContent: model.ResultContent{},
|
||||
AllAnswers: []model.ResultAnswer{},
|
||||
QuestionsMap: nil,
|
||||
}
|
||||
|
||||
fmt.Println("PRTC", data, email, quizConfig)
|
||||
|
||||
err := w.deps.MailClient.SendMailWithAttachment(email, quizConfig.Mailing.Theme, reminderTemplate, data, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *SendToClient) reportError(err error, message string) {
|
||||
if err != nil {
|
||||
fmt.Println(message + ": " + err.Error())
|
||||
w.errChan <- err
|
||||
}
|
||||
}
|
209
app/app.go
Normal file
209
app/app.go
Normal file
@ -0,0 +1,209 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/skeris/appInit"
|
||||
"github.com/themakers/hlog"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/grpc"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/answerwc"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/customer"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/clients/mailclient"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/privilegewc"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/workers/shortstat"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/workers/timeout"
|
||||
"time"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
logger *zap.Logger
|
||||
err chan error
|
||||
}
|
||||
|
||||
func (a App) GetLogger() *zap.Logger {
|
||||
return a.logger
|
||||
}
|
||||
|
||||
func (a App) GetErr() chan error {
|
||||
return a.err
|
||||
}
|
||||
|
||||
var (
|
||||
errInvalidOptions = errors.New("invalid options")
|
||||
)
|
||||
|
||||
var zapOptions = []zap.Option{
|
||||
zap.AddCaller(),
|
||||
zap.AddCallerSkip(2),
|
||||
zap.AddStacktrace(zap.ErrorLevel),
|
||||
}
|
||||
|
||||
var _ appInit.CommonApp = (*App)(nil)
|
||||
|
||||
type Options struct {
|
||||
ServiceName string `env:"SERVICE_NAME" default:"squiz"`
|
||||
KafkaBroker string `env:"KAFKA_BROKER"`
|
||||
KafkaTopic string `env:"KAFKA_TOPIC"`
|
||||
PrivilegeID string `env:"QUIZ_ID"`
|
||||
Amount uint64 `env:"AMOUNT"`
|
||||
UnlimID string `env:"UNLIM_ID"`
|
||||
LoggerProdMode bool `env:"IS_PROD_LOG" default:"false"`
|
||||
IsProd bool `env:"IS_PROD" default:"false"`
|
||||
PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
|
||||
RedisHost string `env:"REDIS_HOST"`
|
||||
RedisPassword string `env:"REDIS_PASSWORD"`
|
||||
RedisDB uint64 `env:"REDIS_DB"`
|
||||
SmtpHost string `env:"SMTP_HOST"`
|
||||
SmtpPort string `env:"SMTP_PORT"`
|
||||
SmtpSender string `env:"SMTP_SENDER"`
|
||||
SmtpIdentity string `env:"SMTP_IDENTITY"`
|
||||
SmtpUsername string `env:"SMTP_USERNAME"`
|
||||
SmtpPassword string `env:"SMTP_PASSWORD"`
|
||||
SmtpApiKey string `env:"SMTP_API_KEY"`
|
||||
CustomerServiceAddress string `env:"CUSTOMER_SERVICE_ADDRESS"`
|
||||
}
|
||||
|
||||
func New(ctx context.Context, opts interface{}, ver appInit.Version) (appInit.CommonApp, error) {
|
||||
var (
|
||||
err, workerErr error
|
||||
zapLogger *zap.Logger
|
||||
errChan = make(chan error)
|
||||
options Options
|
||||
ok bool
|
||||
)
|
||||
|
||||
if options, ok = opts.(Options); !ok {
|
||||
return App{}, errInvalidOptions
|
||||
}
|
||||
|
||||
if options.LoggerProdMode {
|
||||
zapLogger, err = zap.NewProduction(zapOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
zapLogger, err = zap.NewDevelopment(zapOptions...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
zapLogger = zapLogger.With(
|
||||
zap.String("SvcCommit", ver.Commit),
|
||||
zap.String("SvcVersion", ver.Release),
|
||||
zap.String("SvcBuildTime", ver.BuildTime),
|
||||
)
|
||||
|
||||
logger := hlog.New(zapLogger)
|
||||
logger.Emit(InfoSvcStarted{})
|
||||
zapLogger.Info("config", zap.Any("options", options))
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case err := <-errChan:
|
||||
zapLogger.Error("Ошибка при работе воркера", zap.Error(err))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
//init redis
|
||||
redisClient := redis.NewClient(&redis.Options{
|
||||
Addr: options.RedisHost,
|
||||
Password: options.RedisPassword,
|
||||
DB: int(options.RedisDB),
|
||||
})
|
||||
|
||||
smtpData := mailclient.ClientDeps{
|
||||
Host: options.SmtpHost,
|
||||
Port: options.SmtpPort,
|
||||
Sender: options.SmtpSender,
|
||||
ApiKey: options.SmtpApiKey,
|
||||
Auth: &mailclient.PlainAuth{
|
||||
Identity: options.SmtpIdentity,
|
||||
Username: options.SmtpUsername,
|
||||
Password: options.SmtpPassword,
|
||||
},
|
||||
FiberClient: &fiber.Client{},
|
||||
Logger: logger,
|
||||
}
|
||||
|
||||
mailClient := mailclient.NewClient(smtpData)
|
||||
|
||||
customerServiceConn, err := grpc.Dial(options.CustomerServiceAddress, grpc.WithInsecure())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
customerServiceClient := customer.NewCustomerServiceClient(customerServiceConn)
|
||||
|
||||
pgdal, err := dal.New(ctx, options.PostgresCredentials, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
kafkaWorker, err := privilegewc.NewKafkaConsumerWorker(privilegewc.Config{
|
||||
KafkaBroker: options.KafkaBroker,
|
||||
KafkaTopic: options.KafkaTopic,
|
||||
ServiceKey: options.ServiceName,
|
||||
TickerInterval: time.Second * 10,
|
||||
Logger: logger,
|
||||
ErrChan: errChan,
|
||||
}, redisClient, pgdal)
|
||||
if err != nil {
|
||||
logger.Module("Failed start privilege worker")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
checkWorker := privilegewc.NewCheckWorker(privilegewc.CheckWorkerConfig{
|
||||
DefaultData: model.DefaultData{
|
||||
PrivilegeID: options.PrivilegeID,
|
||||
Amount: options.Amount,
|
||||
UnlimID: options.UnlimID,
|
||||
},
|
||||
TickerInterval: time.Minute,
|
||||
Logger: logger,
|
||||
ErrChan: errChan,
|
||||
}, pgdal)
|
||||
|
||||
go kafkaWorker.Start(ctx)
|
||||
go checkWorker.Start(ctx)
|
||||
toClientWorker := answerwc.NewSendToClient(answerwc.DepsSendToClient{
|
||||
Redis: redisClient,
|
||||
Dal: pgdal,
|
||||
MailClient: mailClient,
|
||||
CustomerService: customerServiceClient,
|
||||
}, logger, errChan)
|
||||
|
||||
toRespWorker := answerwc.NewRespWorker(answerwc.DepsRespWorker{
|
||||
Redis: redisClient,
|
||||
Dal: pgdal,
|
||||
MailClient: mailClient,
|
||||
}, logger, errChan)
|
||||
|
||||
go toClientWorker.Start(ctx)
|
||||
go toRespWorker.Start(ctx)
|
||||
|
||||
tow := timeout.New(pgdal, time.Minute)
|
||||
statW := shortstat.New(pgdal, 5*time.Minute)
|
||||
tow.ExposeErr(ctx, &workerErr)
|
||||
statW.ExposeErr(ctx, &workerErr)
|
||||
go tow.Start(ctx)
|
||||
go func() {
|
||||
// defer pgdal.CloseWorker()
|
||||
statW.Start(ctx)
|
||||
}()
|
||||
|
||||
logger.Emit(InfoSvcReady{})
|
||||
// todo implement helper func for service app type. such as server preparing, logger preparing, healthchecks and etc.
|
||||
return &App{
|
||||
logger: zapLogger,
|
||||
err: make(chan error),
|
||||
}, err
|
||||
}
|
10
app/logrecords.go
Normal file
10
app/logrecords.go
Normal file
@ -0,0 +1,10 @@
|
||||
package app
|
||||
|
||||
type InfoSvcStarted struct{}
|
||||
type InfoSvcReady struct{}
|
||||
type InfoSvcShutdown struct {
|
||||
Signal string
|
||||
}
|
||||
type ErrorCanNotServe struct {
|
||||
Err error
|
||||
}
|
182
clients/customer/service.pb.go
Normal file
182
clients/customer/service.pb.go
Normal file
@ -0,0 +1,182 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.31.0
|
||||
// protoc (unknown)
|
||||
// source: customer/service.proto
|
||||
|
||||
package customer
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type History struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
UserID string `protobuf:"bytes,1,opt,name=UserID,proto3" json:"UserID,omitempty"`
|
||||
Comment string `protobuf:"bytes,2,opt,name=Comment,proto3" json:"Comment,omitempty"`
|
||||
Key string `protobuf:"bytes,3,opt,name=Key,proto3" json:"Key,omitempty"`
|
||||
RawDetails string `protobuf:"bytes,4,opt,name=RawDetails,proto3" json:"RawDetails,omitempty"`
|
||||
}
|
||||
|
||||
func (x *History) Reset() {
|
||||
*x = History{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_customer_service_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *History) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*History) ProtoMessage() {}
|
||||
|
||||
func (x *History) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_customer_service_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use History.ProtoReflect.Descriptor instead.
|
||||
func (*History) Descriptor() ([]byte, []int) {
|
||||
return file_customer_service_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *History) GetUserID() string {
|
||||
if x != nil {
|
||||
return x.UserID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *History) GetComment() string {
|
||||
if x != nil {
|
||||
return x.Comment
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *History) GetKey() string {
|
||||
if x != nil {
|
||||
return x.Key
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *History) GetRawDetails() string {
|
||||
if x != nil {
|
||||
return x.RawDetails
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_customer_service_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_customer_service_proto_rawDesc = []byte{
|
||||
0x0a, 0x16, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69,
|
||||
0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d,
|
||||
0x65, 0x72, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22,
|
||||
0x6d, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x55, 0x73,
|
||||
0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x55, 0x73, 0x65, 0x72,
|
||||
0x49, 0x44, 0x12, 0x18, 0x0a, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20,
|
||||
0x01, 0x28, 0x09, 0x52, 0x07, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03,
|
||||
0x4b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x4b, 0x65, 0x79, 0x12, 0x1e,
|
||||
0x0a, 0x0a, 0x52, 0x61, 0x77, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x0a, 0x52, 0x61, 0x77, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x32, 0x4f,
|
||||
0x0a, 0x0f, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63,
|
||||
0x65, 0x12, 0x3c, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x48, 0x69, 0x73, 0x74, 0x6f,
|
||||
0x72, 0x79, 0x12, 0x11, 0x2e, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x2e, 0x48, 0x69,
|
||||
0x73, 0x74, 0x6f, 0x72, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42,
|
||||
0x0c, 0x5a, 0x0a, 0x2e, 0x2f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x65, 0x72, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_customer_service_proto_rawDescOnce sync.Once
|
||||
file_customer_service_proto_rawDescData = file_customer_service_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_customer_service_proto_rawDescGZIP() []byte {
|
||||
file_customer_service_proto_rawDescOnce.Do(func() {
|
||||
file_customer_service_proto_rawDescData = protoimpl.X.CompressGZIP(file_customer_service_proto_rawDescData)
|
||||
})
|
||||
return file_customer_service_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_customer_service_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
|
||||
var file_customer_service_proto_goTypes = []interface{}{
|
||||
(*History)(nil), // 0: customer.History
|
||||
(*emptypb.Empty)(nil), // 1: google.protobuf.Empty
|
||||
}
|
||||
var file_customer_service_proto_depIdxs = []int32{
|
||||
0, // 0: customer.CustomerService.InsertHistory:input_type -> customer.History
|
||||
1, // 1: customer.CustomerService.InsertHistory:output_type -> google.protobuf.Empty
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_customer_service_proto_init() }
|
||||
func file_customer_service_proto_init() {
|
||||
if File_customer_service_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_customer_service_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*History); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_customer_service_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 1,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_customer_service_proto_goTypes,
|
||||
DependencyIndexes: file_customer_service_proto_depIdxs,
|
||||
MessageInfos: file_customer_service_proto_msgTypes,
|
||||
}.Build()
|
||||
File_customer_service_proto = out.File
|
||||
file_customer_service_proto_rawDesc = nil
|
||||
file_customer_service_proto_goTypes = nil
|
||||
file_customer_service_proto_depIdxs = nil
|
||||
}
|
108
clients/customer/service_grpc.pb.go
Normal file
108
clients/customer/service_grpc.pb.go
Normal file
@ -0,0 +1,108 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.3.0
|
||||
// - protoc (unknown)
|
||||
// source: customer/service.proto
|
||||
|
||||
package customer
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
emptypb "google.golang.org/protobuf/types/known/emptypb"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
const (
|
||||
CustomerService_InsertHistory_FullMethodName = "/customer.CustomerService/InsertHistory"
|
||||
)
|
||||
|
||||
// CustomerServiceClient is the client API for CustomerService service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type CustomerServiceClient interface {
|
||||
InsertHistory(ctx context.Context, in *History, opts ...grpc.CallOption) (*emptypb.Empty, error)
|
||||
}
|
||||
|
||||
type customerServiceClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewCustomerServiceClient(cc grpc.ClientConnInterface) CustomerServiceClient {
|
||||
return &customerServiceClient{cc}
|
||||
}
|
||||
|
||||
func (c *customerServiceClient) InsertHistory(ctx context.Context, in *History, opts ...grpc.CallOption) (*emptypb.Empty, error) {
|
||||
out := new(emptypb.Empty)
|
||||
err := c.cc.Invoke(ctx, CustomerService_InsertHistory_FullMethodName, in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CustomerServiceServer is the server API for CustomerService service.
|
||||
// All implementations should embed UnimplementedCustomerServiceServer
|
||||
// for forward compatibility
|
||||
type CustomerServiceServer interface {
|
||||
InsertHistory(context.Context, *History) (*emptypb.Empty, error)
|
||||
}
|
||||
|
||||
// UnimplementedCustomerServiceServer should be embedded to have forward compatible implementations.
|
||||
type UnimplementedCustomerServiceServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedCustomerServiceServer) InsertHistory(context.Context, *History) (*emptypb.Empty, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method InsertHistory not implemented")
|
||||
}
|
||||
|
||||
// UnsafeCustomerServiceServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to CustomerServiceServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeCustomerServiceServer interface {
|
||||
mustEmbedUnimplementedCustomerServiceServer()
|
||||
}
|
||||
|
||||
func RegisterCustomerServiceServer(s grpc.ServiceRegistrar, srv CustomerServiceServer) {
|
||||
s.RegisterService(&CustomerService_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _CustomerService_InsertHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(History)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(CustomerServiceServer).InsertHistory(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: CustomerService_InsertHistory_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(CustomerServiceServer).InsertHistory(ctx, req.(*History))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// CustomerService_ServiceDesc is the grpc.ServiceDesc for CustomerService service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var CustomerService_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "customer.CustomerService",
|
||||
HandlerType: (*CustomerServiceServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "InsertHistory",
|
||||
Handler: _CustomerService_InsertHistory_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "customer/service.proto",
|
||||
}
|
144
clients/mailclient/client.go
Normal file
144
clients/mailclient/client.go
Normal file
@ -0,0 +1,144 @@
|
||||
package mailclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/themakers/hlog"
|
||||
"mime/multipart"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
type ClientDeps struct {
|
||||
Host string
|
||||
Port string
|
||||
Sender string
|
||||
Auth *PlainAuth
|
||||
ApiKey string
|
||||
FiberClient *fiber.Client
|
||||
Logger hlog.Logger
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
deps ClientDeps
|
||||
}
|
||||
|
||||
type PlainAuth struct {
|
||||
Identity string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
type EmailTemplateData struct {
|
||||
QuizConfig model.ResultInfo
|
||||
AnswerContent model.ResultContent
|
||||
AllAnswers []model.ResultAnswer
|
||||
QuestionsMap map[uint64]string
|
||||
AnswerTime string
|
||||
}
|
||||
|
||||
func NewClient(deps ClientDeps) *Client {
|
||||
if deps.FiberClient == nil {
|
||||
deps.FiberClient = fiber.AcquireClient()
|
||||
}
|
||||
return &Client{
|
||||
deps: deps,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) SendMailWithAttachment(recipient, subject string, emailTemplate string, data EmailTemplateData, attachments []Attachment) error {
|
||||
text, err := generateTextFromTemplate(data, emailTemplate)
|
||||
if err != nil {
|
||||
c.deps.Logger.Module("Error generate text from template")
|
||||
return err
|
||||
}
|
||||
|
||||
msg := &Message{
|
||||
From: c.deps.Sender,
|
||||
To: []string{recipient},
|
||||
Subject: subject,
|
||||
Body: text,
|
||||
Attachments: attachments,
|
||||
}
|
||||
|
||||
return c.Send(msg)
|
||||
}
|
||||
|
||||
func (c *Client) Send(msg *Message) error {
|
||||
url := "https://api.smtp.bz/v1/smtp/send"
|
||||
|
||||
form := new(bytes.Buffer)
|
||||
writer := multipart.NewWriter(form)
|
||||
defer writer.Close()
|
||||
|
||||
fields := map[string]string{
|
||||
"from": msg.From,
|
||||
"to": strings.Join(msg.To, ","),
|
||||
"subject": msg.Subject,
|
||||
"html": msg.Body,
|
||||
}
|
||||
for key, value := range fields {
|
||||
if err := writer.WriteField(key, value); err != nil {
|
||||
c.deps.Logger.Module("Error creating form fields")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, attachment := range msg.Attachments {
|
||||
part, err := writer.CreateFormFile("attachments", attachment.Name)
|
||||
if err != nil {
|
||||
c.deps.Logger.Module("Error creating form file for attachment")
|
||||
return err
|
||||
}
|
||||
_, err = part.Write(attachment.Data)
|
||||
if err != nil {
|
||||
c.deps.Logger.Module("Error writing attachment content")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
c.deps.Logger.Module("Error closing writer for multipart form")
|
||||
return err
|
||||
}
|
||||
|
||||
agent := c.deps.FiberClient.Post(url).Body(form.Bytes()).ContentType(writer.FormDataContentType())
|
||||
if c.deps.ApiKey != "" {
|
||||
agent.Set("Authorization", c.deps.ApiKey)
|
||||
}
|
||||
|
||||
statusCode, body, errs := agent.Bytes()
|
||||
if errs != nil {
|
||||
c.deps.Logger.Module("Error sending request")
|
||||
return errs[0]
|
||||
}
|
||||
|
||||
if statusCode != fiber.StatusOK {
|
||||
return fmt.Errorf("SMTP service returned error: %d, Response body: %s", statusCode, body)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateTextFromTemplate(data EmailTemplateData, tpl string) (string, error) {
|
||||
t, err := template.New("email").Parse(tpl)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing template: %w", err)
|
||||
}
|
||||
|
||||
var text bytes.Buffer
|
||||
if err := t.Execute(&text, EmailTemplateData{
|
||||
QuizConfig: data.QuizConfig,
|
||||
AnswerContent: data.AnswerContent,
|
||||
AllAnswers: data.AllAnswers,
|
||||
QuestionsMap: data.QuestionsMap,
|
||||
AnswerTime: data.AnswerTime,
|
||||
}); err != nil {
|
||||
return "", fmt.Errorf("error executing template: %w", err)
|
||||
}
|
||||
|
||||
return text.String(), nil
|
||||
}
|
104
clients/mailclient/message.go
Normal file
104
clients/mailclient/message.go
Normal file
@ -0,0 +1,104 @@
|
||||
package mailclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
To []string
|
||||
From string
|
||||
Subject string
|
||||
Body string
|
||||
Attachments []Attachment
|
||||
}
|
||||
|
||||
type Attachment struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func NewMessage(subject, body string) *Message {
|
||||
if subject == "" {
|
||||
subject = "Вам пришла заявка с PenaQuiz"
|
||||
}
|
||||
return &Message{Subject: subject, Body: body, Attachments: []Attachment{}}
|
||||
}
|
||||
|
||||
func (m *Message) AttachFile(src string) error {
|
||||
data, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, filename := filepath.Split(src)
|
||||
m.Attachments = append(m.Attachments, Attachment{Name: filename, Data: data})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Message) AttachBytesFile(filename string, data []byte) {
|
||||
m.Attachments = append(m.Attachments, Attachment{Name: filename, Data: data})
|
||||
}
|
||||
|
||||
func (m *Message) ToBytes() []byte {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
buf.WriteString("MIME-Version: 1.0\r\n")
|
||||
fmt.Fprintf(buf, "From: %s\r\n", m.From)
|
||||
fmt.Fprintf(buf, "Subject: %s\r\n", m.Subject)
|
||||
fmt.Fprintf(buf, "To: %s\r\n", strings.Join(m.To, ","))
|
||||
|
||||
boundary := randomBoundary()
|
||||
|
||||
if len(m.Attachments) > 0 {
|
||||
buf.WriteString("Content-Type: multipart/mixed;\r\n")
|
||||
|
||||
fmt.Fprintf(buf, " boundary=\"%s\"\r\n", boundary)
|
||||
|
||||
fmt.Fprintf(buf, "\r\n--%s", boundary)
|
||||
for _, attachment := range m.Attachments {
|
||||
buf.WriteString("\r\n")
|
||||
switch strings.Split(attachment.Name, ".")[1] {
|
||||
case "htmlmsg":
|
||||
|
||||
buf.WriteString("Content-Type: text/html; charset=\"utf-8\"\r\n")
|
||||
buf.WriteString("Content-Transfer-Encoding: base64\r\n")
|
||||
case "docx":
|
||||
|
||||
buf.WriteString("Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document\r\n")
|
||||
buf.WriteString("Content-Transfer-Encoding: base64\r\n")
|
||||
fmt.Fprintf(buf, "Content-Disposition: attachment; filename=\"%s\"\r\n", attachment.Name)
|
||||
default:
|
||||
fmt.Fprintf(buf, "Content-Type: %s\r\n", http.DetectContentType(attachment.Data))
|
||||
buf.WriteString("Content-Transfer-Encoding: base64\r\n")
|
||||
fmt.Fprintf(buf, "Content-Disposition: attachment; filename=\"%s\"\r\n", attachment.Name)
|
||||
}
|
||||
|
||||
buf.WriteString("\r\n")
|
||||
|
||||
b := make([]byte, base64.StdEncoding.EncodedLen(len(attachment.Data)))
|
||||
base64.StdEncoding.Encode(b, attachment.Data)
|
||||
|
||||
writer := NewLineWriter(buf, 76)
|
||||
_, err := writer.Write(b)
|
||||
if err != nil {
|
||||
fmt.Println("mailclient-client err:", err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(buf, "\r\n\r\n--%s", boundary)
|
||||
}
|
||||
|
||||
buf.WriteString("--")
|
||||
} else {
|
||||
buf.WriteString("Content-Type: text/plain; charset=utf-8\r\n")
|
||||
buf.WriteString(m.Body)
|
||||
}
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
63
clients/mailclient/utils.go
Normal file
63
clients/mailclient/utils.go
Normal file
@ -0,0 +1,63 @@
|
||||
package mailclient
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
type LineWriter struct {
|
||||
w io.Writer
|
||||
length int
|
||||
}
|
||||
|
||||
func NewLineWriter(w io.Writer, length int) *LineWriter {
|
||||
return &LineWriter{
|
||||
w: w,
|
||||
length: length,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LineWriter) Write(p []byte) (n int, err error) {
|
||||
for i := 0; i < len(p); i += r.length {
|
||||
end := i + r.length
|
||||
|
||||
if end > len(p) {
|
||||
end = len(p) - 1
|
||||
}
|
||||
|
||||
var chunk []byte
|
||||
chunk = append(chunk, p[i:end]...)
|
||||
|
||||
if len(p) >= end+r.length {
|
||||
chunk = append(chunk, []byte("\r\n")...)
|
||||
}
|
||||
|
||||
addN, err := r.w.Write(chunk)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += addN
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (r *LineWriter) WriteString(s string) (n int, err error) {
|
||||
p := []byte(s)
|
||||
return r.Write(p)
|
||||
}
|
||||
|
||||
func (r *LineWriter) WriteFormatString(format string, a ...any) (n int, err error) {
|
||||
p := []byte(fmt.Sprintf(format, a...))
|
||||
return r.Write(p)
|
||||
}
|
||||
|
||||
func randomBoundary() string {
|
||||
var buf [30]byte
|
||||
_, err := io.ReadFull(rand.Reader, buf[:])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return fmt.Sprintf("%x", buf[:])
|
||||
}
|
12
deployments/local/docker-compose.yaml
Normal file
12
deployments/local/docker-compose.yaml
Normal file
@ -0,0 +1,12 @@
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Redalert2
|
||||
POSTGRES_USER: squiz
|
||||
POSTGRES_DB: squiz
|
||||
app:
|
||||
image: penahub.gitlab.yandexcloud.net:5050/backend/squiz:latest
|
||||
ports:
|
||||
- 1488:1488
|
77
deployments/main/docker-compose.yaml
Normal file
77
deployments/main/docker-compose.yaml
Normal file
@ -0,0 +1,77 @@
|
||||
services:
|
||||
core:
|
||||
hostname: squiz-core
|
||||
container_name: squiz-core
|
||||
image: $CI_REGISTRY_IMAGE/main-core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
HUB_ADMIN_URL: 'http://10.8.0.8:59303'
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PORT: 1488
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
AUTH_URL: 'http://10.8.0.8:59300/user'
|
||||
ports:
|
||||
- 10.8.0.9:1488:1488
|
||||
|
||||
storer:
|
||||
hostname: squiz-storer
|
||||
container_name: squiz-storer
|
||||
image: $CI_REGISTRY_IMAGE/main-storer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PORT: 1489
|
||||
MINIO_EP: 'storage.yandexcloud.net'
|
||||
MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA'
|
||||
MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry'
|
||||
PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
ports:
|
||||
- 10.8.0.9:1489:1489
|
||||
worker:
|
||||
hostname: squiz-worker
|
||||
container_name: squiz-worker
|
||||
image: $CI_REGISTRY_IMAGE/main-worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
KAFKA_BROKER: '10.8.0.8:9092'
|
||||
KAFKA_TOPIC: 'tariffs'
|
||||
QUIZ_ID: quizCnt
|
||||
AMOUNT: 10
|
||||
UNLIM_ID: quizUnlimTime
|
||||
REDIS_HOST: '10.8.0.9:6379'
|
||||
REDIS_PASSWORD: 'Redalert2'
|
||||
REDIS_DB: 2
|
||||
SMTP_API_URL: 'https://api.smtp.bz/v1/smtp/send'
|
||||
SMTP_HOST: 'connect.smtp.bz'
|
||||
SMTP_PORT: '587'
|
||||
SMTP_UNAME: 'team@pena.digital'
|
||||
SMTP_PASS: 'AyMfwqA9LkQH'
|
||||
SMTP_API_KEY: '8tv2xcsfCMBX3TCQxzgeeEwAEYyQrPUp0ggw'
|
||||
SMTP_SENDER: 'recovery@noreply.pena.digital'
|
||||
CUSTOMER_SERVICE_ADDRESS: 'http://10.8.0.8:8065/'
|
||||
answerer:
|
||||
hostname: squiz-answerer
|
||||
container_name: squiz-answerer
|
||||
image: $CI_REGISTRY_IMAGE/main-answerer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PORT: 1490
|
||||
MINIO_EP: 'storage.yandexcloud.net'
|
||||
MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA'
|
||||
MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry'
|
||||
PG_CRED: 'host=10.8.0.9 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
REDIS_HOST: '10.8.0.9:6379'
|
||||
REDIS_PASSWORD: 'Redalert2'
|
||||
REDIS_DB: 2
|
||||
ports:
|
||||
- 10.8.0.9:1490:1490
|
77
deployments/main/staging/docker-compose.yaml
Normal file
77
deployments/main/staging/docker-compose.yaml
Normal file
@ -0,0 +1,77 @@
|
||||
services:
|
||||
core:
|
||||
hostname: squiz-core
|
||||
container_name: squiz-core
|
||||
image: $CI_REGISTRY_IMAGE/core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
HUB_ADMIN_URL: 'http://10.6.0.11:59303'
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PORT: 1488
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
AUTH_URL: 'http://10.6.0.11:59300/user'
|
||||
ports:
|
||||
- 1488:1488
|
||||
|
||||
storer:
|
||||
hostname: squiz-storer
|
||||
container_name: squiz-storer
|
||||
image: $CI_REGISTRY_IMAGE/storer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PORT: 1489
|
||||
MINIO_EP: 'storage.yandexcloud.net'
|
||||
MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA'
|
||||
MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry'
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
ports:
|
||||
- 1489:1489
|
||||
worker:
|
||||
hostname: squiz-worker
|
||||
container_name: squiz-worker
|
||||
image: $CI_REGISTRY_IMAGE/worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
KAFKA_BROKER: '10.6.0.11:9092'
|
||||
KAFKA_TOPIC: 'tariffs'
|
||||
QUIZ_ID: quizCnt
|
||||
AMOUNT: 10
|
||||
UNLIM_ID: quizUnlimTime
|
||||
REDIS_HOST: '10.6.0.23:6379'
|
||||
REDIS_PASSWORD: 'Redalert2'
|
||||
REDIS_DB: 2
|
||||
SMTP_HOST: 'connect.mailclient.bz'
|
||||
SMTP_PORT: '587'
|
||||
SMTP_SENDER: 'noreply@mailing.pena.digital'
|
||||
SMTP_IDENTITY: ''
|
||||
SMTP_USERNAME: 'kotilion.95@gmail.com'
|
||||
SMTP_PASSWORD: 'vWwbCSg4bf0p'
|
||||
SMTP_API_KEY: 'P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev'
|
||||
CUSTOMER_SERVICE_ADDRESS: 'http://10.6.0.11:8065/'
|
||||
answerer:
|
||||
hostname: squiz-answerer
|
||||
container_name: squiz-answerer
|
||||
image: $CI_REGISTRY_IMAGE/answerer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PORT: 1490
|
||||
MINIO_EP: 'storage.yandexcloud.net'
|
||||
MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA'
|
||||
MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry'
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
REDIS_HOST: '10.6.0.23:6379'
|
||||
REDIS_PASSWORD: 'Redalert2'
|
||||
REDIS_DB: 2
|
||||
ports:
|
||||
- 1490:1490
|
77
deployments/staging/docker-compose.yaml
Normal file
77
deployments/staging/docker-compose.yaml
Normal file
@ -0,0 +1,77 @@
|
||||
services:
|
||||
core:
|
||||
hostname: squiz-core
|
||||
container_name: squiz-core
|
||||
image: $CI_REGISTRY_IMAGE/staging-core:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
HUB_ADMIN_URL: 'http://10.6.0.11:59303'
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PORT: 1488
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
AUTH_URL: 'http://10.6.0.11:59300/user'
|
||||
ports:
|
||||
- 1488:1488
|
||||
|
||||
storer:
|
||||
hostname: squiz-storer
|
||||
container_name: squiz-storer
|
||||
image: $CI_REGISTRY_IMAGE/staging-storer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PORT: 1489
|
||||
MINIO_EP: 'storage.yandexcloud.net'
|
||||
MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA'
|
||||
MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry'
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
ports:
|
||||
- 1489:1489
|
||||
worker:
|
||||
hostname: squiz-worker
|
||||
container_name: squiz-worker
|
||||
image: $CI_REGISTRY_IMAGE/staging-worker:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
KAFKA_BROKER: '10.6.0.11:9092'
|
||||
KAFKA_TOPIC: 'tariffs'
|
||||
QUIZ_ID: quizCnt
|
||||
AMOUNT: 10
|
||||
UNLIM_ID: quizUnlimTime
|
||||
REDIS_HOST: '10.6.0.23:6379'
|
||||
REDIS_PASSWORD: 'Redalert2'
|
||||
REDIS_DB: 2
|
||||
SMTP_HOST: 'connect.mailclient.bz'
|
||||
SMTP_PORT: '587'
|
||||
SMTP_SENDER: 'noreply@mailing.pena.digital'
|
||||
SMTP_IDENTITY: ''
|
||||
SMTP_USERNAME: 'kotilion.95@gmail.com'
|
||||
SMTP_PASSWORD: 'vWwbCSg4bf0p'
|
||||
SMTP_API_KEY: 'P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev'
|
||||
CUSTOMER_SERVICE_ADDRESS: 'http://10.6.0.11:8065/'
|
||||
answerer:
|
||||
hostname: squiz-answerer
|
||||
container_name: squiz-answerer
|
||||
image: $CI_REGISTRY_IMAGE/staging-answerer:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
tty: true
|
||||
environment:
|
||||
IS_PROD_LOG: 'false'
|
||||
IS_PROD: 'false'
|
||||
PUBLIC_ACCESS_SECRET_KEY: $JWT_PUBLIC_KEY
|
||||
PORT: 1490
|
||||
MINIO_EP: 'storage.yandexcloud.net'
|
||||
MINIO_AK: 'YCAJEOcqqTHpiwL4qFwLfHPNA'
|
||||
MINIO_SK: 'YCNIAIat0XqdDzycWsYKX3OU7mPor6S0WmMoG4Ry'
|
||||
PG_CRED: 'host=10.6.0.23 port=5433 user=squiz password=Redalert2 dbname=squiz sslmode=disable'
|
||||
REDIS_HOST: '10.6.0.23:6379'
|
||||
REDIS_PASSWORD: 'Redalert2'
|
||||
REDIS_DB: 2
|
||||
ports:
|
||||
- 1490:1490
|
102
deployments/test/docker-compose.yaml
Normal file
102
deployments/test/docker-compose.yaml
Normal file
@ -0,0 +1,102 @@
|
||||
version: '3'
|
||||
services:
|
||||
test-postgres:
|
||||
image: postgres
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Redalert2
|
||||
POSTGRES_USER: squiz
|
||||
POSTGRES_DB: squiz
|
||||
volumes:
|
||||
- test-postgres:/var/lib/postgresql/data
|
||||
ports:
|
||||
- 35432:5432
|
||||
networks:
|
||||
- penatest
|
||||
healthcheck:
|
||||
test: pg_isready -U squiz
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 10
|
||||
|
||||
# need update!
|
||||
# test-pena-auth-service:
|
||||
# image: penahub.gitlab.yandexcloud.net:5050/pena-services/pena-auth-service:staging.872
|
||||
# container_name: test-pena-auth-service
|
||||
# init: true
|
||||
# env_file: auth.env.test
|
||||
# healthcheck:
|
||||
# test: wget -T1 --spider http://localhost:8000/user
|
||||
# interval: 2s
|
||||
# timeout: 2s
|
||||
# retries: 5
|
||||
# environment:
|
||||
# - DB_HOST=test-pena-auth-db
|
||||
# - DB_PORT=27017
|
||||
# - ENVIRONMENT=staging
|
||||
# - HTTP_HOST=0.0.0.0
|
||||
# - HTTP_PORT=8000
|
||||
# - DB_USERNAME=test
|
||||
# - DB_PASSWORD=test
|
||||
# - DB_NAME=admin
|
||||
# - DB_AUTH=admin
|
||||
# # ports:
|
||||
# # - 8000:8000
|
||||
# depends_on:
|
||||
# - test-pena-auth-db
|
||||
# # - pena-auth-migration
|
||||
# networks:
|
||||
# - penatest
|
||||
#
|
||||
# test-pena-auth-db:
|
||||
# container_name: test-pena-auth-db
|
||||
# init: true
|
||||
# image: "mongo:6.0.3"
|
||||
# command: mongod --quiet --logpath /dev/null
|
||||
# volumes:
|
||||
# - test-mongodb:/data/db
|
||||
# - test-mongoconfdb:/data/configdb
|
||||
# environment:
|
||||
# MONGO_INITDB_ROOT_USERNAME: test
|
||||
# MONGO_INITDB_ROOT_PASSWORD: test
|
||||
# # ports:
|
||||
# # - 27017:27017
|
||||
# networks:
|
||||
# - penatest
|
||||
|
||||
test-minio:
|
||||
container_name: test-minio
|
||||
init: true
|
||||
image: quay.io/minio/minio
|
||||
volumes:
|
||||
- test-minio:/data
|
||||
command: [ "minio", "--quiet", "server", "/data" ]
|
||||
networks:
|
||||
- penatest
|
||||
|
||||
test-squiz:
|
||||
container_name: test-squiz
|
||||
init: true
|
||||
build:
|
||||
context: ../..
|
||||
dockerfile: TestsDockerfile
|
||||
depends_on:
|
||||
test-postgres:
|
||||
condition: service_healthy
|
||||
# test-pena-auth-service:
|
||||
# condition: service_healthy
|
||||
# volumes:
|
||||
# - ./../..:/app:ro
|
||||
# command: [ "go", "test", "./tests", "-run", "TestFoo" ]
|
||||
command: [ "go", "test", "-parallel", "1", "./tests" ]
|
||||
networks:
|
||||
- penatest
|
||||
|
||||
networks:
|
||||
penatest:
|
||||
|
||||
|
||||
volumes:
|
||||
test-minio:
|
||||
test-postgres:
|
||||
test-mongodb:
|
||||
test-mongoconfdb:
|
23
deployments/testmigrate/docker-compose.yaml
Normal file
23
deployments/testmigrate/docker-compose.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
version: '3'
|
||||
services:
|
||||
test-postgres:
|
||||
image: postgres
|
||||
environment:
|
||||
POSTGRES_PASSWORD: Redalert2
|
||||
POSTGRES_USER: squiz
|
||||
POSTGRES_DB: squiz
|
||||
ports:
|
||||
- 35432:5432
|
||||
networks:
|
||||
- penatest
|
||||
healthcheck:
|
||||
test: pg_isready -U squiz
|
||||
interval: 2s
|
||||
timeout: 2s
|
||||
retries: 10
|
||||
|
||||
networks:
|
||||
penatest:
|
||||
|
||||
# просто чтоб тестануть мигрировала ли бд
|
||||
# в app/app.go pgdal, err := dal.New(ctx, "host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable")
|
45
go.mod
Normal file
45
go.mod
Normal file
@ -0,0 +1,45 @@
|
||||
module penahub.gitlab.yandexcloud.net/backend/quiz/worker.git
|
||||
|
||||
go 1.21.4
|
||||
|
||||
require (
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/gofiber/fiber/v2 v2.52.0
|
||||
github.com/golang/protobuf v1.5.3
|
||||
github.com/skeris/appInit v1.0.2
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf
|
||||
github.com/twmb/franz-go v1.16.1
|
||||
go.uber.org/zap v1.26.0
|
||||
google.golang.org/grpc v1.61.1
|
||||
google.golang.org/protobuf v1.32.0
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219175507-7f8de986a6dc
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.19 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.7.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
go.uber.org/atomic v1.7.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/net v0.18.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d // indirect
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/core.git v0.0.0-20240219174804-d78fd38511af // indirect
|
||||
)
|
174
go.sum
Normal file
174
go.sum
Normal file
@ -0,0 +1,174 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dhui/dktest v0.4.0 h1:z05UmuXZHO/bgj/ds2bGMBu8FI4WA+Ag/m3ghL+om7M=
|
||||
github.com/dhui/dktest v0.4.0/go.mod h1:v/Dbz1LgCBOi2Uki2nUqLBGa83hWBGFMu5MrgMDCc78=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
|
||||
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE=
|
||||
github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0 h1:rd40H3QXU0AA4IoLllFcEAEo9dYKRHYND2gB4p7xcaU=
|
||||
github.com/golang-migrate/migrate/v4 v4.17.0/go.mod h1:+Cp2mtLP4/aXDTKb9wmXYitdrNx2HGs45rbWAo6OsKM=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=
|
||||
github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||
github.com/pierrec/lz4/v4 v4.1.19 h1:tYLzDnjDXh9qIxSTKHwXwOYmm9d887Y7Y1ZkyXYHAN4=
|
||||
github.com/pierrec/lz4/v4 v4.1.19/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/skeris/appInit v1.0.2 h1:Hr4KbXYd6kolTVq4cXGqDpgnpmaauiOiKizA1+Ep4KQ=
|
||||
github.com/skeris/appInit v1.0.2/go.mod h1:4ElEeXWVGzU3dlYq/eMWJ/U5hd+LKisc1z3+ySh1XmY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf h1:TJJm6KcBssmbWzplF5lzixXl1RBAi/ViPs1GaSOkhwo=
|
||||
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf/go.mod h1:1FsorU3vnXO9xS9SrhUp8fRb/6H/Zfll0rPt1i4GWaA=
|
||||
github.com/twmb/franz-go v1.16.1 h1:rpWc7fB9jd7TgmCyfxzenBI+QbgS8ZfJOUQE+tzPtbE=
|
||||
github.com/twmb/franz-go v1.16.1/go.mod h1:/pER254UPPGp/4WfGqRi+SIRGE50RSQzVubQp6+N4FA=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.7.0 h1:a457IbvezYfA5UkiBvyV3zj0Is3y1i8EJgqjJYoij2E=
|
||||
github.com/twmb/franz-go/pkg/kmsg v1.7.0/go.mod h1:se9Mjdt0Nwzc9lnjJ0HyDtLyBnaBDAd7pCje47OhSyw=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
|
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
|
||||
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
|
||||
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
|
||||
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg=
|
||||
golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 h1:Jyp0Hsi0bmHXG6k9eATXoYtjd6e2UzZ1SCn/wIupY14=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:oQ5rr10WTTMvP4A36n8JpR1OrO1BEiV4f78CneXZxkA=
|
||||
google.golang.org/grpc v1.61.1 h1:kLAiWrZs7YeDM6MumDe7m3y4aM6wacLzM1Y/wiLP9XY=
|
||||
google.golang.org/grpc v1.61.1/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
||||
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d h1:gbaDt35HMDqOK84WYmDIlXMI7rstUcRqNttaT6Kx1do=
|
||||
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d/go.mod h1:lTmpjry+8evVkXWbEC+WMOELcFkRD1lFMc7J09mOndM=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219175507-7f8de986a6dc h1:jIN9XyfL/FJ/eSsYopE1olHboituwmisC1Sf1d4nhWE=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240219175507-7f8de986a6dc/go.mod h1:OXYvMlc+3qfcllPTywUB3QDiPK1kwsMNdZMTlPXFIdo=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/core.git v0.0.0-20240219174804-d78fd38511af h1:jQ7HaXSutDX5iepU7VRImxhikK7lV/lBKkiloOZ4Emo=
|
||||
penahub.gitlab.yandexcloud.net/backend/quiz/core.git v0.0.0-20240219174804-d78fd38511af/go.mod h1:5S5YwjSXWmnEKjBjG6MtyGtFmljjukDRS8CwHk/CF/I=
|
10
main.go
Normal file
10
main.go
Normal file
@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/skeris/appInit"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/app"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appInit.Initialize(app.New, app.Options{})
|
||||
}
|
67
privilegewc/check.go
Normal file
67
privilegewc/check.go
Normal file
@ -0,0 +1,67 @@
|
||||
package privilegewc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/themakers/hlog"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type CheckWorkerConfig struct {
|
||||
TickerInterval time.Duration
|
||||
DefaultData model.DefaultData
|
||||
Logger hlog.Logger
|
||||
ErrChan chan<- error
|
||||
}
|
||||
|
||||
type CheckWorker struct {
|
||||
config CheckWorkerConfig
|
||||
privilegeDAL *dal.DAL
|
||||
}
|
||||
|
||||
func NewCheckWorker(config CheckWorkerConfig, privilegeDAL *dal.DAL) *CheckWorker {
|
||||
return &CheckWorker{
|
||||
config: config,
|
||||
privilegeDAL: privilegeDAL,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *CheckWorker) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(w.config.TickerInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
fmt.Println("CHECK")
|
||||
w.performScheduledTasks(ctx)
|
||||
case <-ctx.Done():
|
||||
fmt.Println("Check worker terminated")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe one query?
|
||||
func (w *CheckWorker) performScheduledTasks(ctx context.Context) {
|
||||
fmt.Println("CHEC0")
|
||||
w.deleteExpired(ctx)
|
||||
}
|
||||
|
||||
func (w *CheckWorker) deleteExpired(ctx context.Context) {
|
||||
expiredData, err := w.privilegeDAL.AccountRepo.GetExpired(ctx, w.config.DefaultData.UnlimID)
|
||||
if err != nil {
|
||||
w.config.Logger.Module("Error getting expired quizUnlimTime records")
|
||||
w.config.ErrChan <- err
|
||||
}
|
||||
|
||||
for _, data := range expiredData {
|
||||
err := w.privilegeDAL.AccountRepo.DeletePrivilegeByID(ctx, data.ID)
|
||||
if err != nil {
|
||||
w.config.Logger.Module("Error deleting expired quizUnlimTime record")
|
||||
w.config.ErrChan <- err
|
||||
}
|
||||
}
|
||||
}
|
155
privilegewc/consumer.go
Normal file
155
privilegewc/consumer.go
Normal file
@ -0,0 +1,155 @@
|
||||
package privilegewc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/themakers/hlog"
|
||||
"github.com/twmb/franz-go/pkg/kgo"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/wctools"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Config содержит параметры конфигурации.
|
||||
type Config struct {
|
||||
KafkaBroker string
|
||||
KafkaTopic string
|
||||
ServiceKey string
|
||||
TickerInterval time.Duration
|
||||
Logger hlog.Logger
|
||||
ErrChan chan<- error
|
||||
}
|
||||
|
||||
type KafkaConsumerWorker struct {
|
||||
config Config
|
||||
client *kgo.Client
|
||||
redis *redis.Client
|
||||
privilegeDAL *dal.DAL
|
||||
}
|
||||
|
||||
// NewKafkaConsumerWorker создает новый экземпляр KafkaConsumerWorker.
|
||||
func NewKafkaConsumerWorker(config Config, redis *redis.Client, privilegeDAL *dal.DAL) (*KafkaConsumerWorker, error) {
|
||||
client, err := kgo.NewClient(
|
||||
kgo.SeedBrokers(config.KafkaBroker),
|
||||
kgo.ConsumerGroup("squiz1"),
|
||||
kgo.ConsumeTopics(config.KafkaTopic),
|
||||
kgo.ConsumeResetOffset(kgo.NewOffset().AfterMilli(time.Now().UnixMilli())),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &KafkaConsumerWorker{
|
||||
config: config,
|
||||
client: client,
|
||||
privilegeDAL: privilegeDAL,
|
||||
redis: redis,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start запускает.
|
||||
func (w *KafkaConsumerWorker) Start(ctx context.Context) {
|
||||
ticker := time.NewTicker(w.config.TickerInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
fmt.Println("KONSUMER", w.config.TickerInterval)
|
||||
select {
|
||||
case <-ticker.C:
|
||||
w.fetchMessages(ctx)
|
||||
case <-ctx.Done():
|
||||
w.config.Logger.Module("Kafka worker terminated")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetchAndProcessMessages извлекает сообщения из темы Kafka и обрабатывает их.
|
||||
func (w *KafkaConsumerWorker) fetchMessages(ctx context.Context) {
|
||||
fetches := w.client.PollFetches(ctx)
|
||||
iter := fetches.RecordIter()
|
||||
|
||||
fmt.Println("KONSUMER1", fetches, w.config.ServiceKey)
|
||||
for !iter.Done() {
|
||||
record := iter.Next()
|
||||
|
||||
privilege, userID, err := wctools.IsValidMessage(record.Value, w.config.ServiceKey)
|
||||
fmt.Println("KONSUMER2", err, userID)
|
||||
if err != nil {
|
||||
w.config.Logger.Module("Error validating Kafka message")
|
||||
}
|
||||
|
||||
err = w.processValidMessage(ctx, privilege, userID)
|
||||
if err != nil {
|
||||
w.config.Logger.Module("Error processing valid message")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// processValidMessage обрабатывает валидное сообщение.
|
||||
func (w *KafkaConsumerWorker) processValidMessage(ctx context.Context, privilege []model.PrivilegeMessage, userID string) error {
|
||||
currentPrivileges, err := w.privilegeDAL.AccountRepo.GetPrivilegesByAccountID(ctx, userID)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: refactor getting accountId
|
||||
|
||||
accountId, err := w.privilegeDAL.AccountRepo.GetAccountByID(ctx, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currentPrivilegeMap := make(map[string]*model.ShortPrivilege)
|
||||
for i := range currentPrivileges {
|
||||
currentPrivilegeMap[currentPrivileges[i].PrivilegeName] = ¤tPrivileges[i]
|
||||
}
|
||||
|
||||
for _, receivedPrivilege := range privilege {
|
||||
fmt.Println("KONSUMERl", privilege, receivedPrivilege.PrivilegeID, wctools.FindPrivilegeName(receivedPrivilege.PrivilegeID))
|
||||
if matchingCurrentPrivilege, found := currentPrivilegeMap[receivedPrivilege.PrivilegeID]; found {
|
||||
matchingCurrentPrivilege.Amount += receivedPrivilege.Amount
|
||||
matchingCurrentPrivilege.CreatedAt = time.Now()
|
||||
|
||||
err := w.privilegeDAL.AccountRepo.UpdatePrivilege(ctx, matchingCurrentPrivilege, accountId.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
newPrivilege := &model.ShortPrivilege{
|
||||
PrivilegeID: receivedPrivilege.PrivilegeID,
|
||||
PrivilegeName: wctools.FindPrivilegeName(receivedPrivilege.PrivilegeID),
|
||||
Amount: receivedPrivilege.Amount,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := w.privilegeDAL.AccountRepo.InsertPrivilege(ctx, newPrivilege, accountId.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("RESET STALE", w.resetStaleMessages(ctx, accountId.ID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *KafkaConsumerWorker) resetStaleMessages(ctx context.Context, accountID string) error {
|
||||
keys, err := w.redis.Keys(ctx, accountID+":*").Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
renameRes := w.redis.Rename(ctx, key, strings.TrimPrefix(key, accountID+":"))
|
||||
if renameRes == nil {
|
||||
return renameRes.Err()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
72
savewc/for_client.go
Normal file
72
savewc/for_client.go
Normal file
@ -0,0 +1,72 @@
|
||||
package savewc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/themakers/hlog"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DepsForClient struct {
|
||||
WorkerSendClientCh chan model.Answer
|
||||
Redis *redis.Client
|
||||
}
|
||||
|
||||
type SaveForClient struct {
|
||||
deps DepsForClient
|
||||
errChan chan<- error
|
||||
logger hlog.Logger
|
||||
}
|
||||
|
||||
func NewSaveClientWorker(deps DepsForClient, errChan chan<- error, logger hlog.Logger) *SaveForClient {
|
||||
return &SaveForClient{
|
||||
deps: deps,
|
||||
errChan: errChan,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SaveForClient) Start(ctx context.Context) {
|
||||
|
||||
for {
|
||||
select {
|
||||
case answer, ok := <-w.deps.WorkerSendClientCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fmt.Println("SAVECLINT")
|
||||
err := w.saveAnswer(ctx, answer)
|
||||
if err != nil {
|
||||
fmt.Println("Error save answer")
|
||||
w.errChan <- err
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
fmt.Println("Save for client worker terminated")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SaveForClient) saveAnswer(ctx context.Context, answer model.Answer) error {
|
||||
answerJSON, err := json.Marshal(answer)
|
||||
if err != nil {
|
||||
fmt.Println("Error marshal answer to redis", err)
|
||||
w.errChan <- err
|
||||
return err
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("answer:%d", time.Now().UnixNano())
|
||||
|
||||
err = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err()
|
||||
if err != nil {
|
||||
fmt.Println("Error saving answer to redis", err)
|
||||
w.errChan <- err
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
71
savewc/for_respondent.go
Normal file
71
savewc/for_respondent.go
Normal file
@ -0,0 +1,71 @@
|
||||
package savewc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/themakers/hlog"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DepsForResp struct {
|
||||
WorkerRespondentCh chan []model.Answer
|
||||
Redis *redis.Client
|
||||
}
|
||||
|
||||
type SaveForRespondent struct {
|
||||
deps DepsForResp
|
||||
errChan chan<- error
|
||||
logger hlog.Logger
|
||||
}
|
||||
|
||||
func NewSaveRespWorker(deps DepsForResp, errChan chan<- error, logger hlog.Logger) *SaveForRespondent {
|
||||
return &SaveForRespondent{
|
||||
deps: deps,
|
||||
errChan: errChan,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SaveForRespondent) Start(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case answer, ok := <-w.deps.WorkerRespondentCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
fmt.Println("SAVERESP")
|
||||
err := w.saveAnswers(ctx, answer)
|
||||
if err != nil {
|
||||
w.logger.Module("Error save answers")
|
||||
w.errChan <- err
|
||||
}
|
||||
|
||||
case <-ctx.Done():
|
||||
w.logger.Module("Save for respondent worker terminated")
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *SaveForRespondent) saveAnswers(ctx context.Context, answers []model.Answer) error {
|
||||
for _, answer := range answers {
|
||||
answerJSON, err := json.Marshal(answer)
|
||||
if err != nil {
|
||||
fmt.Println("Error marshal answer", err)
|
||||
w.errChan <- err
|
||||
}
|
||||
|
||||
key := fmt.Sprintf("toRespondent:%d", time.Now().UnixNano())
|
||||
|
||||
err = w.deps.Redis.Set(ctx, key, answerJSON, 0).Err()
|
||||
if err != nil {
|
||||
fmt.Println("Error setting to redis", err)
|
||||
w.errChan <- err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
142
wctools/tools.go
Normal file
142
wctools/tools.go
Normal file
@ -0,0 +1,142 @@
|
||||
package wctools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model/tariff"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var DaysOfWeek = map[string]string{
|
||||
"Monday": "понедельник",
|
||||
"Tuesday": "вторник",
|
||||
"Wednesday": "среда",
|
||||
"Thursday": "четверг",
|
||||
"Friday": "пятница",
|
||||
"Saturday": "суббота",
|
||||
"Sunday": "воскресенье",
|
||||
}
|
||||
|
||||
var MonthsOfYear = map[string]string{
|
||||
"January": "января",
|
||||
"February": "февраля",
|
||||
"March": "марта",
|
||||
"April": "апреля",
|
||||
"May": "мая",
|
||||
"June": "июня",
|
||||
"July": "июля",
|
||||
"August": "августа",
|
||||
"September": "сентября",
|
||||
"October": "октября",
|
||||
"November": "ноября",
|
||||
"December": "декабря",
|
||||
}
|
||||
|
||||
func IsValidMessage(message []byte, expectedServiceKey string) ([]model.PrivilegeMessage, string, error) {
|
||||
var decodedMessage tariff.TariffMessage
|
||||
err := proto.Unmarshal(message, &decodedMessage)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if decodedMessage.UserID == "" || len(decodedMessage.Privileges) == 0 {
|
||||
return nil, "", errors.New("Invalid message structure")
|
||||
}
|
||||
|
||||
privileges := decodedMessage.Privileges
|
||||
userID := decodedMessage.UserID
|
||||
|
||||
var validPrivileges []model.PrivilegeMessage
|
||||
for _, privilege := range privileges {
|
||||
if IsServiceKeyValid(privilege.ServiceKey, expectedServiceKey) {
|
||||
validPrivileges = append(validPrivileges, model.PrivilegeMessage{
|
||||
PrivilegeID: privilege.PrivilegeID,
|
||||
ServiceKey: privilege.ServiceKey,
|
||||
Type: model.PrivilegeType(privilege.Type),
|
||||
Value: privilege.Value,
|
||||
Amount: privilege.Amount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(validPrivileges) == 0 {
|
||||
return nil, "", errors.New("No valid privileges found")
|
||||
}
|
||||
|
||||
return validPrivileges, userID, nil
|
||||
}
|
||||
|
||||
func IsServiceKeyValid(actualServiceKey, expectedServiceKey string) bool {
|
||||
return actualServiceKey == expectedServiceKey
|
||||
}
|
||||
|
||||
func FindPrivilegeName(privilegeID string) string {
|
||||
for _, p := range model.Privileges {
|
||||
if p.PrivilegeID == privilegeID {
|
||||
return p.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func ProcessAnswer(answer string) (model.ResultContent, error) {
|
||||
content := model.ResultContent{}
|
||||
err := json.Unmarshal([]byte(answer), &content)
|
||||
if err != nil {
|
||||
return model.ResultContent{}, err
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func ProcessQuiz(quiz string) (model.QuizConfig, error) {
|
||||
quizConfig := model.QuizConfig{}
|
||||
err := json.Unmarshal([]byte(quiz), &quizConfig)
|
||||
if err != nil {
|
||||
return model.QuizConfig{}, err
|
||||
}
|
||||
return quizConfig, nil
|
||||
}
|
||||
|
||||
func HasUnlimitedPrivilege(privileges []model.ShortPrivilege) bool {
|
||||
for _, privilege := range privileges {
|
||||
if privilege.PrivilegeID == "quizUnlimTime" {
|
||||
return IsPrivilegeExpired(privilege)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func IsPrivilegeExpired(privilege model.ShortPrivilege) bool {
|
||||
expirationTime := privilege.CreatedAt.Add(time.Duration(privilege.Amount) * 24 * time.Hour)
|
||||
|
||||
currentTime := time.Now()
|
||||
return currentTime.Before(expirationTime)
|
||||
}
|
||||
|
||||
func ExtractEmail(key string) string {
|
||||
parts := strings.Split(key, ":")
|
||||
if len(parts) != 2 {
|
||||
return ""
|
||||
}
|
||||
return parts[1]
|
||||
}
|
||||
|
||||
func HasQuizCntPrivilege(privileges []model.ShortPrivilege) *model.ShortPrivilege {
|
||||
for _, privilege := range privileges {
|
||||
if privilege.PrivilegeID == "quizCnt" && privilege.Amount > 0 {
|
||||
return &privilege
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToJSON(data interface{}) (string, error) {
|
||||
result, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
43
workers/shortstat/timeout.go
Normal file
43
workers/shortstat/timeout.go
Normal file
@ -0,0 +1,43 @@
|
||||
package shortstat
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/workers"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ShortStat struct of worker for expiration worker
|
||||
type ShortStat struct {
|
||||
w *workers.Worker
|
||||
d *dal.DAL
|
||||
}
|
||||
|
||||
// New creation of worker
|
||||
func New(d *dal.DAL, p time.Duration) *ShortStat {
|
||||
return &ShortStat{
|
||||
w: workers.New(p),
|
||||
d: d,
|
||||
}
|
||||
}
|
||||
|
||||
// Start method for starting worker with long polling from postgres
|
||||
func (t *ShortStat) Start(ctx context.Context) {
|
||||
t.w.Start(ctx, func(ctx context.Context) error {
|
||||
fmt.Println("SHORTSTAT1")
|
||||
if err := t.d.WorkerRepo.WorkerStatProcess(ctx); err != nil {
|
||||
fmt.Println("SHORTSTAT2", err)
|
||||
if err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *ShortStat) ExposeErr(ctx context.Context, err *error) {
|
||||
t.w.ExposeErr(ctx, err)
|
||||
}
|
40
workers/timeout/timeout.go
Normal file
40
workers/timeout/timeout.go
Normal file
@ -0,0 +1,40 @@
|
||||
package timeout
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/dal"
|
||||
"penahub.gitlab.yandexcloud.net/backend/quiz/worker.git/workers"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timeout struct of worker for expiration worker
|
||||
type Timeout struct {
|
||||
w *workers.Worker
|
||||
d *dal.DAL
|
||||
}
|
||||
|
||||
// New creation of worker
|
||||
func New(d *dal.DAL, p time.Duration) *Timeout {
|
||||
return &Timeout{
|
||||
w: workers.New(p),
|
||||
d: d,
|
||||
}
|
||||
}
|
||||
|
||||
// Start method for starting worker with long polling from postgres
|
||||
func (t *Timeout) Start(ctx context.Context) {
|
||||
t.w.Start(ctx, func(ctx context.Context) error {
|
||||
if err := t.d.WorkerRepo.WorkerTimeoutProcess(ctx); err != nil {
|
||||
if err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (t *Timeout) ExposeErr(ctx context.Context, err *error) {
|
||||
t.w.ExposeErr(ctx, err)
|
||||
}
|
55
workers/worker.go
Normal file
55
workers/worker.go
Normal file
@ -0,0 +1,55 @@
|
||||
package workers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
period time.Duration
|
||||
errChan chan error
|
||||
}
|
||||
|
||||
// New creation of worker
|
||||
func New(p time.Duration) *Worker {
|
||||
return &Worker{
|
||||
period: p,
|
||||
errChan: make(chan error),
|
||||
}
|
||||
}
|
||||
|
||||
// Start method for starting worker with long polling from postgres
|
||||
func (t *Worker) Start(ctx context.Context, job func(ctx context.Context) error) {
|
||||
metronome := time.Tick(t.period)
|
||||
for {
|
||||
select {
|
||||
case <-metronome:
|
||||
func() {
|
||||
defer func() {
|
||||
if v := recover(); v != any(nil) {
|
||||
t.errChan <- fmt.Errorf("%v", v)
|
||||
}
|
||||
}()
|
||||
if err := job(ctx); err != nil {
|
||||
t.errChan <- err
|
||||
}
|
||||
}()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Worker) ExposeErr(ctx context.Context, err *error) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case e := <-t.errChan:
|
||||
err = &e
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
Loading…
Reference in New Issue
Block a user