package importer
import (
"context"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"unicode/utf16"
"git.neonxp.ru/neonxp/guessr/pkg/config"
"git.neonxp.ru/neonxp/guessr/pkg/db"
"git.neonxp.ru/neonxp/guessr/pkg/model"
"github.com/urfave/cli/v3"
)
var Command = &cli.Command{
Name: "import",
Usage: "import json with places",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "download-img",
Usage: "download images locally",
},
},
ArgsUsage: "path to json file",
Action: func(ctx context.Context, c *cli.Command) error {
downloadImg := c.Bool("download-img")
cfg, err := config.New()
if err != nil {
return err
}
dbClient := db.New(cfg.DB)
fp, err := os.Open(c.Args().First())
if err != nil {
return err
}
defer fp.Close()
places := Places{}
dec := json.NewDecoder(fp)
if err := dec.Decode(&places); err != nil {
return err
}
for _, p := range places {
decodedValue := unescape(p.Name)
h := hash(p.Img)
prefix := h[0:1]
path := filepath.Join(".", "static", "places", prefix)
file := filepath.Join(path, h+".jpg")
if downloadImg {
//nolint:gomnd
if err := os.MkdirAll(path, 0o777); err != nil {
return err
}
if err := downloadFile(p.Img, file); err != nil {
return err
}
}
np := model.Place{
GUID: p.GUID,
Img: strings.Replace(file, "static", "", 1),
Name: strings.ReplaceAll(decodedValue, "�", `"`),
Position: &model.Point{
Lat: p.Position.Lat,
Lon: p.Position.Lng,
},
}
if _, err := dbClient.NewInsert().Model(&np).Exec(ctx); err != nil {
return err
}
}
return nil
},
}
type Place struct {
GUID string `json:"guid"`
Position PositionEntity `json:"position"`
Img string `json:"img"`
Name string `json:"name"`
}
type PositionEntity struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type Places []Place
func hash(in string) string {
hasher := sha256.New()
if _, err := hasher.Write([]byte(in)); err != nil {
return ""
}
return base64.URLEncoding.EncodeToString(hasher.Sum(nil))
}
func unescape(input string) string {
output := make([]rune, 0, len(input))
length := len(input)
for index := 0; index < length; {
//nolint:nestif
if input[index] == '%' {
if index <= length-6 && input[index+1] == 'u' {
byte16, err := hex.DecodeString(input[index+2 : index+6])
if err == nil {
//nolint:gomnd
value := uint16(byte16[0])<<8 + uint16(byte16[1])
chr := utf16.Decode([]uint16{value})[0]
output = append(output, chr)
index += 6
continue
}
}
if index <= length-3 {
byte8, err := hex.DecodeString(input[index+1 : index+3])
if err == nil {
value := uint16(byte8[0])
chr := utf16.Decode([]uint16{value})[0]
output = append(output, chr)
index += 3
continue
}
}
}
output = append(output, rune(input[index]))
index++
}
return string(output)
}
func downloadFile(fileURL, fileName string) error {
//nolint:gosec,noctx
response, err := http.Get(fileURL)
if err != nil {
return err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return errors.New("received non 200 response code")
}
// Create a empty file
file, err := os.Create(fileName)
if err != nil {
return err
}
defer file.Close()
// Write the bytes to the fiel
_, err = io.Copy(file, response.Body)
if err != nil {
return err
}
return nil
}