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 }