diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..febfa9f --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +build: + go build -v ./cmd/apisirius + +run: + go run ./cmd/apisirius + + +.DEFAULT_GOAL := build diff --git a/cmd/apisirius/main.go b/cmd/apisirius/main.go new file mode 100644 index 0000000..f8ddf96 --- /dev/null +++ b/cmd/apisirius/main.go @@ -0,0 +1,28 @@ +package main + +import ( + "os" + + log "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "github.com/zhashkevych/s3-file-uploader/pkg/config" + "github.com/zhashkevych/s3-file-uploader/pkg/server" +) + +func main() { + log.SetFormatter(&log.JSONFormatter{}) + log.SetOutput(os.Stdout) + log.SetLevel(log.WarnLevel) + + if err := config.Init(); err != nil { + log.Fatalf("%s", err.Error()) + } + + accessKey := os.Getenv("ACCESS_KEY") + secretKey := os.Getenv("SECRET_KEY") + app := server.NewApp(accessKey, secretKey) + + if err := app.Run(viper.GetString("port")); err != nil { + log.Fatalf("%s", err.Error()) + } +} diff --git a/config/config.yml b/config/config.yml new file mode 100644 index 0000000..b0f5549 --- /dev/null +++ b/config/config.yml @@ -0,0 +1,5 @@ +port: 8000 + +storage: + endpoint: "localstack:4572" + bucket: "file-storage" \ No newline at end of file diff --git a/go.sum b/go.sum index 26c731f..a238250 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/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= @@ -11,6 +13,7 @@ github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK 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/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= @@ -23,6 +26,7 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI= golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -59,4 +63,5 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= diff --git a/internal/controller/http/handler.go b/internal/controller/http/handler.go new file mode 100644 index 0000000..1e8dc51 --- /dev/null +++ b/internal/controller/http/handler.go @@ -0,0 +1,78 @@ +package http + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/zhashkevych/s3-file-uploader/pkg/uploader" +) + +const ( + MAX_UPLOAD_SIZE = 5 << 20 // 5 megabytes +) + +var ( + IMAGE_TYPES = map[string]interface{}{ + "image/jpeg": nil, + "image/png": nil, + } +) + +type Handler struct { + uploader uploader.Uploader +} + +func NewHandler(uploader uploader.Uploader) *Handler { + return &Handler{ + uploader: uploader, + } +} + +type uploadResponse struct { + Status string `json:"status"` + Msg string `json:"message,omitempty"` + URL string `json:"url,omitempty"` +} + +func (h *Handler) Upload(c *gin.Context) { + // Limit Upload File Size + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, MAX_UPLOAD_SIZE) + + file, fileHeader, err := c.Request.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, &uploadResponse{ + Status: "error", + Msg: err.Error(), + }) + return + } + + defer file.Close() + + buffer := make([]byte, fileHeader.Size) + file.Read(buffer) + fileType := http.DetectContentType(buffer) + + // Validate File Type + if _, ex := IMAGE_TYPES[fileType]; !ex { + c.JSON(http.StatusBadRequest, &uploadResponse{ + Status: "error", + Msg: "file type is not supported", + }) + return + } + + url, err := h.uploader.Upload(c.Request.Context(), file, fileHeader.Size, fileType) + if err != nil { + c.JSON(http.StatusBadRequest, &uploadResponse{ + Status: "error", + Msg: err.Error(), + }) + return + } + + c.JSON(http.StatusOK, &uploadResponse{ + Status: "ok", + URL: url, + }) +} diff --git a/internal/controller/http/register.go b/internal/controller/http/register.go new file mode 100644 index 0000000..86bc3b7 --- /dev/null +++ b/internal/controller/http/register.go @@ -0,0 +1,12 @@ +package http + +import ( + "github.com/gin-gonic/gin" + "github.com/zhashkevych/s3-file-uploader/pkg/uploader" +) + +func RegisterHTTPEndpoints(router *gin.RouterGroup, uploader uploader.Uploader) { + h := NewHandler(uploader) + + router.POST("/upload", h.Upload) +} diff --git a/internal/controller/interface.go b/internal/controller/interface.go new file mode 100644 index 0000000..4710086 --- /dev/null +++ b/internal/controller/interface.go @@ -0,0 +1,10 @@ +package controller + +import ( + "context" + "io" +) + +type Uploader interface { + Upload(ctx context.Context, file io.Reader, size int64, contentType string) (string, error) +} diff --git a/internal/controller/upload/uploader.go b/internal/controller/upload/uploader.go new file mode 100644 index 0000000..c1c9fc3 --- /dev/null +++ b/internal/controller/upload/uploader.go @@ -0,0 +1,41 @@ +package upload + +import ( + "context" + "io" + "math/rand" + + "github.com/zhashkevych/s3-file-uploader/sidecar/filestorage" +) + +const ( + letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + fileNameLength = 16 +) + +type Uploader struct { + fs *filestorage.FileStorage +} + +func NewUploader(fs *filestorage.FileStorage) *Uploader { + return &Uploader{ + fs: fs, + } +} + +func (u *Uploader) Upload(ctx context.Context, file io.Reader, size int64, contentType string) (string, error) { + return u.fs.Upload(ctx, filestorage.UploadInput{ + File: file, + Name: generateFileName(), + Size: size, + ContentType: contentType, + }) +} + +func generateFileName() string { + b := make([]byte, fileNameLength) + for i := range b { + b[i] = letterBytes[rand.Intn(len(letterBytes))] + } + return string(b) +} diff --git a/internal/model/config.go b/internal/model/config.go new file mode 100644 index 0000000..f654244 --- /dev/null +++ b/internal/model/config.go @@ -0,0 +1,12 @@ +package config + +import ( + "github.com/spf13/viper" +) + +func Init() error { + viper.AddConfigPath("./config") + viper.SetConfigName("config") + + return viper.ReadInConfig() +} diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..3f50046 --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,131 @@ +package model + +import "fmt" + +type Bolid struct { + Devices []DevBolid + Zones []ZoneBolid +} + +type DevBolid struct { + Name string + Type int64 + Addr int64 + Input []InputBolid +} + +type InputBolid struct { + ZoneId int64 + Name string + Addr int64 +} + +type ZoneBolid struct { + ZoneId int64 + Name string + Number int64 +} + +type ZoneInfo struct { + ZoneNum int64 + ZoneName string + Input []InputInfo +} + +type InputInfo struct { + DevAdd int64 + Address int64 +} + +func (z *ZoneInfo) InputString() string { + var zones string + + i := 0 + for _, inp := range z.Input { + if i != 0 { + zones = zones + ", " + } + zones = zones + fmt.Sprint(inp.DevAdd) + "." + fmt.Sprint(inp.Address) + i++ + } + + return zones +} + +func (b *Bolid) ZoneInfo() []ZoneInfo { + var zonesInfo []ZoneInfo + + for _, zon := range b.Zones { + var zi ZoneInfo + for _, dev := range b.Devices { + var i InputInfo + for _, inp := range dev.Input { + if zon.ZoneId == inp.ZoneId { + zi.ZoneNum = zon.Number + zi.ZoneName = zon.Name + i.DevAdd = dev.Addr + i.Address = inp.Addr + zi.Input = append(zi.Input, i) + } + } + } + zonesInfo = append(zonesInfo, zi) + } + + return zonesInfo +} + +func (s *Sirius) NewBolid() *Bolid { + return &Bolid{ + Devices: s.NewDevices(), + Zones: s.NewZones(), + } +} + +func (s *Sirius) NewDevices() []DevBolid { + var devices []DevBolid + + for _, dev := range s.Device.Get() { + d := DevBolid{ + Name: dev.Name, + Type: dev.Type, + Addr: dev.Addr, + Input: s.GetInput(dev.DevID), + } + devices = append(devices, d) + } + + return devices +} + +func (s *Sirius) GetInput(devId int64) []InputBolid { + var inputs []InputBolid + + for _, inp := range s.Input.Get() { + if devId == inp.DevID { + i := InputBolid{ + ZoneId: inp.ZoneID, + Name: inp.Name, + Addr: inp.AddrU, + } + inputs = append(inputs, i) + } + } + + return inputs +} + +func (s *Sirius) NewZones() []ZoneBolid { + var zones []ZoneBolid + + for _, zon := range s.Zone.Get() { + z := ZoneBolid{ + ZoneId: zon.ZoneID, + Name: zon.Name, + Number: zon.Number, + } + zones = append(zones, z) + } + + return zones +} diff --git a/internal/model/sirius.go b/internal/model/sirius.go new file mode 100644 index 0000000..523a446 --- /dev/null +++ b/internal/model/sirius.go @@ -0,0 +1,257 @@ +package model + +// Sirius structure +type Sirius struct { + Device `json:"device"` + Zone `json:"zone"` + ZoneGroup `json:"zone_group"` + Input `json:"input"` + Output `json:"output"` + Reader `json:"reader"` + Channel `json:"channel"` + AccessGroup `json:"access_group"` + User `json:"user"` + Script `json:"script"` + Program `json:"program"` + Controller `json:"controller"` +} + +// Device +type Device struct { + Keys []string `json:"keys"` + Rows [][]interface{} `json:"rows"` +} + +func (z *Device) Get() []DeviceRows { + var zr []DeviceRows + for _, z := range z.Rows { + i, b, s, _ := InterToArray(z) + + zz := DeviceRows{ + DevID: i[0], + I2: i[1], + B1: b[0], + Addr: i[2], + I4: i[3], + Type: i[4], + I6: i[5], + I7: i[6], + I8: i[7], + I9: i[8], + Name: s[0], + } + zr = append(zr, zz) + } + return zr +} + +// Type-40(КДЛС-С) Type-81(КДЛ-2И) +type DeviceRows struct { + DevID int64 + I2 int64 + B1 bool + Addr int64 + I4 int64 + Type int64 + I6 int64 + I7 int64 + I8 int64 + I9 int64 + IntArr1 []int64 + IntArr2 []int64 + Name string +} + +// Zone +type Zone struct { + Keys []string `json:"keys"` + Rows [][]interface{} `json:"rows"` +} + +func (z *Zone) Get() []ZoneRows { + var zr []ZoneRows + for _, z := range z.Rows { + i, b, s, _ := InterToArray(z) + + zz := ZoneRows{ + ZoneID: i[0], + I1: i[1], + B1: b[0], + Number: i[2], + I3: i[3], + I4: i[4], + B2: b[1], + B3: b[2], + Name: s[0], + } + zr = append(zr, zz) + } + return zr +} + +type ZoneRows struct { + ZoneID int64 + I1 int64 + B1 bool + Number int64 + I3 int64 + I4 int64 + B2 bool + B3 bool + Name string +} + +// ZoneGroup +type ZoneGroup struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// type ZoneGroupRows struct { +// Number int64 +// I2 int64 +// B1 bool +// I3 int64 +// I4 int64 +// IntArr []int64 +// Name string +// } + +// Input +type Input struct { + Keys []string `json:"keys"` + Rows [][]interface{} `json:"rows"` +} + +func (z *Input) Get() []InputRows { + var zr []InputRows + for _, z := range z.Rows { + i, _, s, _ := InterToArray(z) + + zz := InputRows{ + InputID: i[0], + DevID: i[1], + AddrU: i[2], + I4: i[3], + I5: i[4], + I6: i[5], + ZoneID: i[6], + I8: i[7], + Name: s[0], + } + zr = append(zr, zz) + } + return zr +} + +type InputRows struct { + InputID int64 + DevID int64 + AddrU int64 + I4 int64 + I5 int64 + I6 int64 + ZoneID int64 + I8 int64 + Name string +} + +// Output +type Output struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// type OutputRows struct { +// Addr int64 +// I2 int64 +// I3 int64 +// I4 int64 +// I5 int64 +// I6 int64 +// I7 int64 +// I8 int64 +// I9 int64 +// I10 int64 +// I11 int64 +// IntArr1 []int64 +// IntArr2 []int64 +// I12 int64 +// I13 int64 +// Name string +// } + +// Reader +type Reader struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// type ReaderRows struct { +// I1 int64 +// I2 int64 +// I3 int64 +// I4 int64 +// I5 int64 +// I6 int64 +// IntArr1 []int64 +// IntArr2 []int64 +// Name string +// } + +// Channel +type Channel struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// type ChannelRows struct { +// I1 int64 +// I2 int64 +// I3 int64 +// I4 int64 +// I5 int64 +// Name string +// } + +// AccessGroup +type AccessGroup struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// type AccessGroupRows struct { +// I1 int64 +// I2 int64 +// IntArr1 []AccessGroupIntArray +// Name string +// } + +// type AccessGroupIntArray struct { +// I1 int64 +// I2 int64 +// } + +// User +type User struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// Script +type Script struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// Program +type Program struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} + +// Controller +type Controller struct { + Keys []string `json:"keys"` + Rows []interface{} `json:"rows"` +} diff --git a/internal/model/utils.go b/internal/model/utils.go new file mode 100644 index 0000000..80726d6 --- /dev/null +++ b/internal/model/utils.go @@ -0,0 +1,22 @@ +package model + +func InterToArray(inp []interface{}) ([]int64, []bool, []string, []interface{}) { + var i []int64 + var b []bool + var s []string + var a []interface{} + for _, v1 := range inp { + switch v2 := v1.(type) { + case float64: + i = append(i, int64(v2)) + case bool: + b = append(b, bool(v2)) + case string: + s = append(s, string(v2)) + default: + a = append(a, v2) + } + + } + return i, b, s, a +} diff --git a/internal/server/app.go b/internal/server/app.go new file mode 100644 index 0000000..f1d64f5 --- /dev/null +++ b/internal/server/app.go @@ -0,0 +1,85 @@ +package server + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/minio/minio-go" + "github.com/spf13/viper" + + "github.com/zhashkevych/s3-file-uploader/pkg/uploader" + uphttp "github.com/zhashkevych/s3-file-uploader/pkg/uploader/delivery/http" + "github.com/zhashkevych/s3-file-uploader/pkg/uploader/upload" + "github.com/zhashkevych/s3-file-uploader/sidecar/filestorage" + + "log" + "net/http" + "os" + "os/signal" + "time" +) + +type App struct { + httpServer *http.Server + + fileStorage *filestorage.FileStorage + imageUploader uploader.Uploader +} + +func NewApp(accessKey, secretKey string) *App { + // Initiate an S3 compatible client + client, err := minio.New(viper.GetString("storage.endpoint"), accessKey, secretKey, false) + if err != nil { + log.Fatal(err) + } + + fileStorage := filestorage.NewFileStorage( + client, + viper.GetString("storage.bucket"), + viper.GetString("storage.endpoint"), + ) + + return &App{ + fileStorage: fileStorage, + imageUploader: upload.NewUploader(fileStorage), + } +} + +func (a *App) Run(port string) error { + // Init gin handler + router := gin.Default() + + router.Use( + gin.Recovery(), + gin.Logger(), + ) + + // API endpoints + api := router.Group("/api") + uphttp.RegisterHTTPEndpoints(api, a.imageUploader) + + // HTTP Server + a.httpServer = &http.Server{ + Addr: ":" + port, + Handler: router, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + MaxHeaderBytes: 1 << 20, + } + + go func() { + if err := a.httpServer.ListenAndServe(); err != nil { + log.Fatalf("Failed to listen and serve: %+v", err) + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, os.Interrupt, os.Interrupt) + + <-quit + + ctx, shutdown := context.WithTimeout(context.Background(), 5*time.Second) + defer shutdown() + + return a.httpServer.Shutdown(ctx) +} diff --git a/main.exe b/main.exe new file mode 100644 index 0000000..1f3d2c0 Binary files /dev/null and b/main.exe differ diff --git a/sirius.xlsx b/sirius.xlsx index 67642bc..e473d86 100755 Binary files a/sirius.xlsx and b/sirius.xlsx differ