portowyi 1 year ago
commit
6b2e51763a
17 changed files with 760 additions and 0 deletions
  1. 10 0
      .env
  2. 10 0
      .env.local
  3. 2 0
      .gitignore
  4. 61 0
      controller/authentication.go
  5. 36 0
      controller/cdn.go
  6. 45 0
      controller/products.go
  7. 141 0
      cron-job/cronJobs.go
  8. 29 0
      database/database.go
  9. 45 0
      go.mod
  10. 103 0
      go.sum
  11. 74 0
      helper/jwt.go
  12. 13 0
      helper/rootDir.go
  13. 57 0
      main.go
  14. 19 0
      middleware/jwtAuth.go
  15. 6 0
      model/authenticationInput.go
  16. 53 0
      model/product.go
  17. 56 0
      model/user.go

+ 10 - 0
.env

@@ -0,0 +1,10 @@
+#POSTGRES credentials
+DB_HOST=localhost #and will be DB_HOST=postgres/DB_HOST=pg_tefo from container
+DB_PORT=5432
+DB_USER=postgres
+DB_PASSWORD=postgres
+DB_NAME=kng_feed_api
+
+# Authentication credentials
+TOKEN_TTL="2000"
+JWT_PRIVATE_KEY="Kq4NZSrtRkyD5NqzjkeHlw"

+ 10 - 0
.env.local

@@ -0,0 +1,10 @@
+#POSTGRES credentials
+DB_HOST=localhost #and will be DB_HOST=postgres/DB_HOST=pg_tefo from container
+DB_PORT=5432
+DB_USER=postgres
+DB_PASSWORD=postgres
+DB_NAME=kng_feed_api
+
+# Authentication credentials
+TOKEN_TTL="2000"
+JWT_PRIVATE_KEY="Kq4NZSrtRkyD5NqzjkeHlw"

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+feed.xml
+.vscode

+ 61 - 0
controller/authentication.go

@@ -0,0 +1,61 @@
+package controller
+
+import (
+	"kng_feed_api/helper"
+	"kng_feed_api/model"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func Register(context *gin.Context) {
+	var input model.AuthenticationInput
+
+	if err := context.ShouldBindJSON(&input); err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	user := model.User{
+		Username: input.Username,
+		Password: input.Password,
+	}
+
+	savedUser, err := user.Save()
+
+	if err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	context.JSON(http.StatusCreated, gin.H{"user": savedUser})
+}
+
+func Login(context *gin.Context) {
+	var input model.AuthenticationInput
+
+	if err := context.ShouldBindJSON(&input); err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	user, err := model.FindUserByUsername(input.Username)
+	if err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	err = user.ValidatePassword(input.Password)
+	if err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	jwt, err := helper.GenerateJWT(user)
+	if err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	context.JSON(http.StatusOK, gin.H{"jwt": jwt})
+}

+ 36 - 0
controller/cdn.go

@@ -0,0 +1,36 @@
+package controller
+
+import (
+	"kng_feed_api/helper"
+	"net/http"
+	"os"
+	"path/filepath"
+
+	"github.com/gin-gonic/gin"
+)
+
+func SendFileToClient(context *gin.Context) {
+	var fileName = filepath.Join(helper.RootDir(), "feed.xml")
+	_, err := os.Stat(fileName)
+	if err != nil {
+		if os.IsNotExist(err) {
+			context.JSON(http.StatusInternalServerError,
+				gin.H{
+					"description": "The file does not exist yet. Contact the administrator.",
+					"err":         "ERR-001",
+				},
+			)
+			return
+		} else {
+			context.JSON(http.StatusInternalServerError,
+				gin.H{
+					"description": err.Error(),
+					"err":         "unknown error",
+				},
+			)
+			return
+		}
+	}
+	context.FileAttachment(fileName, "feed.xml")
+	context.Status(http.StatusOK)
+}

+ 45 - 0
controller/products.go

@@ -0,0 +1,45 @@
+package controller
+
+import (
+	"kng_feed_api/model"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func LoadProductsInfo(context *gin.Context) {
+	var products model.ProductsArray
+	err := context.ShouldBindJSON(&products)
+	if err != nil {
+		context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+		return
+	}
+
+	for _, prod := range products.Products {
+		product := model.Product{
+			CodeUT10:      prod.CodeUT10,
+			Name:          prod.Name,
+			Manufacturer:  prod.Manufacturer,
+			ArticleNumber: prod.ArticleNumber,
+			Brand:         prod.Brand,
+			Unit:          prod.Unit,
+			GroupLimit:    prod.GroupLimit,
+			GroupPrice:    prod.GroupPrice,
+			NumberCatalog: prod.NumberCatalog,
+			NumberDrawing: prod.NumberDrawing,
+			NumberBrand:   prod.NumberBrand,
+			NumberPrefix:  prod.NumberPrefix,
+			NumberArticle: prod.NumberArticle,
+			NumberSuffix:  prod.NumberSuffix,
+			CategoryId:    prod.CategoryId,
+		}
+		_, err := product.Save()
+		if err != nil {
+			context.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+			return
+		}
+	}
+
+	context.JSON(http.StatusCreated, gin.H{"ok": "Saved"})
+
+}

+ 141 - 0
cron-job/cronJobs.go

@@ -0,0 +1,141 @@
+package cronjob
+
+import (
+	"encoding/xml"
+	"fmt"
+	"kng_feed_api/helper"
+	"kng_feed_api/model"
+	"os"
+	"path/filepath"
+	"time"
+)
+
+func CreateXMLFeed() {
+
+	type Category struct {
+		Text     string `xml:",chardata"`
+		Id       string `xml:"id,attr"`
+		ParentId string `xml:"parentId,attr"`
+		URL      string `xml:"url,attr"`
+	}
+
+	type Param struct {
+		Text string `xml:",chardata"`
+		Name string `xml:"name,attr"`
+	}
+
+	type Offer struct {
+		Id         string  `xml:"id,attr"`
+		Avalieble  string  `xml:"available,attr"`
+		Name       string  `xml:"name"`
+		URL        string  `xml:"url"`
+		CategoryId string  `xml:"categoryId"`
+		Vendor     string  `xml:"vendor"`
+		Param      []Param `xml:"param"`
+	}
+
+	type Shop struct {
+		Name     string     `xml:"name"`
+		URL      string     `xml:"url"`
+		Category []Category `xml:"categories>category"`
+		Offer    []Offer    `xml:"offers>offer"`
+	}
+
+	type YamlCatalog struct {
+		XMLName xml.Name `xml:"yml_catalog"`
+		Date    string   `xml:"date,attr"`
+		Shop    Shop     `xml:"shop"`
+	}
+
+	fmt.Println("CRON JOB WORK - FROM EXTERNAL FILE ", time.Now())
+	rows, err := model.GetAllProducts()
+	if err != nil {
+		return
+	}
+
+	var yml_catalog = &YamlCatalog{}
+
+	time_now := time.Now().UTC()
+	yml_catalog.Date = time_now.Format("2006-01-02 18:15")
+
+	var shop = &Shop{}
+	shop.Name = "ООО \"Княгиня\""
+	shop.URL = "https://b2bn.kngnn.ru"
+
+	for _, value := range rows {
+		var offer = Offer{
+			Name:       value.Name,
+			URL:        "https://b2bn.kngnn.ru/?item-code=" + value.CodeUT10,
+			CategoryId: value.CategoryId,
+			Vendor:     value.Manufacturer,
+		}
+		offer.Param = append(offer.Param, Param{
+			Text: value.CodeUT10,
+			Name: "code",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.CodeUT10,
+			Name: "code_ut10",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.Manufacturer,
+			Name: "manufacturer",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.ArticleNumber,
+			Name: "article_number",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.Brand,
+			Name: "brand",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.Unit,
+			Name: "unit",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.GroupLimit,
+			Name: "group_limit",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.GroupPrice,
+			Name: "group_price",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.NumberCatalog,
+			Name: "number_catalog",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.NumberDrawing,
+			Name: "number_drawing",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.NumberBrand,
+			Name: "number_brand",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.NumberArticle,
+			Name: "number_article",
+		})
+		offer.Param = append(offer.Param, Param{
+			Text: value.NumberSuffix,
+			Name: "number_suffix",
+		})
+
+		shop.Offer = append(shop.Offer, offer)
+
+	}
+
+	yml_catalog.Shop = *shop
+
+	byteXmlText, err := xml.MarshalIndent(yml_catalog, " ", " ")
+
+	if err != nil {
+		fmt.Printf("error: %v\n", err)
+	} else {
+		var Header = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
+		var byteXmlText_withHeasder = []byte(Header + string(byteXmlText))
+
+		os.WriteFile(filepath.Join(helper.RootDir(), "feed.xml"), byteXmlText_withHeasder, 0666)
+	}
+}

+ 29 - 0
database/database.go

@@ -0,0 +1,29 @@
+package database
+
+import (
+	"fmt"
+	"os"
+
+	"gorm.io/driver/postgres"
+	"gorm.io/gorm"
+)
+
+var Database *gorm.DB
+
+func Connect() {
+	var err error
+	host := os.Getenv("DB_HOST")
+	username := os.Getenv("DB_USER")
+	password := os.Getenv("DB_PASSWORD")
+	databaseName := os.Getenv("DB_NAME")
+	port := os.Getenv("DB_PORT")
+
+	dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Africa/Lagos", host, username, password, databaseName, port)
+	Database, err = gorm.Open(postgres.Open(dsn), &gorm.Config{})
+
+	if err != nil {
+		panic(err)
+	} else {
+		fmt.Println("Successfully connected to the database")
+	}
+}

+ 45 - 0
go.mod

@@ -0,0 +1,45 @@
+module kng_feed_api
+
+go 1.23.0
+
+require (
+	github.com/bytedance/sonic v1.11.6 // indirect
+	github.com/bytedance/sonic/loader v0.1.1 // indirect
+	github.com/cloudwego/base64x v0.1.4 // indirect
+	github.com/cloudwego/iasm v0.2.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/gin-gonic/gin v1.10.0 // indirect
+	github.com/go-playground/locales v0.14.1 // indirect
+	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/go-playground/validator/v10 v10.20.0 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
+	github.com/jackc/pgpassfile v1.0.0 // indirect
+	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
+	github.com/jackc/pgx/v5 v5.5.5 // indirect
+	github.com/jackc/puddle/v2 v2.2.1 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
+	github.com/joho/godotenv v1.5.1 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+	github.com/leodido/go-urn v1.4.0 // indirect
+	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/robfig/cron v1.2.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.12 // indirect
+	golang.org/x/arch v0.8.0 // indirect
+	golang.org/x/crypto v0.26.0 // indirect
+	golang.org/x/net v0.25.0 // indirect
+	golang.org/x/sync v0.8.0 // indirect
+	golang.org/x/sys v0.23.0 // indirect
+	golang.org/x/text v0.17.0 // indirect
+	google.golang.org/protobuf v1.34.1 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	gorm.io/driver/postgres v1.5.9 // indirect
+	gorm.io/gorm v1.25.11 // indirect
+)

+ 103 - 0
go.sum

@@ -0,0 +1,103 @@
+github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
+github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
+github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
+github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
+github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
+github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
+github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
+github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
+github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
+github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
+github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
+github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
+github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
+github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
+github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
+github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
+github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
+github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
+github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
+github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
+golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
+golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
+golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
+golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
+golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
+golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
+golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
+google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
+gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
+gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
+gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

+ 74 - 0
helper/jwt.go

@@ -0,0 +1,74 @@
+package helper
+
+import (
+	"errors"
+	"fmt"
+	"kng_feed_api/model"
+	"os"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/gin-gonic/gin"
+	"github.com/golang-jwt/jwt/v4"
+)
+
+var privateKey = []byte(os.Getenv("JWT_PRIVATE_KEY"))
+
+func GenerateJWT(user model.User) (string, error) {
+	tokenTTL, _ := strconv.Atoi(os.Getenv("TOKEN_TTL"))
+	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
+		"id":  user.ID,
+		"iat": time.Now().Unix(),
+		"eat": time.Now().Add(time.Second * time.Duration(tokenTTL)).Unix(),
+	})
+	return token.SignedString(privateKey)
+}
+
+func ValidateJWT(context *gin.Context) error {
+	token, err := getToken(context)
+	if err != nil {
+		return err
+	}
+	_, ok := token.Claims.(jwt.MapClaims)
+	if ok && token.Valid {
+		return nil
+	}
+	return errors.New("invalid token provided")
+}
+
+func CurentUser(context *gin.Context) (model.User, error) {
+	err := ValidateJWT(context)
+	if err != nil {
+		return model.User{}, err
+	}
+	token, _ := getToken(context)
+	claims, _ := token.Claims.(jwt.MapClaims)
+	userId := uint(claims["id"].(float64))
+
+	user, err := model.FindUserById(userId)
+	if err != nil {
+		return model.User{}, err
+	}
+	return user, nil
+}
+
+func getToken(context *gin.Context) (*jwt.Token, error) {
+	tokenString := getTokenFromRequest(context)
+	token, err := jwt.Parse(tokenString, func(t *jwt.Token) (interface{}, error) {
+		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
+			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
+		}
+		return privateKey, nil
+	})
+	return token, err
+}
+
+func getTokenFromRequest(context *gin.Context) string {
+	bearerToken := context.Request.Header.Get("Autorization")
+	splitToken := strings.Split(bearerToken, " ")
+	if len(splitToken) == 2 {
+		return splitToken[1]
+	}
+	return ""
+}

+ 13 - 0
helper/rootDir.go

@@ -0,0 +1,13 @@
+package helper
+
+import (
+	"path"
+	"path/filepath"
+	"runtime"
+)
+
+func RootDir() string {
+	_, b, _, _ := runtime.Caller(0)
+	d := path.Join(path.Dir(b))
+	return filepath.Dir(d)
+}

+ 57 - 0
main.go

@@ -0,0 +1,57 @@
+package main
+
+import (
+	"fmt"
+	"kng_feed_api/controller"
+	cronjob "kng_feed_api/cron-job"
+	"kng_feed_api/database"
+	"kng_feed_api/model"
+	"log"
+
+	"github.com/gin-gonic/gin"
+	"github.com/joho/godotenv"
+	"github.com/robfig/cron"
+)
+
+func main() {
+	loadEnv()
+	loadDatabase()
+	startCronJob()
+	serveApplication()
+}
+
+func startCronJob() {
+	cron := cron.New()
+	cron.AddFunc("@every 15s", cronjob.CreateXMLFeed)
+	cron.Start()
+}
+
+func loadDatabase() {
+	database.Connect()
+	database.Database.AutoMigrate(&model.User{})
+	database.Database.AutoMigrate(model.Product{})
+}
+
+func loadEnv() {
+	err := godotenv.Load(".env.local")
+	if err != nil {
+		log.Fatal("Error loading .env file")
+	}
+}
+
+func serveApplication() {
+	router := gin.Default()
+
+	router.StaticFile("/feed.xml", "./feed.xml") //filepath.Join(helper.RootDir()))
+
+	publicRoutes := router.Group("/auth")
+	publicRoutes.POST("/register", controller.Register)
+	publicRoutes.POST("/login", controller.Login)
+
+	feedRoutes := router.Group("/feed")
+	feedRoutes.POST("/products", controller.LoadProductsInfo)
+	feedRoutes.GET("/", controller.SendFileToClient)
+
+	router.Run(":8000")
+	fmt.Println("Server running on port 8000")
+}

+ 19 - 0
middleware/jwtAuth.go

@@ -0,0 +1,19 @@
+package middleware
+
+import (
+	"kng_feed_api/helper"
+	"net/http"
+
+	"github.com/gin-gonic/gin"
+)
+
+func JWTAuthMiddleware() gin.HandlerFunc {
+	return func(ctx *gin.Context) {
+		err := helper.ValidateJWT(ctx)
+		if err != nil {
+			ctx.JSON(http.StatusUnauthorized, gin.H{"error": "Authentication required"})
+			ctx.Abort()
+		}
+		ctx.Next()
+	}
+}

+ 6 - 0
model/authenticationInput.go

@@ -0,0 +1,6 @@
+package model
+
+type AuthenticationInput struct {
+	Username string `json:"username" binding:"required"`
+	Password string `json:"password" binding:"required"`
+}

+ 53 - 0
model/product.go

@@ -0,0 +1,53 @@
+package model
+
+import (
+	"kng_feed_api/database"
+)
+
+type Product struct {
+	CodeUT10      string `gorm:"size:6;not null; unique; index; primaryKey" json:"code_ut10"`
+	Name          string `gorm:"size:100" json:"name"`
+	Manufacturer  string `gorm:"size:50" json:"manufacturer"`
+	ArticleNumber string `gorm:"size:50" json:"article_number"`
+	Brand         string `gorm:"size:100" json:"brand"`
+	Unit          string `gorm:"size:50" json:"unit"`
+	GroupLimit    string `gorm:"size:2" json:"group_limit"`
+	GroupPrice    string `gorm:"size:50" json:"group_price"`
+	NumberCatalog string `gorm:"size:50" json:"number_catalog"`
+	NumberDrawing string `gorm:"size:50" json:"number_drawing"`
+	NumberBrand   string `gorm:"size:10" json:"number_brand"`
+	NumberPrefix  string `gorm:"size:4" json:"number_prefix"`
+	NumberArticle string `gorm:"size:25" json:"number_article"`
+	NumberSuffix  string `gorm:"size:3" json:"number_suffix"`
+	CategoryId    string `gorm:"size:5" json:"categoryId"`
+}
+
+type ProductsArray struct {
+	Products []Product `binding:"dive" json:"products"`
+}
+
+func GetAllProducts() ([]Product, error) {
+	var products []Product
+	result := database.Database.Find(&products)
+	if result.Error != nil {
+		return products, result.Error
+	}
+	return products, nil
+}
+
+func GetProductsCount() (int, error) {
+	var products []Product
+	result := database.Database.Find(&products)
+	if result.Error != nil {
+		return 0, result.Error
+	}
+	return int(result.RowsAffected), nil
+}
+
+func (product *Product) Save() (*Product, error) {
+	err := database.Database.Save(&product).Error
+	if err != nil {
+		return &Product{}, err
+	}
+	return product, nil
+}

+ 56 - 0
model/user.go

@@ -0,0 +1,56 @@
+package model
+
+import (
+	"html"
+	"kng_feed_api/database"
+	"strings"
+
+	"golang.org/x/crypto/bcrypt"
+	"gorm.io/gorm"
+)
+
+type User struct {
+	gorm.Model
+	Username string `gorm:"size:225;not null;unique" json:"username"`
+	Password string `gorm:"size:225;not null" json:"-"`
+}
+
+func (user *User) Save() (*User, error) {
+	err := database.Database.Create(&user).Error
+	if err != nil {
+		return &User{}, err
+	}
+	return user, nil
+}
+
+func (user *User) BeforeSave(*gorm.DB) error {
+	passwordHash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
+	if err != nil {
+		return err
+	}
+	user.Password = string(passwordHash)
+	user.Username = html.EscapeString(strings.TrimSpace(user.Username))
+	return nil
+}
+
+func (user *User) ValidatePassword(password string) error {
+	return bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
+}
+
+func FindUserByUsername(username string) (User, error) {
+	var user User
+	err := database.Database.Where("username=?", username).Find(&user).Error
+	if err != nil {
+		return User{}, err
+	}
+	return user, nil
+}
+
+func FindUserById(id uint) (User, error) {
+	var user User
+	err := database.Database.Where("ID=?", id).Find(&user).Error
+	if err != nil {
+		return User{}, err
+	}
+	return user, nil
+}