Compare commits
4 Commits
main
...
pubjs_augm
Author | SHA1 | Date | |
---|---|---|---|
c901e52844 | |||
1cfb0f1b86 | |||
3164b42bc4 | |||
988df9d658 |
196
service/frontend_model.go
Normal file
196
service/frontend_model.go
Normal file
@ -0,0 +1,196 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.pena/SQuiz/common/model"
|
||||
)
|
||||
|
||||
type Props struct {
|
||||
QuizSettings *QuizSettings `json:"quizSettings,omitempty"`
|
||||
QuizID string `json:"quizId"`
|
||||
//Preview *bool `json:"preview,omitempty"`
|
||||
//ChangeFaviconAndTitle *bool `json:"changeFaviconAndTitle,omitempty"`
|
||||
//ClassName *string `json:"className,omitempty"`
|
||||
//DisableGlobalCss *bool `json:"disableGlobalCss,omitempty"`
|
||||
}
|
||||
|
||||
type QuizSettings struct {
|
||||
Questions []Question `json:"questions"`
|
||||
Settings Settings `json:"settings"`
|
||||
Cnt uint64 `json:"cnt"`
|
||||
RecentlyCompleted bool `json:"recentlyCompleted"`
|
||||
ShowBadge bool `json:"show_badge"`
|
||||
}
|
||||
|
||||
type Question struct {
|
||||
BackendID int `json:"backendId"`
|
||||
ID string `json:"id"`
|
||||
QuizID int `json:"quizId"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Page int `json:"page"`
|
||||
Type *string `json:"type"`
|
||||
Expanded bool `json:"expanded"`
|
||||
OpenedModalSettings bool `json:"openedModalSettings"`
|
||||
Deleted bool `json:"deleted"`
|
||||
Required bool `json:"required"`
|
||||
DeleteTimeoutID int `json:"deleteTimeoutId"`
|
||||
Content Content `json:"content"`
|
||||
}
|
||||
|
||||
type Content struct {
|
||||
ID string `json:"id"`
|
||||
Hint Hint `json:"hint"`
|
||||
Rule Rule `json:"rule"`
|
||||
Back *string `json:"back"`
|
||||
OriginalBack *string `json:"originalBack"`
|
||||
Autofill bool `json:"autofill"`
|
||||
}
|
||||
|
||||
type Hint struct {
|
||||
Text string `json:"text"`
|
||||
Video string `json:"video"`
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Children []interface{} `json:"children"`
|
||||
Default string `json:"default"`
|
||||
Main []interface{} `json:"main"`
|
||||
ParentID string `json:"parentId"`
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
FP bool `json:"fp"`
|
||||
REP bool `json:"rep"`
|
||||
Name string `json:"name"`
|
||||
CFG Config `json:"cfg"`
|
||||
LIM int `json:"lim"`
|
||||
DUE int `json:"due"`
|
||||
Delay int `json:"delay"`
|
||||
Pausable bool `json:"pausable"`
|
||||
Status string `json:"status"` // "start" | "stop" | "ai"
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
IsUnSc *bool `json:"isUnSc,omitempty"`
|
||||
Spec *bool `json:"spec,omitempty"`
|
||||
Type *string `json:"type"` // "quiz" | "form" | null
|
||||
NoStartPage bool `json:"noStartPage"`
|
||||
StartpageType *string `json:"startpageType"` // "standard" | "expanded" | "centered"
|
||||
Score *bool `json:"score,omitempty"`
|
||||
Results *bool `json:"results"`
|
||||
HaveRoot *string `json:"haveRoot"`
|
||||
Theme string `json:"theme"`
|
||||
Design bool `json:"design"`
|
||||
ResultInfo ResultInfo `json:"resultInfo"`
|
||||
Startpage Startpage `json:"startpage"`
|
||||
FormContact FormContact `json:"formContact"`
|
||||
Info Info `json:"info"`
|
||||
Meta string `json:"meta"`
|
||||
Antifraud *bool `json:"antifraud,omitempty"`
|
||||
Showfc *bool `json:"showfc,omitempty"`
|
||||
YandexMetricsNumber *int `json:"yandexMetricsNumber,omitempty"`
|
||||
VkMetricsNumber *int `json:"vkMetricsNumber,omitempty"`
|
||||
}
|
||||
|
||||
type ResultInfo struct {
|
||||
When string `json:"when"` // "email" | ""
|
||||
Share bool `json:"share"`
|
||||
Replay bool `json:"replay"`
|
||||
Theme string `json:"theme"`
|
||||
Reply string `json:"reply"`
|
||||
Replname string `json:"replname"`
|
||||
ShowResultForm string `json:"showResultForm"` // "before" | "after"
|
||||
}
|
||||
|
||||
type Startpage struct {
|
||||
Description string `json:"description"`
|
||||
Button string `json:"button"`
|
||||
Position string `json:"position"` // "left" | "right" | "center"
|
||||
FavIcon *string `json:"favIcon"`
|
||||
Logo *string `json:"logo"`
|
||||
OriginalLogo *string `json:"originalLogo"`
|
||||
Background Background `json:"background"`
|
||||
}
|
||||
|
||||
type Background struct {
|
||||
Type *string `json:"type"` // null | "image" | "video"
|
||||
Desktop *string `json:"desktop"`
|
||||
OriginalDesktop *string `json:"originalDesktop"`
|
||||
Mobile *string `json:"mobile"`
|
||||
OriginalMobile *string `json:"originalMobile"`
|
||||
Video *string `json:"video"`
|
||||
Cycle bool `json:"cycle"`
|
||||
}
|
||||
|
||||
type FormContact struct {
|
||||
Title string `json:"title"`
|
||||
Desc string `json:"desc"`
|
||||
Fields map[string]interface{} `json:"fields"`
|
||||
Button string `json:"button"`
|
||||
}
|
||||
|
||||
type Info struct {
|
||||
PhoneNumber string `json:"phonenumber"`
|
||||
Clickable bool `json:"clickable"`
|
||||
OrgName string `json:"orgname"`
|
||||
Site string `json:"site"`
|
||||
Law *string `json:"law,omitempty"`
|
||||
}
|
||||
|
||||
func dao2dtoProps(quiz model.Quiz, questions []model.Question, showBadge bool, cnt uint64) Props {
|
||||
var cfg Config
|
||||
err := json.Unmarshal([]byte(quiz.Config), &cfg)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to unmarshal quiz.Config: %v\n", err)
|
||||
cfg = Config{}
|
||||
}
|
||||
|
||||
return Props{
|
||||
QuizID: quiz.Qid,
|
||||
QuizSettings: &QuizSettings{
|
||||
Settings: Settings{
|
||||
FP: quiz.Fingerprinting,
|
||||
REP: quiz.Repeatable,
|
||||
Name: quiz.Name,
|
||||
CFG: cfg,
|
||||
LIM: int(quiz.Limit),
|
||||
DUE: int(quiz.DueTo),
|
||||
Delay: int(quiz.TimeOfPassing),
|
||||
Pausable: quiz.Pausable,
|
||||
Status: quiz.Status,
|
||||
},
|
||||
Cnt: cnt,
|
||||
ShowBadge: showBadge,
|
||||
Questions: dao2dtoQuestions(int(quiz.Id), questions),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func dao2dtoQuestions(quizId int, questions []model.Question) []Question {
|
||||
var res []Question
|
||||
for _, q := range questions {
|
||||
var content Content
|
||||
err := json.Unmarshal([]byte(q.Content), &content)
|
||||
if err != nil {
|
||||
fmt.Printf("failed to unmarshal question.Content for ID %d: %v\n", q.Id, err)
|
||||
content = Content{}
|
||||
}
|
||||
|
||||
t := q.Type
|
||||
res = append(res, Question{
|
||||
BackendID: int(q.Id),
|
||||
//ID: "",
|
||||
QuizID: quizId,
|
||||
Title: q.Title,
|
||||
Description: q.Description,
|
||||
Page: q.Page,
|
||||
Type: &t,
|
||||
Deleted: q.Deleted,
|
||||
Required: q.Required,
|
||||
Content: content,
|
||||
})
|
||||
}
|
||||
return res
|
||||
}
|
@ -12,6 +12,7 @@ import (
|
||||
"gitea.pena/SQuiz/common/model"
|
||||
"gitea.pena/SQuiz/common/utils"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -36,6 +37,8 @@ type Service struct {
|
||||
encrypt *utils.Encrypt
|
||||
redirectURl string
|
||||
aiClient *clients.AIClient
|
||||
|
||||
script scriptTemplate
|
||||
}
|
||||
|
||||
type ServiceDeps struct {
|
||||
@ -48,6 +51,11 @@ type ServiceDeps struct {
|
||||
AiClient *clients.AIClient
|
||||
}
|
||||
|
||||
type scriptTemplate struct {
|
||||
full []byte
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func New(deps ServiceDeps) *Service {
|
||||
return &Service{
|
||||
store: deps.Store,
|
||||
@ -66,6 +74,9 @@ func (s *Service) Register(app *fiber.App) *fiber.App {
|
||||
app.Post("/answer", s.PutAnswersOnePiece)
|
||||
app.Post("/settings", s.GetQuizData)
|
||||
app.Get("/logo", s.MiniPart)
|
||||
|
||||
app.Put("/pub.js", s.UploadScriptTemplate)
|
||||
app.Get("/pub/:qID.js", s.GetScriptTemplate)
|
||||
return app
|
||||
}
|
||||
|
||||
@ -563,3 +574,174 @@ func (s *Service) MiniPart(ctx *fiber.Ctx) error {
|
||||
|
||||
return ctx.Redirect(s.redirectURl, fiber.StatusFound)
|
||||
}
|
||||
|
||||
const marker = "window.__CONFIG__"
|
||||
|
||||
func (s *Service) UploadScriptTemplate(ctx *fiber.Ctx) error {
|
||||
fileHeader, err := ctx.FormFile("pub.js")
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("failed to get file: " + err.Error())
|
||||
}
|
||||
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to open file: " + err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
content, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to read file: " + err.Error())
|
||||
}
|
||||
|
||||
s.script.mu.Lock()
|
||||
s.script.full = content
|
||||
s.script.mu.Unlock()
|
||||
|
||||
return ctx.SendStatus(fiber.StatusNoContent)
|
||||
}
|
||||
|
||||
// возвращаем файл pub.js начало marker = маршаленные данные структуры Props и s.script.full
|
||||
func (s *Service) GetScriptTemplate(ctx *fiber.Ctx) error {
|
||||
cs, ok := ctx.Context().Value(middleware.ContextKey(middleware.SessionKey)).(string)
|
||||
if !ok {
|
||||
return ctx.Status(fiber.StatusUnauthorized).SendString("no session in cookie")
|
||||
}
|
||||
|
||||
qID := ctx.Params("qID")
|
||||
if qID == "" {
|
||||
return ctx.Status(fiber.StatusBadRequest).SendString("missing qID in path")
|
||||
}
|
||||
|
||||
hlogger := log_mw.ExtractLogger(ctx)
|
||||
|
||||
quiz, err := s.dal.QuizRepo.GetQuizByQid(ctx.Context(), qID)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
|
||||
if quiz.Status != model.StatusStart && quiz.Status != model.StatusAI {
|
||||
return ctx.Status(fiber.StatusLocked).SendString("quiz is inactive")
|
||||
}
|
||||
|
||||
if quiz.DueTo < uint64(time.Now().Unix()) && quiz.DueTo > 0 {
|
||||
return ctx.Status(fiber.StatusGone).SendString("quiz timeouted")
|
||||
}
|
||||
|
||||
account, err := s.dal.AccountRepo.GetAccountByID(ctx.Context(), quiz.AccountId)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("can`t get account by quiz.AccountId")
|
||||
}
|
||||
|
||||
showBadge := true
|
||||
if priv, ok := account.Privileges["squizHideBadge"]; ok {
|
||||
expiration := priv.CreatedAt.Add(time.Duration(priv.Amount) * 24 * time.Hour)
|
||||
|
||||
if time.Now().Before(expiration) {
|
||||
showBadge = false
|
||||
}
|
||||
}
|
||||
|
||||
var questions []model.Question
|
||||
var cnt uint64
|
||||
|
||||
if quiz.Status == model.StatusAI {
|
||||
questions, cnt, err = s.dal.QuestionRepo.GetQuestionsAI(ctx.Context(), int64(quiz.Id), cs, 100_000, 0, 0)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
} else {
|
||||
questions, cnt, err = s.dal.QuestionRepo.GetQuestionList(
|
||||
ctx.Context(),
|
||||
100_000, 0, 0, 0, quiz.Id, false, false, "", "", 0,
|
||||
)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
utmData := model.UTMSavingMap{
|
||||
"utm_content": ctx.Query("utm_content"),
|
||||
"utm_medium": ctx.Query("utm_medium"),
|
||||
"utm_campaign": ctx.Query("utm_campaign"),
|
||||
"utm_source": ctx.Query("utm_source"),
|
||||
"utm_term": ctx.Query("utm_term"),
|
||||
"utm_referrer": ctx.Query("utm_referrer"),
|
||||
"roistat": ctx.Query("roistat"),
|
||||
"referrer": ctx.Query("referrer"),
|
||||
"openstat_service": ctx.Query("openstat_service"),
|
||||
"openstat_campaign": ctx.Query("openstat_campaign"),
|
||||
"openstat_ad": ctx.Query("openstat_ad"),
|
||||
"openstat_source": ctx.Query("openstat_source"),
|
||||
"from": ctx.Query("from"),
|
||||
"gclientid": ctx.Query("gclientid"),
|
||||
"_ym_uid": ctx.Query("_ym_uid"),
|
||||
"_ym_counter": ctx.Query("_ym_counter"),
|
||||
"gclid": ctx.Query("gclid"),
|
||||
"yclid": ctx.Query("yclid"),
|
||||
"fbclid": ctx.Query("fbclid"),
|
||||
}
|
||||
|
||||
deviceType := ctx.Get("DeviceType")
|
||||
os := ctx.Get("OS")
|
||||
browser := ctx.Get("Browser")
|
||||
ip := ctx.IP()
|
||||
device := ctx.Get("Device")
|
||||
referrer := ctx.Get("Referer")
|
||||
fp := ""
|
||||
if cfp := ctx.Cookies(fingerprintCookie); cfp != "" {
|
||||
fp = cfp
|
||||
}
|
||||
|
||||
if len(questions) == 0 {
|
||||
return ctx.Status(fiber.StatusNotFound).SendString("question not found")
|
||||
}
|
||||
|
||||
answers, errs := s.dal.AnswerRepo.CreateAnswers(ctx.Context(), []model.Answer{{
|
||||
Content: "start",
|
||||
QuestionId: questions[0].Id,
|
||||
QuizId: quiz.Id,
|
||||
Start: true,
|
||||
DeviceType: deviceType,
|
||||
Device: device,
|
||||
Browser: browser,
|
||||
IP: ip,
|
||||
OS: os,
|
||||
Utm: utmData,
|
||||
}}, cs, fp, quiz.Id)
|
||||
if len(errs) != 0 {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString(errs[0].Error())
|
||||
}
|
||||
|
||||
hlogger.Emit(models.InfoQuizOpen{
|
||||
KeyOS: os,
|
||||
KeyDevice: device,
|
||||
KeyDeviceType: deviceType,
|
||||
KeyBrowser: browser,
|
||||
CtxQuiz: qID,
|
||||
CtxQuizID: int64(quiz.Id),
|
||||
CtxReferrer: referrer,
|
||||
CtxIDInt: int64(answers[0].Id),
|
||||
CtxSession: cs,
|
||||
})
|
||||
|
||||
props := dao2dtoProps(quiz, questions, showBadge, cnt)
|
||||
|
||||
propsJSON, err := json.Marshal(props)
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to marshal props")
|
||||
}
|
||||
|
||||
propsJSONString, err := json.Marshal(string(propsJSON))
|
||||
if err != nil {
|
||||
return ctx.Status(fiber.StatusInternalServerError).SendString("failed to escape props JSON")
|
||||
}
|
||||
|
||||
s.script.mu.RLock()
|
||||
defer s.script.mu.RUnlock()
|
||||
|
||||
script := fmt.Sprintf("%s = %s;\n%s", marker, string(propsJSONString), s.script.full)
|
||||
|
||||
ctx.Set("Content-Disposition", `attachment; filename="pub.js"`)
|
||||
return ctx.Type("application/javascript").Status(fiber.StatusOK).SendString(script)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user