diff --git a/app/app.go b/app/app.go
index 1483f45..a728467 100644
--- a/app/app.go
+++ b/app/app.go
@@ -51,10 +51,10 @@ type Options struct {
NumberPort string `env:"PORT" default:"1488"`
CrtFile string `env:"CRT" default:"server.crt"`
KeyFile string `env:"KEY" default:"server.key"`
- PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=5432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
+ PostgresCredentials string `env:"PG_CRED" default:"host=localhost port=35432 user=squiz password=Redalert2 dbname=squiz sslmode=disable"`
HubAdminUrl string `env:"HUB_ADMIN_URL" default:"http://localhost:8001/"`
ServiceName string `env:"SERVICE_NAME" default:"squiz"`
- AuthServiceURL string `env:"AUTH_URL"`
+ AuthServiceURL string `env:"AUTH_URL" default:"http://localhost:8000/"`
RedirectURL string `env:"REDIRECT_URL" default:"https://squiz.pena.digital"`
PubKey string `env:"PUBLIC_KEY"`
PrivKey string `env:"PRIVATE_KEY"`
diff --git a/go.mod b/go.mod
index 2f560fc..68fcedc 100644
--- a/go.mod
+++ b/go.mod
@@ -10,9 +10,9 @@ require (
github.com/pioz/faker v1.7.3
github.com/skeris/appInit v1.0.2
github.com/stretchr/testify v1.8.4
- github.com/tealeg/xlsx v1.0.5
github.com/themakers/bdd v0.0.0-20210316111417-6b1dfe326f33
github.com/themakers/hlog v0.0.0-20191205140925-235e0e4baddf
+ github.com/xuri/excelize/v2 v2.8.1
go.uber.org/zap v1.26.0
penahub.gitlab.yandexcloud.net/backend/penahub_common v0.0.0-20240202120244-c4ef330cfe5d
penahub.gitlab.yandexcloud.net/backend/quiz/common.git v0.0.0-20240325084116-830f54870853
@@ -35,16 +35,23 @@ require (
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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/richardlehane/mscfb v1.0.4 // indirect
+ github.com/richardlehane/msoleps v1.0.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/xid v1.5.0 // indirect
+ github.com/tealeg/xlsx v1.0.5 // 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
+ github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
+ github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // 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/crypto v0.19.0 // indirect
+ golang.org/x/net v0.21.0 // indirect
+ golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc v1.61.1 // indirect
diff --git a/go.sum b/go.sum
index c2f171f..b90d7d7 100644
--- a/go.sum
+++ b/go.sum
@@ -74,6 +74,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
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/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
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=
@@ -93,6 +95,11 @@ 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/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
+github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM=
+github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
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=
@@ -118,6 +125,12 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
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=
+github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 h1:Chd9DkqERQQuHpXjR/HSV1jLZA6uaoiwwH3vSuF3IW0=
+github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.8.1 h1:pZLMEwK8ep+CLIUWpWmvW8IWE/yxqG0I1xcN6cVMGuQ=
+github.com/xuri/excelize/v2 v2.8.1/go.mod h1:oli1E4C3Pa5RXg1TBXn4ENCXDV5JUMlBluUhG7c+CEE=
+github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 h1:qhbILQo1K3mphbwKh1vNm4oGezE1eF9fQWmNiIpSfI4=
+github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
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=
@@ -132,6 +145,10 @@ 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.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
+golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
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=
@@ -139,8 +156,8 @@ 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/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
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=
@@ -149,8 +166,8 @@ golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/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/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
+golang.org/x/sys v0.17.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=
diff --git a/tools/tools.go b/tools/tools.go
index 72f843d..872fd0d 100644
--- a/tools/tools.go
+++ b/tools/tools.go
@@ -1,15 +1,35 @@
package tools
import (
- "github.com/tealeg/xlsx"
+ "github.com/xuri/excelize/v2"
+ _ "image/gif"
+ _ "image/jpeg"
+ _ "image/png"
"io"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "path/filepath"
"penahub.gitlab.yandexcloud.net/backend/quiz/common.git/model"
+ "regexp"
"sort"
+ "strconv"
+ "strings"
+)
+
+const (
+ bucketImages = "squizimages"
+ bucketFonts = "squizfonts"
+ bucketScripts = "squizscript"
+ bucketStyle = "squizstyle"
+ bucketAnswers = "squizanswer"
)
func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []model.Answer) error {
- file := xlsx.NewFile()
- sheet, err := file.AddSheet("Results")
+ file := excelize.NewFile()
+ sheet := "Sheet1"
+
+ _, err := file.NewSheet(sheet)
if err != nil {
return err
}
@@ -34,10 +54,11 @@ func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []mo
headers = append(headers, "Результат")
// добавляем заголовки в первую строку
- row := sheet.AddRow()
- for _, header := range headers {
- cell := row.AddCell()
- cell.Value = header
+ for col, header := range headers {
+ cell := ToAlphaString(col+1) + "1"
+ if err := file.SetCellValue(sheet, cell, header); err != nil {
+ return err
+ }
}
// мапа для хранения обычных ответов респондентов
@@ -49,18 +70,20 @@ func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []mo
// заполняем мапу ответами и данными респондентов
for _, answer := range answers {
if answer.Result {
- // если это результат то данные респондента берутся из контента ответа по сессии
results[answer.Session] = answer
} else {
- // если это обычный ответ то добавляем его в соответствующий список ответов респондента
standart[answer.Session] = append(standart[answer.Session], answer)
}
}
+
// записываем данные в файл
+ row := 2
for session, _ := range results {
response := standart[session]
- row := sheet.AddRow()
- row.AddCell().Value = results[session].Content // данные респондента
+ if err := file.SetCellValue(sheet, "A"+strconv.Itoa(row), results[session].Content); err != nil {
+ return err
+ }
+ count := 2
for _, q := range questions {
if !q.Deleted && q.Type != model.TypeResult {
sort.Slice(response, func(i, j int) bool {
@@ -68,18 +91,68 @@ func WriteDataToExcel(buffer io.Writer, questions []model.Question, answers []mo
})
index := binarySearch(response, q.Id)
if index != -1 {
- row.AddCell().Value = response[index].Content
+ cell := ToAlphaString(count) + strconv.Itoa(row)
+ typeMap := FileSearch(response[index].Content)
+
+ for _, tipe := range typeMap {
+ if tipe != "Text" && q.Type == model.TypeImages || q.Type == model.TypeVarImages {
+ urle := ExtractImageURL(response[index].Content)
+ urlData := strings.Split(urle, " ")
+ for _, k := range urlData {
+ u, err := url.Parse(k)
+ if err == nil && u.Scheme != "" && u.Host != "" {
+ picture, err := downloadImage(k)
+ if err != nil {
+ return err
+ }
+ file.SetColWidth(sheet, ToAlphaString(count), ToAlphaString(count), 50)
+ file.SetRowHeight(sheet, row, 150)
+ if err := file.AddPictureFromBytes(sheet, cell, picture); err != nil {
+ return err
+ }
+ } else {
+ if err := file.SetCellValue(sheet, cell, k); err != nil {
+ return err
+ }
+ }
+ }
+ } else if tipe != "Text" && q.Type == model.TypeFile {
+ urle := ExtractImageURL(response[index].Content)
+ display, tooltip := urle, urle
+ if err := file.SetCellValue(sheet, cell, response[index].Content); err != nil {
+ return err
+ }
+ if err := file.SetCellHyperLink(sheet, cell, urle, "External", excelize.HyperlinkOpts{
+ Display: &display,
+ Tooltip: &tooltip,
+ }); err != nil {
+ return err
+ }
+ } else if q.Type != model.TypeFile {
+ if err := file.SetCellValue(sheet, cell, response[index].Content); err != nil {
+ return err
+ }
+ }
+ }
+
} else {
- row.AddCell().Value = "-"
+ cell := ToAlphaString(count) + strconv.Itoa(row)
+ if err := file.SetCellValue(sheet, cell, "-"); err != nil {
+ return err
+ }
}
+ count++
}
}
- row.AddCell().Value = mapQueRes[results[session].QuestionId]
+ cell := ToAlphaString(len(headers)) + strconv.Itoa(row)
+ if err := file.SetCellValue(sheet, cell, mapQueRes[results[session].QuestionId]); err != nil {
+ return err
+ }
+ row++
}
// cохраняем данные в буфер
- err = file.Write(buffer)
- if err != nil {
+ if err := file.Write(buffer); err != nil {
return err
}
@@ -101,3 +174,98 @@ func binarySearch(answers []model.Answer, questionID uint64) int {
}
return -1
}
+
+func FileSearch(content string) map[string]string {
+ types := make(map[string]string)
+
+ words := strings.Fields(content)
+ for _, word := range words {
+ if strings.Contains(word, bucketImages) {
+ types[word] = FileType(word)
+ } else if strings.Contains(word, bucketFonts) {
+ types[word] = FileType(word)
+ } else if strings.Contains(word, bucketScripts) {
+ types[word] = FileType(word)
+ } else if strings.Contains(word, bucketStyle) {
+ types[word] = FileType(word)
+ } else if strings.Contains(word, bucketAnswers) {
+ types[word] = FileType(word)
+ } else {
+ types[word] = "Text"
+ }
+ }
+
+ return types
+}
+
+func FileType(filename string) string {
+ parts := strings.Split(filename, ".")
+ extension := parts[len(parts)-1]
+
+ switch extension {
+ case "png", "jpg", "jpeg", "gif", "bmp", "svg", "webp", "tiff", "ico":
+ return "Image"
+ default:
+ return "File"
+ }
+}
+
+func downloadImage(url string) (*excelize.Picture, error) {
+ resp, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ imgData, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ ext := filepath.Ext(url)
+ if ext == "" {
+ contentType := resp.Header.Get("Content-Type")
+ switch {
+ case strings.HasPrefix(contentType, "image/jpeg"):
+ ext = ".jpg"
+ case strings.HasPrefix(contentType, "image/png"):
+ ext = ".png"
+ default:
+ ext = ".png"
+ }
+ }
+
+ pic := &excelize.Picture{
+ Extension: ext,
+ File: imgData,
+ Format: &excelize.GraphicOptions{
+ AutoFit: true,
+ Positioning: "oneCell",
+ },
+ }
+ return pic, nil
+}
+
+func ToAlphaString(col int) string {
+ var result string
+ for col > 0 {
+ col--
+ result = string(rune('A'+col%26)) + result
+ col /= 26
+ }
+ return result
+}
+
+func ExtractImageURL(htmlContent string) string {
+ re := regexp.MustCompile(`(?:]*src="([^"]+)"[^>]*>)|(?: