diff --git a/back/cmd/main.go b/back/cmd/main.go deleted file mode 100644 index 0d407bc..0000000 --- a/back/cmd/main.go +++ /dev/null @@ -1,60 +0,0 @@ -package main - -import ( - "awesome-back/internal/datastore" - "awesome-back/internal/handler" - "awesome-back/internal/service" - - "github.com/gin-gonic/gin" -) - -func main() { - // Инициализация репозиториев - coffeeRepo := datastore.NewMockCoffeeRepository() - pastryRepo := datastore.NewMockPastryRepository() - orderRepo := datastore.NewMockOrderRepository() - - // Инициализация сервисов - coffeeService := service.NewCoffeeService(coffeeRepo) - pastryService := service.NewPastryService(pastryRepo) - orderService := service.NewOrderService(orderRepo) - - // Инициализация обработчиков - coffeeHandler := handler.NewCoffeeHandler(coffeeService) - pastryHandler := handler.NewPastryHandler(pastryService) - orderHandler := handler.NewOrderHandler(orderService) - - // Настройка маршрутов - router := gin.Default() - setupRoutes(router, coffeeHandler, pastryHandler, orderHandler) - - router.Run(":8080") -} - -func setupRoutes(router *gin.Engine, coffeeHandler *handler.CoffeeHandler, pastryHandler *handler.PastryHandler, orderHandler *handler.OrderHandler) { - api := router.Group("/api/v1") - { - // Кофе - coffeeRoutes := api.Group("/coffees") - { - coffeeRoutes.GET("/", coffeeHandler.GetCoffees) - coffeeRoutes.GET("/:id", coffeeHandler.GetCoffeeByID) - } - - // Выпечка - pastryRoutes := api.Group("/pastries") - { - pastryRoutes.GET("/", pastryHandler.GetPastries) - pastryRoutes.GET("/:id", pastryHandler.GetPastryByID) - pastryRoutes.GET("/category/:category", pastryHandler.GetPastriesByCategory) - } - - // Заказы - orderRoutes := api.Group("/orders") - { - orderRoutes.GET("/", orderHandler.GetOrders) - orderRoutes.GET("/:id", orderHandler.GetOrderByID) - orderRoutes.POST("/", orderHandler.CreateOrder) - } - } -} diff --git a/back/go.mod b/back/go.mod deleted file mode 100644 index 8905f35..0000000 --- a/back/go.mod +++ /dev/null @@ -1,39 +0,0 @@ -module awesome-back - -go 1.25.1 - -require github.com/gin-gonic/gin v1.11.0 - -require ( - github.com/bytedance/sonic v1.14.0 // indirect - github.com/bytedance/sonic/loader v0.3.0 // indirect - github.com/cloudwego/base64x v0.1.6 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/gin-contrib/sse v1.1.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.27.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect - github.com/goccy/go-yaml v1.18.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // 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-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.54.0 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.3.0 // indirect - go.uber.org/mock v0.5.0 // indirect - golang.org/x/arch v0.20.0 // indirect - golang.org/x/crypto v0.40.0 // indirect - golang.org/x/mod v0.25.0 // indirect - golang.org/x/net v0.42.0 // indirect - golang.org/x/sync v0.16.0 // indirect - golang.org/x/sys v0.35.0 // indirect - golang.org/x/text v0.27.0 // indirect - golang.org/x/tools v0.34.0 // indirect - google.golang.org/protobuf v1.36.9 // indirect -) diff --git a/back/go.sum b/back/go.sum deleted file mode 100644 index a25fff5..0000000 --- a/back/go.sum +++ /dev/null @@ -1,88 +0,0 @@ -github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= -github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= -github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= -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/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= -github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -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.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= -github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -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/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= -github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -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.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -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 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/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.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -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/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= -github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= -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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -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.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -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.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= -github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= -golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= -golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= -golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= -golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= -golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= -golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= -golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= -golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= -google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= -google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= -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/back/internal/datastore/mock_coffee_repository.go b/back/internal/datastore/mock_coffee_repository.go deleted file mode 100644 index 36ece2b..0000000 --- a/back/internal/datastore/mock_coffee_repository.go +++ /dev/null @@ -1,34 +0,0 @@ -package datastore - -import ( - "awesome-back/internal/domain/entities" - "awesome-back/internal/domain/repositories" - "awesome-back/pkg/errors" -) - -type MockCoffeeRepository struct { - coffees []entities.Coffee -} - -func NewMockCoffeeRepository() repositories.CoffeeRepository { - return &MockCoffeeRepository{ - coffees: []entities.Coffee{ - {ID: 1, Name: "Эспрессо", Description: "Крепкий черный кофе", Price: 120.0, Size: "S"}, - {ID: 2, Name: "Капучино", Description: "Кофе с молочной пенкой", Price: 180.0, Size: "M"}, - // ... остальные данные - }, - } -} - -func (r *MockCoffeeRepository) FindAll() ([]entities.Coffee, error) { - return r.coffees, nil -} - -func (r *MockCoffeeRepository) FindByID(id int) (*entities.Coffee, error) { - for _, coffee := range r.coffees { - if coffee.ID == id { - return &coffee, nil - } - } - return nil, errors.NewNotFoundError("coffee not found") -} diff --git a/back/internal/datastore/mock_order_repository.go b/back/internal/datastore/mock_order_repository.go deleted file mode 100644 index e5dbeb6..0000000 --- a/back/internal/datastore/mock_order_repository.go +++ /dev/null @@ -1,82 +0,0 @@ -package datastore - -import ( - "time" - - "awesome-back/internal/domain/entities" - "awesome-back/internal/domain/repositories" - "awesome-back/pkg/errors" -) - -type MockOrderRepository struct { - orders []entities.Order - nextID int -} - -func NewMockOrderRepository() repositories.OrderRepository { - return &MockOrderRepository{ - orders: []entities.Order{ - { - ID: 1, - Items: []entities.Item{ - {ProductID: 1, Quantity: 1}, - {ProductID: 1, Quantity: 2}, - }, - Total: 320.0, - Status: "completed", - Timestamp: "2024-01-15 10:30:00", - }, - { - ID: 2, - Items: []entities.Item{ - {ProductID: 2, Quantity: 1}, - {ProductID: 3, Quantity: 1}, - }, - Total: 380.0, - Status: "preparing", - Timestamp: "2024-01-15 11:15:00", - }, - }, - nextID: 3, - } -} - -func (r *MockOrderRepository) FindAll() ([]entities.Order, error) { - return r.orders, nil -} - -func (r *MockOrderRepository) FindByID(id int) (*entities.Order, error) { - for _, order := range r.orders { - if order.ID == id { - return &order, nil - } - } - return nil, errors.NewNotFoundError("order not found") -} - -func (r *MockOrderRepository) Save(order *entities.Order) error { - if order == nil { - return errors.NewInvalidError("order cannot be nil") - } - - // Если это новый заказ (ID = 0), присваиваем следующий ID - if order.ID == 0 { - order.ID = r.nextID - order.Timestamp = time.Now().Format("2006-01-02 15:04:05") - if order.Status == "" { - order.Status = "created" - } - r.orders = append(r.orders, *order) - r.nextID++ - } else { - // Обновление существующего заказа - for i, existingOrder := range r.orders { - if existingOrder.ID == order.ID { - r.orders[i] = *order - break - } - } - } - - return nil -} diff --git a/back/internal/datastore/mock_pastry_repository.go b/back/internal/datastore/mock_pastry_repository.go deleted file mode 100644 index a89fcc5..0000000 --- a/back/internal/datastore/mock_pastry_repository.go +++ /dev/null @@ -1,51 +0,0 @@ -package datastore - -import ( - "awesome-back/internal/domain/entities" - "awesome-back/internal/domain/repositories" - "awesome-back/pkg/errors" -) - -type MockPastryRepository struct { - pastries []entities.Pastry -} - -func NewMockPastryRepository() repositories.PastryRepository { - return &MockPastryRepository{ - pastries: []entities.Pastry{ - {ID: 1, Name: "Круассан", Description: "Слоеная выпечка с маслом", Price: 80.0, Category: "Выпечка"}, - {ID: 2, Name: "Тирамису", Description: "Итальянский десерт", Price: 150.0, Category: "Десерты"}, - {ID: 3, Name: "Чизкейк", Description: "Сырный торт", Price: 130.0, Category: "Десерты"}, - {ID: 4, Name: "Маффин", Description: "Шоколадный кекс", Price: 70.0, Category: "Выпечка"}, - {ID: 5, Name: "Печенье", Description: "Домашнее овсяное печенье", Price: 50.0, Category: "Печенье"}, - }, - } -} - -func (r *MockPastryRepository) FindAll() ([]entities.Pastry, error) { - return r.pastries, nil -} - -func (r *MockPastryRepository) FindByID(id int) (*entities.Pastry, error) { - for _, pastry := range r.pastries { - if pastry.ID == id { - return &pastry, nil - } - } - return nil, errors.NewNotFoundError("pastry not found") -} - -func (r *MockPastryRepository) FindByCategory(category string) ([]entities.Pastry, error) { - var filtered []entities.Pastry - for _, pastry := range r.pastries { - if pastry.Category == category { - filtered = append(filtered, pastry) - } - } - - if len(filtered) == 0 { - return nil, errors.NewNotFoundError("no pastries found for category") - } - - return filtered, nil -} diff --git a/back/internal/domain/entities/coffee.go b/back/internal/domain/entities/coffee.go deleted file mode 100644 index fb1f8f0..0000000 --- a/back/internal/domain/entities/coffee.go +++ /dev/null @@ -1,9 +0,0 @@ -package entities - -type Coffee struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Price float64 `json:"price"` - Size string `json:"size"` -} diff --git a/back/internal/domain/entities/order.go b/back/internal/domain/entities/order.go deleted file mode 100644 index 68e6888..0000000 --- a/back/internal/domain/entities/order.go +++ /dev/null @@ -1,14 +0,0 @@ -package entities - -type Order struct { - ID int `json:"id"` - Items []Item `json:"items"` - Total float64 `json:"total"` - Status string `json:"status"` - Timestamp string `json:"timestamp"` -} - -type Item struct { - ProductID int `json:"product_id"` - Quantity int `json:"quantity"` -} diff --git a/back/internal/domain/entities/pastry.go b/back/internal/domain/entities/pastry.go deleted file mode 100644 index 0a2f4b4..0000000 --- a/back/internal/domain/entities/pastry.go +++ /dev/null @@ -1,9 +0,0 @@ -package entities - -type Pastry struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Price float64 `json:"price"` - Category string `json:"category"` -} diff --git a/back/internal/domain/repositories/coffee_repository.go b/back/internal/domain/repositories/coffee_repository.go deleted file mode 100644 index 4b32193..0000000 --- a/back/internal/domain/repositories/coffee_repository.go +++ /dev/null @@ -1,8 +0,0 @@ -package repositories - -import "awesome-back/internal/domain/entities" - -type CoffeeRepository interface { - FindAll() ([]entities.Coffee, error) - FindByID(id int) (*entities.Coffee, error) -} diff --git a/back/internal/domain/repositories/order_repository.go b/back/internal/domain/repositories/order_repository.go deleted file mode 100644 index e997467..0000000 --- a/back/internal/domain/repositories/order_repository.go +++ /dev/null @@ -1,9 +0,0 @@ -package repositories - -import "awesome-back/internal/domain/entities" - -type OrderRepository interface { - FindAll() ([]entities.Order, error) - FindByID(id int) (*entities.Order, error) - Save(order *entities.Order) error -} diff --git a/back/internal/domain/repositories/pastry_repository.go b/back/internal/domain/repositories/pastry_repository.go deleted file mode 100644 index 555f259..0000000 --- a/back/internal/domain/repositories/pastry_repository.go +++ /dev/null @@ -1,9 +0,0 @@ -package repositories - -import "awesome-back/internal/domain/entities" - -type PastryRepository interface { - FindAll() ([]entities.Pastry, error) - FindByID(id int) (*entities.Pastry, error) - FindByCategory(category string) ([]entities.Pastry, error) -} diff --git a/back/internal/handler/coffee_handler.go b/back/internal/handler/coffee_handler.go deleted file mode 100644 index 89818d0..0000000 --- a/back/internal/handler/coffee_handler.go +++ /dev/null @@ -1,45 +0,0 @@ -package handler - -import ( - "strconv" - - "awesome-back/internal/service" - "awesome-back/pkg/errors" - - "github.com/gin-gonic/gin" -) - -type CoffeeHandler struct { - coffeeService *service.CoffeeService -} - -func NewCoffeeHandler(coffeeService *service.CoffeeService) *CoffeeHandler { - return &CoffeeHandler{coffeeService: coffeeService} -} - -func (h *CoffeeHandler) GetCoffees(c *gin.Context) { - coffees, err := h.coffeeService.GetAllCoffees() - if err != nil { - Error(c, err) - return - } - - Success(c, coffees) -} - -func (h *CoffeeHandler) GetCoffeeByID(c *gin.Context) { - idStr := c.Param("id") - id, err := strconv.Atoi(idStr) - if err != nil { - Error(c, errors.NewInvalidError("invalid coffee ID format")) - return - } - - coffee, err := h.coffeeService.GetCoffeeByID(id) - if err != nil { - Error(c, err) - return - } - - Success(c, coffee) -} diff --git a/back/internal/handler/order_handler.go b/back/internal/handler/order_handler.go deleted file mode 100644 index 0a3e83a..0000000 --- a/back/internal/handler/order_handler.go +++ /dev/null @@ -1,62 +0,0 @@ -package handler - -import ( - "strconv" - - "awesome-back/internal/domain/entities" - "awesome-back/internal/service" - "awesome-back/pkg/errors" - - "github.com/gin-gonic/gin" -) - -type OrderHandler struct { - orderService *service.OrderService -} - -func NewOrderHandler(orderService *service.OrderService) *OrderHandler { - return &OrderHandler{orderService: orderService} -} - -func (h *OrderHandler) GetOrders(c *gin.Context) { - orders, err := h.orderService.GetAllOrders() - if err != nil { - Error(c, err) - return - } - - Success(c, orders) -} - -func (h *OrderHandler) GetOrderByID(c *gin.Context) { - idStr := c.Param("id") - id, err := strconv.Atoi(idStr) - if err != nil { - Error(c, errors.NewInvalidError("invalid order ID format")) - return - } - - order, err := h.orderService.GetOrderByID(id) - if err != nil { - Error(c, err) - return - } - - Success(c, order) -} - -func (h *OrderHandler) CreateOrder(c *gin.Context) { - var order entities.Order - if err := c.ShouldBindJSON(&order); err != nil { - Error(c, errors.NewInvalidError("invalid order data")) - return - } - - err := h.orderService.CreateOrder(&order) - if err != nil { - Error(c, err) - return - } - - Created(c, order) -} diff --git a/back/internal/handler/pastry_handler.go b/back/internal/handler/pastry_handler.go deleted file mode 100644 index 1fa3879..0000000 --- a/back/internal/handler/pastry_handler.go +++ /dev/null @@ -1,61 +0,0 @@ -package handler - -import ( - "strconv" - - "awesome-back/internal/service" - "awesome-back/pkg/errors" - - "github.com/gin-gonic/gin" -) - -type PastryHandler struct { - pastryService *service.PastryService -} - -func NewPastryHandler(pastryService *service.PastryService) *PastryHandler { - return &PastryHandler{pastryService: pastryService} -} - -func (h *PastryHandler) GetPastries(c *gin.Context) { - pastries, err := h.pastryService.GetAllPastries() - if err != nil { - Error(c, err) - return - } - - Success(c, pastries) -} - -func (h *PastryHandler) GetPastryByID(c *gin.Context) { - idStr := c.Param("id") - id, err := strconv.Atoi(idStr) - if err != nil { - Error(c, errors.NewInvalidError("invalid pastry ID format")) - return - } - - pastry, err := h.pastryService.GetPastryByID(id) - if err != nil { - Error(c, err) - return - } - - Success(c, pastry) -} - -func (h *PastryHandler) GetPastriesByCategory(c *gin.Context) { - category := c.Param("category") - if category == "" { - Error(c, errors.NewInvalidError("category parameter is required")) - return - } - - pastries, err := h.pastryService.GetPastriesByCategory(category) - if err != nil { - Error(c, err) - return - } - - Success(c, pastries) -} diff --git a/back/internal/handler/response.go b/back/internal/handler/response.go deleted file mode 100644 index c7772e8..0000000 --- a/back/internal/handler/response.go +++ /dev/null @@ -1,55 +0,0 @@ -package handler - -import ( - "errors" - "net/http" - - appErr "awesome-back/pkg/errors" - - "github.com/gin-gonic/gin" -) - -type Response struct { - Success bool `json:"success"` - Data interface{} `json:"data,omitempty"` - Error string `json:"error,omitempty"` - Message string `json:"message,omitempty"` -} - -func Success(c *gin.Context, data interface{}) { - c.JSON(http.StatusOK, Response{ - Success: true, - Data: data, - }) -} - -func Created(c *gin.Context, data interface{}) { - c.JSON(http.StatusCreated, Response{ - Success: true, - Data: data, - }) -} - -func Error(c *gin.Context, err error) { - var appErr *appErr.AppError - if errors.As(err, &appErr) { - statusCode := appErr.Code - if statusCode == 0 { - statusCode = http.StatusInternalServerError - } - - c.JSON(statusCode, Response{ - Success: false, - Error: appErr.Message, - }) - return - } - - // Ошибка по умолчанию - c.JSON(http.StatusInternalServerError, Response{ - Success: false, - Error: "Internal server error", - }) -} - -// Аналогично для PastryHandler и OrderHandler... diff --git a/back/internal/service/coffee_service.go b/back/internal/service/coffee_service.go deleted file mode 100644 index d721117..0000000 --- a/back/internal/service/coffee_service.go +++ /dev/null @@ -1,39 +0,0 @@ -package service - -import ( - "awesome-back/internal/domain/entities" - "awesome-back/internal/domain/repositories" - "awesome-back/pkg/errors" -) - -type CoffeeService struct { - repo repositories.CoffeeRepository -} - -func NewCoffeeService(repo repositories.CoffeeRepository) *CoffeeService { - return &CoffeeService{repo: repo} -} - -func (s *CoffeeService) GetAllCoffees() ([]entities.Coffee, error) { - coffees, err := s.repo.FindAll() - if err != nil { - return nil, errors.Wrap(err, "failed to get coffees") - } - return coffees, nil -} - -func (s *CoffeeService) GetCoffeeByID(id int) (*entities.Coffee, error) { - if id <= 0 { - return nil, errors.NewInvalidError("invalid coffee ID") - } - - coffee, err := s.repo.FindByID(id) - if err != nil { - if errors.IsNotFound(err) { - return nil, errors.NewNotFoundError("coffee not found") - } - return nil, errors.Wrap(err, "failed to get coffee") - } - - return coffee, nil -} diff --git a/back/internal/service/order_service.go b/back/internal/service/order_service.go deleted file mode 100644 index 16b929e..0000000 --- a/back/internal/service/order_service.go +++ /dev/null @@ -1,57 +0,0 @@ -// internal/service/order_service.go -package service - -import ( - "awesome-back/internal/domain/entities" - "awesome-back/internal/domain/repositories" - "awesome-back/pkg/errors" -) - -type OrderService struct { - orderRepo repositories.OrderRepository -} - -func NewOrderService(orderRepo repositories.OrderRepository) *OrderService { - return &OrderService{orderRepo: orderRepo} -} - -func (s *OrderService) GetAllOrders() ([]entities.Order, error) { - orders, err := s.orderRepo.FindAll() - if err != nil { - return nil, errors.Wrap(err, "failed to get orders") - } - return orders, nil -} - -func (s *OrderService) GetOrderByID(id int) (*entities.Order, error) { - if id <= 0 { - return nil, errors.NewInvalidError("invalid order ID") - } - - order, err := s.orderRepo.FindByID(id) - if err != nil { - if errors.IsNotFound(err) { - return nil, errors.NewNotFoundError("order not found") - } - return nil, errors.Wrap(err, "failed to get order") - } - - return order, nil -} - -func (s *OrderService) CreateOrder(order *entities.Order) error { - if order == nil { - return errors.NewInvalidError("order cannot be nil") - } - - if len(order.Items) == 0 { - return errors.NewInvalidError("order must contain at least one item") - } - - err := s.orderRepo.Save(order) - if err != nil { - return errors.Wrap(err, "failed to create order") - } - - return nil -} diff --git a/back/internal/service/pastry_service.go b/back/internal/service/pastry_service.go deleted file mode 100644 index 9d1605c..0000000 --- a/back/internal/service/pastry_service.go +++ /dev/null @@ -1,52 +0,0 @@ -package service - -import ( - "awesome-back/internal/domain/entities" - "awesome-back/internal/domain/repositories" - "awesome-back/pkg/errors" -) - -type PastryService struct { - repo repositories.PastryRepository -} - -func NewPastryService(repo repositories.PastryRepository) *PastryService { - return &PastryService{repo: repo} -} - -func (s *PastryService) GetAllPastries() ([]entities.Pastry, error) { - pastries, err := s.repo.FindAll() - if err != nil { - return nil, errors.Wrap(err, "failed to get pastries") - } - return pastries, nil -} - -func (s *PastryService) GetPastryByID(id int) (*entities.Pastry, error) { - if id <= 0 { - return nil, errors.NewInvalidError("invalid pastry ID") - } - - pastry, err := s.repo.FindByID(id) - if err != nil { - if errors.IsNotFound(err) { - return nil, errors.NewNotFoundError("pastry not found") - } - return nil, errors.Wrap(err, "failed to get pastry") - } - - return pastry, nil -} - -func (s *PastryService) GetPastriesByCategory(category string) ([]entities.Pastry, error) { - if category == "" { - return nil, errors.NewInvalidError("category cannot be empty") - } - - pastries, err := s.repo.FindByCategory(category) - if err != nil { - return nil, errors.Wrap(err, "failed to get pastries by category") - } - - return pastries, nil -} diff --git a/back/pkg/errors/errors.go b/back/pkg/errors/errors.go deleted file mode 100644 index 1d27f64..0000000 --- a/back/pkg/errors/errors.go +++ /dev/null @@ -1,118 +0,0 @@ -package errors - -import ( - "errors" - "fmt" -) - -// ErrorType представляет тип ошибки -type ErrorType string - -const ( - ErrorTypeInvalid ErrorType = "INVALID" - ErrorTypeNotFound ErrorType = "NOT_FOUND" - ErrorTypeConflict ErrorType = "CONFLICT" - ErrorTypeInternal ErrorType = "INTERNAL" - ErrorTypeUnauthorized ErrorType = "UNAUTHORIZED" - ErrorTypeForbidden ErrorType = "FORBIDDEN" -) - -// AppError - кастомная ошибка приложения -type AppError struct { - Type ErrorType `json:"type"` - Message string `json:"message"` - Code int `json:"code,omitempty"` - Err error `json:"-"` -} - -// Error реализует интерфейс error -func (e *AppError) Error() string { - if e.Err != nil { - return fmt.Sprintf("%s: %v", e.Message, e.Err) - } - return e.Message -} - -// Unwrap для поддержки errors.Unwrap -func (e *AppError) Unwrap() error { - return e.Err -} - -// WithCode добавляет HTTP код к ошибке -func (e *AppError) WithCode(code int) *AppError { - e.Code = code - return e -} - -// Wrap оборачивает существующую ошибку -func (e *AppError) Wrap(err error) *AppError { - e.Err = err - return e -} - -// Конструкторы ошибок -func NewInvalidError(message string) *AppError { - return &AppError{ - Type: ErrorTypeInvalid, - Message: message, - Code: 400, - } -} - -func NewNotFoundError(message string) *AppError { - return &AppError{ - Type: ErrorTypeNotFound, - Message: message, - Code: 404, - } -} - -func NewInternalError(message string) *AppError { - return &AppError{ - Type: ErrorTypeInternal, - Message: message, - Code: 500, - } -} - -func NewConflictError(message string) *AppError { - return &AppError{ - Type: ErrorTypeConflict, - Message: message, - Code: 409, - } -} - -// Вспомогательные функции для проверки типов ошибок -func IsNotFound(err error) bool { - var appErr *AppError - if errors.As(err, &appErr) { - return appErr.Type == ErrorTypeNotFound - } - return false -} - -func IsInvalid(err error) bool { - var appErr *AppError - if errors.As(err, &appErr) { - return appErr.Type == ErrorTypeInvalid - } - return false -} - -func IsConflict(err error) bool { - var appErr *AppError - if errors.As(err, &appErr) { - return appErr.Type == ErrorTypeConflict - } - return false -} - -// Wrap оборачивает ошибку с сообщением -func Wrap(err error, message string) error { - return fmt.Errorf("%s: %w", message, err) -} - -func Wrapf(err error, format string, args ...interface{}) error { - return fmt.Errorf("%s: %w", fmt.Sprintf(format, args...), err) -} diff --git a/cli/awesome_cli b/cli/awesome_cli deleted file mode 100755 index 6967e53..0000000 Binary files a/cli/awesome_cli and /dev/null differ diff --git a/cli/cmd/cli.go b/cli/cmd/cli.go deleted file mode 100644 index c2c7fc6..0000000 --- a/cli/cmd/cli.go +++ /dev/null @@ -1,29 +0,0 @@ -package cmd - -import ( - "os" - - "github.com/spf13/cobra" -) - -var ( - RootCmd = &cobra.Command{ - Use: "acli", - Short: "Интерфейс командной строки", - CompletionOptions: cobra.CompletionOptions{ - HiddenDefaultCmd: true, - }, - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.Help() - os.Exit(0) - } - }, - } -) - -func Execute() { - if err := RootCmd.Execute(); err != nil { - panic(err) - } -} diff --git a/cli/cmd/list.go b/cli/cmd/list.go deleted file mode 100644 index 26504cc..0000000 --- a/cli/cmd/list.go +++ /dev/null @@ -1,26 +0,0 @@ -package cmd - -import ( - "awesome_cli/internal/service" - "awesome_cli/internal/views" - - "github.com/spf13/cobra" -) - -func init() { - RootCmd.AddCommand(list()) -} - -func list() *cobra.Command { - c := &cobra.Command{ - Use: "list", - Aliases: []string{"l"}, - Short: "Вывод списка", - Run: func(cmd *cobra.Command, args []string) { - vms := service.VMService().List() - views.List(vms) - }, - } - - return c -} diff --git a/cli/internal/entity/vm.go b/cli/internal/entity/vm.go deleted file mode 100644 index a536d40..0000000 --- a/cli/internal/entity/vm.go +++ /dev/null @@ -1,42 +0,0 @@ -package entity - -import ( - "awesome_cli/internal/ui" -) - -type VirtualMachineOutput struct { - UUID string `json:"vm_uuid,omitempty"` - Status VMStatus `json:"status,omitempty"` - Name string `json:"name,omitempty"` - Autostart bool `json:"autostart,omitempty"` - CPU int `json:"cpu,omitempty"` - DatastoreName string `json:"datastore_name,omitempty"` - Memory int `json:"memory,omitempty"` -} - -type VMStatus int - -const ( - Creating VMStatus = iota + 1 - Running - Stopped - Failed - Paused -) - -func (s VMStatus) String() string { - switch s { - case Creating: - return ui.SetColor(ui.Yellow, "Creating") - case Running: - return ui.SetColor(ui.Green, "Running") - case Stopped: - return ui.SetColor(ui.Red, "Stopped") - case Failed: - return ui.SetColor(ui.Red, "Failed") - case Paused: - return ui.SetColor(ui.Yellow, "Paused") - default: - return "Unknown" - } -} diff --git a/cli/internal/service/vm.go b/cli/internal/service/vm.go deleted file mode 100644 index a9f4722..0000000 --- a/cli/internal/service/vm.go +++ /dev/null @@ -1,37 +0,0 @@ -package service - -import ( - "awesome_cli/internal/entity" - "github.com/gofrs/uuid" -) - -type VM struct { -} - -func VMService() *VM { - return &VM{} -} - -func (vm *VM) List() []entity.VirtualMachineOutput { - u, _ := uuid.NewV4() - return []entity.VirtualMachineOutput{ - { - UUID: u.String(), - Status: 1, - Name: "Awesome VM", - Autostart: false, - CPU: 8, - DatastoreName: "datastore", - Memory: 8, - }, - { - UUID: u.String(), - Status: 2, - Name: "Awesome VM2", - Autostart: true, - CPU: 18, - DatastoreName: "datastore2", - Memory: 8, - }, - } -} diff --git a/cli/internal/ui/interactive.go b/cli/internal/ui/interactive.go deleted file mode 100644 index 78d78bc..0000000 --- a/cli/internal/ui/interactive.go +++ /dev/null @@ -1,166 +0,0 @@ -package ui - -import ( - "fmt" - "strconv" - - "github.com/charmbracelet/lipgloss" - - "github.com/charmbracelet/huh" - log "github.com/sirupsen/logrus" -) - -type FormBuilder struct { - widgets []huh.Field - form huh.Form -} - -func NewForm() *FormBuilder { - return &FormBuilder{} -} - -func (f *FormBuilder) AddWidget(w huh.Field) { - f.widgets = append(f.widgets, w) -} - -func (f *FormBuilder) Group(w ...huh.Field) *huh.Group { - group := huh.NewGroup(w...) - return group -} - -func (f *FormBuilder) BuildGroups(g ...*huh.Group) *huh.Form { - form := huh.NewForm(g...) - return form -} - -func (f *FormBuilder) Build() *huh.Form { - form := huh.NewForm(huh.NewGroup(f.widgets...)).WithTheme(ThemeCustom()) - return form -} - -func (f *FormBuilder) GetString(key string) string { - fmt.Println(f.form.GetString(key)) - return f.form.GetString(key) -} - -func (f *FormBuilder) GetBool(key string) bool { - fmt.Println(f.form.GetString(key)) - return f.form.GetBool(key) -} - -func (f *FormBuilder) GetInt(key string) int64 { - v := f.form.GetString(key) - fmt.Println(v) - value, err := strconv.Atoi(v) - if err != nil { - log.WithError(err).Fatalf(fmt.Sprintf("failed to convert value %s to integer", v)) - return 0 - } - return int64(value) -} - -func MultiSelect(title, key string, options []string) *huh.MultiSelect[string] { - s := huh.NewMultiSelect[string](). - Title(title). - Options(huh.NewOptions[string](options...)...). - Key(key) - return s -} - -func InteractiveList(title, key string, opts map[string]string) *huh.Select[string] { - var pick string - var options []huh.Option[string] - s := huh.NewSelect[string](). - Title(title). - Key(key). - Value(&pick) - if opts != nil { - for k, v := range opts { - options = append(options, huh.Option[string]{Key: k, Value: v}) - } - - s.Options(options...) - } - return s -} - -func Input(question string, key string) *huh.Input { - var answer string - input := huh.NewInput(). - Title(fmt.Sprintf("%s", question)). - Prompt("> "). - Validate(ValidateString). - Key(key). - Value(&answer) - return input -} - -func Confirm(question string, key string) *huh.Confirm { - var happy bool - confirm := huh.NewConfirm(). - Title(question). - Affirmative("Yes"). - Negative("No"). - Value(&happy). - Key(key) - - return confirm -} - -func Text(title, desc string) *huh.Note { - t := huh.NewNote(). - Title(title). - Description(desc) - return t -} - -func TextField(title, key string) *huh.Text { - t := huh.NewText(). - Title(title).Key(key).ShowLineNumbers(true).Editor("vim") - return t -} - -func ThemeCustom() *huh.Theme { - t := huh.ThemeBase() - - var ( - normalFg = lipgloss.AdaptiveColor{Light: "235", Dark: "252"} - // indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} - indigo = lipgloss.AdaptiveColor{Light: "32", Dark: "33"} - cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"} - // fuchsia = lipgloss.Color("#F780E2") - green = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"} - red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"} - ) - - t.Focused.Base = t.Focused.Base.BorderForeground(lipgloss.Color("238")) - t.Focused.Title = t.Focused.Title.Foreground(White).Bold(true) - t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(White).Bold(true).MarginBottom(1) - t.Focused.Directory = t.Focused.Directory.Foreground(indigo) - t.Focused.Description = t.Focused.Description.Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}) - t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red) - t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red) - t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(green) - t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(indigo) - t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(indigo) - t.Focused.Option = t.Focused.Option.Foreground(normalFg) - t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(green) - t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(green) - t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(green).SetString("✓ ") - t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}).SetString("• ") - t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(normalFg) - t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(cream).Background(indigo) - t.Focused.Next = t.Focused.FocusedButton - t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(normalFg).Background(lipgloss.AdaptiveColor{Light: "252", Dark: "237"}) - - t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(Gray) - t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(lipgloss.AdaptiveColor{Light: "248", Dark: "238"}) - t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(green) - - t.Blurred = t.Focused - t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder()) - t.Blurred.NextIndicator = lipgloss.NewStyle() - t.Blurred.PrevIndicator = lipgloss.NewStyle() - - return t -} diff --git a/cli/internal/ui/question.go b/cli/internal/ui/question.go deleted file mode 100644 index 704c2f5..0000000 --- a/cli/internal/ui/question.go +++ /dev/null @@ -1,84 +0,0 @@ -package ui - -import ( - "fmt" - "strconv" - - "github.com/charmbracelet/huh" - log "github.com/sirupsen/logrus" -) - -const ( - proceedTemplate = "%s (y/n): " - defaultTemplate = "%s: " - chooseTemplate = "%d : %s\n" -) - -func AskToConfirm(question, affirmative, negative string) bool { - confirm := Confirm(question, "res") - confirm.Affirmative(affirmative) - confirm.Negative(negative) - form := huh.NewForm(huh.NewGroup(confirm)) - form.Run() - - if form.State != huh.StateCompleted { - log.Fatal("canceled") - } - - return form.GetBool("res") -} - -func AskToValue(question string) (string, error) { - input := Input(question, "res") - form := huh.NewForm(huh.NewGroup(input)) - form.Run() - - if form.State != huh.StateCompleted { - log.Fatal("canceled") - } - - return form.GetString("res"), nil -} - -func AskToInt(question string) (int, error) { - v, err := AskToValue(question) - if err != nil { - return 0, err - } - value, err := strconv.Atoi(v) - if err != nil { - return 0, err - } - return value, nil -} - -func AskToChoose(question string, list map[int]string) string { - for i, item := range list { - fmt.Printf(chooseTemplate, i, item) - } - - answer, err := AskToInt(question) - if err != nil { - log.WithError(err).Error("failed to read the answer") - } - - return list[answer] -} - -func AskToChooseInteractive(question string, list []string) string { - values := make(map[string]string) - if len(list) == 0 { - log.Fatal("no items found") - } - for _, item := range list { - values[item] = item - } - widget := InteractiveList(question, "res", values) - form := huh.NewForm(huh.NewGroup(widget)).WithShowHelp(false) - form.Run() - - if form.State != huh.StateCompleted { - log.Fatal("canceled") - } - return form.GetString("res") -} diff --git a/cli/internal/ui/validate.go b/cli/internal/ui/validate.go deleted file mode 100644 index 6861e2d..0000000 --- a/cli/internal/ui/validate.go +++ /dev/null @@ -1,14 +0,0 @@ -package ui - -import ( - "errors" - "regexp" -) - -func ValidateString(s string) error { - validRegex := regexp.MustCompile(`^[a-zA-Z0-9_.]+$`) - if !validRegex.MatchString(s) { - return errors.New("invalid input") - } - return nil -} diff --git a/cli/internal/views/vm.go b/cli/internal/views/vm.go deleted file mode 100644 index 1a23c58..0000000 --- a/cli/internal/views/vm.go +++ /dev/null @@ -1,35 +0,0 @@ -package views - -import ( - "fmt" - "strconv" - - "awesome_cli/internal/entity" - "awesome_cli/internal/ui" -) - -func List(vms []entity.VirtualMachineOutput) { - var runCount int - - t := ui.NewTableBuilder() - t.AddTableHeader("Status", "Name", "vCPU", "Memory", "Datastore") - for _, vm := range vms { - t.AddRow( - vm.Status.String(), - vm.Name, - strconv.FormatInt(int64(vm.CPU), 10), - strconv.FormatInt(int64(vm.Memory), 10), - vm.DatastoreName, - ) - if vm.Status == entity.Running { - runCount++ - } - } - - header := ui.Header("Virtual machines") - count := ui.Info(fmt.Sprintf("Total: %d", len(vms))) - running := ui.Info(fmt.Sprintf("Run: %d", runCount)) - - fmt.Printf("%s\n\n", ui.StringBar(header, count, running)) - fmt.Println(t.CreateTable()) -} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..84f9833 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,86 @@ +package cmd + +import ( + "fmt" + "os" + + "awesome_cli/internal/client" + "awesome_cli/internal/handler" + "awesome_cli/internal/service" + + "github.com/spf13/cobra" +) + +var ( + baseURL string + verbose bool +) + +var rootCmd = &cobra.Command{ + Use: "coffee-cli", + Short: "CLI client for Coffee Shop API", + Long: `A comprehensive CLI client for interacting with the Coffee Shop REST API`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + if verbose { + fmt.Printf("Using API base URL: %s\n", baseURL) + } + }, +} + +var listCmd = &cobra.Command{ + Use: "list", + Short: "List all coffees", + Run: func(cmd *cobra.Command, args []string) { + apiClient := client.NewAPIClient(baseURL) + coffeeService := service.NewCoffeeService(apiClient) + coffeeHandler := handler.NewCoffeeHandler(coffeeService) + + if err := coffeeHandler.HandleList(); err != nil { + os.Exit(1) + } + }, +} + +var getCmd = &cobra.Command{ + Use: "get [id]", + Short: "Get coffee by ID", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + apiClient := client.NewAPIClient(baseURL) + coffeeService := service.NewCoffeeService(apiClient) + coffeeHandler := handler.NewCoffeeHandler(coffeeService) + + retry, _ := cmd.Flags().GetInt("retry") + coffeeID := args[0] + + if retry > 0 { + if err := coffeeHandler.HandleGetWithRetry(coffeeID, retry); err != nil { + os.Exit(1) + } + } else { + if err := coffeeHandler.HandleGet(coffeeID); err != nil { + os.Exit(1) + } + } + }, +} + +func init() { + // Global flags + rootCmd.PersistentFlags().StringVarP(&baseURL, "url", "u", "http://localhost:8080", "API base URL") + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Verbose output") + + // Get command flags + getCmd.Flags().IntP("retry", "r", 0, "Number of retries on failure") + + // Add commands + rootCmd.AddCommand(listCmd) + rootCmd.AddCommand(getCmd) +} + +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} diff --git a/cli/go.mod b/go.mod similarity index 96% rename from cli/go.mod rename to go.mod index d2d9bae..c1ae7d8 100644 --- a/cli/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module awesome_cli -go 1.25 +go 1.25.1 require ( github.com/charmbracelet/huh v0.7.0 @@ -12,6 +12,7 @@ require ( ) require ( + code.linberg.su/linberg/awesome-back v0.0.3 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/catppuccin/go v0.3.0 // indirect diff --git a/cli/go.sum b/go.sum similarity index 93% rename from cli/go.sum rename to go.sum index b76622e..7ba3120 100644 --- a/cli/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +code.linberg.su/linberg/awesome-back v0.0.0-20251016101642-da80211647f8 h1:G+4HZHikWqm3lHGVfLJ6BA4x/0qL99LLVrSG9r6LtH8= +code.linberg.su/linberg/awesome-back v0.0.0-20251016101642-da80211647f8/go.mod h1:Zuow6nHAsi1M48lxchBnZDMppJuPMZWMhTKr3dKmLq0= +code.linberg.su/linberg/awesome-back v0.0.2 h1:gxFNVf3MfexDqcm8naEsP4f24wWNmHbxsZsiBZCsIdg= +code.linberg.su/linberg/awesome-back v0.0.2/go.mod h1:Zuow6nHAsi1M48lxchBnZDMppJuPMZWMhTKr3dKmLq0= +code.linberg.su/linberg/awesome-back v0.0.3 h1:KOA4Zq3b9aUs4fhoFu0ECmIsjpenJ6sD8bL+8JF4xTQ= +code.linberg.su/linberg/awesome-back v0.0.3/go.mod h1:Zuow6nHAsi1M48lxchBnZDMppJuPMZWMhTKr3dKmLq0= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= diff --git a/internal/client/api.go b/internal/client/api.go new file mode 100644 index 0000000..cb0a9b3 --- /dev/null +++ b/internal/client/api.go @@ -0,0 +1,90 @@ +package client + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" + + "code.linberg.su/linberg/awesome-back/pkg/errors" +) + +type APIClient struct { + baseURL string + httpClient *http.Client +} + +func NewAPIClient(baseURL string) *APIClient { + return &APIClient{ + baseURL: baseURL, + httpClient: &http.Client{ + Timeout: 30 * time.Second, + }, + } +} + +type APIResponse struct { + Success bool `json:"success"` + Data json.RawMessage `json:"data,omitempty"` + Error string `json:"error,omitempty"` +} + +func (c *APIClient) doRequest(method, path string) (*APIResponse, error) { + url := fmt.Sprintf("%s%s", c.baseURL, path) + + req, err := http.NewRequest(method, url, nil) + if err != nil { + return nil, errors.NewNetworkError("failed to create request").Wrap(err) + } + + req.Header.Set("Accept", "application/json") + + resp, err := c.httpClient.Do(req) + if err != nil { + return nil, errors.NewNetworkError("request failed").Wrap(err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.NewNetworkError("failed to read response").Wrap(err) + } + + var apiResponse APIResponse + if err := json.Unmarshal(body, &apiResponse); err != nil { + return nil, errors.NewInternalError("failed to parse response").Wrap(err) + } + + // Обработка HTTP ошибок + if resp.StatusCode >= 400 { + switch resp.StatusCode { + case http.StatusNotFound: + return nil, errors.NewNotFoundError(apiResponse.Error) + case http.StatusBadRequest: + return nil, errors.NewInvalidError(apiResponse.Error) + default: + return nil, errors.NewInternalError(fmt.Sprintf("server error: %s", apiResponse.Error)) + } + } + + return &apiResponse, nil +} + +// Get выполняет GET запрос и парсит ответ в target +func (c *APIClient) Get(path string, target interface{}) error { + response, err := c.doRequest("GET", path) + if err != nil { + return err + } + + if !response.Success { + return errors.NewInternalError("API request failed") + } + + if err := json.Unmarshal(response.Data, target); err != nil { + return errors.NewInternalError("failed to parse response data").Wrap(err) + } + + return nil +} diff --git a/internal/handler/coffee_handler.go b/internal/handler/coffee_handler.go new file mode 100644 index 0000000..4242402 --- /dev/null +++ b/internal/handler/coffee_handler.go @@ -0,0 +1,131 @@ +package handler + +import ( + "errors" + "fmt" + "strconv" + + "awesome_cli/internal/service" + "awesome_cli/pkg/ui" + + libErr "code.linberg.su/linberg/awesome-back/pkg/errors" +) + +type CoffeeHandler struct { + coffeeService *service.CoffeeService +} + +func NewCoffeeHandler(coffeeService *service.CoffeeService) *CoffeeHandler { + return &CoffeeHandler{ + coffeeService: coffeeService, + } +} + +func (h *CoffeeHandler) HandleList() error { + coffees, err := h.coffeeService.GetAllCoffees() + if err != nil { + return h.handleError(err, "Failed to list coffees") + } + + h.printCoffeesTable(coffees) + return nil +} + +func (h *CoffeeHandler) HandleGet(coffeeID string) error { + id, err := strconv.Atoi(coffeeID) + if err != nil { + return libErr.NewInvalidError("coffee ID must be a number") + } + + coffee, err := h.coffeeService.GetCoffeeByID(id) + if err != nil { + return h.handleError(err, fmt.Sprintf("Failed to get coffee with ID %d", id)) + } + + h.printCoffeeDetail(coffee) + return nil +} + +func (h *CoffeeHandler) HandleGetWithRetry(coffeeID string, maxRetries int) error { + id, err := strconv.Atoi(coffeeID) + if err != nil { + return libErr.NewInvalidError("coffee ID must be a number") + } + + fmt.Printf("Fetching coffee with ID %d (max retries: %d)...\n", id, maxRetries) + + coffee, err := h.coffeeService.GetCoffeeByIDWithRetry(id, maxRetries) + if err != nil { + return h.handleError(err, fmt.Sprintf("Failed to get coffee with ID %d after retries", id)) + } + + h.printCoffeeDetail(coffee) + return nil +} + +func (h *CoffeeHandler) printCoffeesTable(coffees []service.Coffee) { + t := ui.NewTableBuilder() + t.AddTableHeader("ID", "Name", "Price", "Size", "Description") + for _, coffee := range coffees { + t.AddRow( + strconv.FormatInt(int64(coffee.ID), 10), + coffee.Name, + strconv.FormatInt(int64(coffee.Price), 10), + coffee.Size, + coffee.Description, + ) + } + + fmt.Println(t.CreateTable()) +} + +func (h *CoffeeHandler) printCoffeeDetail(coffee *service.Coffee) { + fmt.Printf("ID: %s\n", ui.SetColor(ui.Green, strconv.FormatInt(int64(coffee.ID), 10))) + fmt.Printf("Name: %s\n", ui.SetColor(ui.Green, coffee.Name)) + fmt.Printf("Price: %s\n", ui.SetColor(ui.Green, fmt.Sprintf("%.2f ₽", coffee.Price))) + fmt.Printf("Size: %s\n", ui.SetColor(ui.Green, coffee.Size)) + fmt.Printf("Description: %s\n", ui.SetColor(ui.Green, coffee.Description)) + fmt.Println() +} + +// handleError демонстрирует продвинутую обработку ошибок +func (h *CoffeeHandler) handleError(err error, message string) error { + red := func(s string) string { + return ui.SetColor(ui.Red, s) + } + + fmt.Printf("%s: %s\n", red("Error"), message) + + // Демонстрация использования errors.Is и errors.As + switch { + case libErr.IsNotFound(err): + fmt.Printf("%s: Resource not found\n", red("Type")) + fmt.Printf("%s: %v\n", red("Details"), err) + + case libErr.IsInvalid(err): + fmt.Printf("%s: Invalid request\n", red("Type")) + fmt.Printf("%s: %v\n", red("Details"), err) + + case libErr.IsNetworkError(err): + fmt.Printf("%s: Network error\n", red("Type")) + fmt.Printf("%s: Please check your connection and try again\n", red("Details")) + + default: + // Используем errors.As для получения дополнительной информации + var appErr *libErr.AppError + if errors.As(err, &appErr) { + fmt.Printf("%s: %s\n", red("Type"), string(appErr.Type)) + fmt.Printf("%s: %s\n", red("Message"), appErr.Message) + + // Демонстрация unwrap цепочки ошибок + if appErr.Err != nil { + fmt.Printf("%s: %v\n", red("Underlying error"), appErr.Err) + } + } else { + fmt.Printf("%s: %v\n", red("Unknown error"), err) + } + } + + fmt.Println() // Пустая строка для читабельности + return err +} diff --git a/internal/service/coffee_service.go b/internal/service/coffee_service.go new file mode 100644 index 0000000..1fb3c84 --- /dev/null +++ b/internal/service/coffee_service.go @@ -0,0 +1,96 @@ +package service + +import ( + "errors" + "fmt" + + "awesome_cli/internal/client" + + libErr "code.linberg.su/linberg/awesome-back/pkg/errors" +) + +type Coffee struct { + ID int `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Price float64 `json:"price"` + Size string `json:"size"` +} + +type CoffeeService struct { + apiClient *client.APIClient +} + +func NewCoffeeService(apiClient *client.APIClient) *CoffeeService { + return &CoffeeService{ + apiClient: apiClient, + } +} + +func (s *CoffeeService) GetAllCoffees() ([]Coffee, error) { + var coffees []Coffee + err := s.apiClient.Get("/api/v1/coffees", &coffees) + if err != nil { + return nil, libErr.Wrap(err, "failed to fetch coffees") + } + return coffees, nil +} + +func (s *CoffeeService) GetCoffeeByID(id int) (*Coffee, error) { + if id <= 0 { + return nil, libErr.NewInvalidError("coffee ID must be positive") + } + + var coffee Coffee + path := fmt.Sprintf("/api/v1/coffees/%d", id) + err := s.apiClient.Get(path, &coffee) + if err != nil { + // Демонстрация использования errors.Is и errors.As + if libErr.IsNotFound(err) { + return nil, libErr.NewNotFoundError(fmt.Sprintf("coffee with ID %d not found", id)) + } + return nil, libErr.Wrapf(err, "failed to fetch coffee with ID %d", id) + } + + return &coffee, nil +} + +// GetCoffeeByIDWithRetry демонстрирует сложную обработку ошибок с retry логикой +func (s *CoffeeService) GetCoffeeByIDWithRetry(id int, maxRetries int) (*Coffee, error) { + var lastErr error + + for attempt := 0; attempt <= maxRetries; attempt++ { + coffee, err := s.GetCoffeeByID(id) + if err == nil { + return coffee, nil + } + + lastErr = err + + // Проверяем тип ошибки для принятия решения о retry + if libErr.IsNetworkError(err) { + // Network errors - можно retry + continue + } + + if libErr.IsNotFound(err) || libErr.IsInvalid(err) { + // Client errors - не retry + break + } + + // Для других ошибок можно добавить дополнительную логику + var appErr *libErr.AppError + if errors.As(err, &appErr) { + switch appErr.Type { + case libErr.ErrorTypeInternal: + // Server errors - можно retry + continue + default: + // Другие ошибки - не retry + break + } + } + } + + return nil, libErr.Wrapf(lastErr, "failed after %d attempts", maxRetries+1) +} diff --git a/cli/main.go b/main.go similarity index 100% rename from cli/main.go rename to main.go diff --git a/cli/internal/ui/render.go b/pkg/ui/render.go similarity index 66% rename from cli/internal/ui/render.go rename to pkg/ui/render.go index 6b2d4f2..1b245fe 100644 --- a/cli/internal/ui/render.go +++ b/pkg/ui/render.go @@ -1,18 +1,15 @@ package ui import ( - "fmt" "os" - "time" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss/table" terminal "golang.org/x/term" ) +// ANSI256 256 colors, 8-bit var ( - // ANSI256 256 colors, 8-bit - Blue = lipgloss.Color("21") Magenta = lipgloss.Color("13") Gray = lipgloss.Color("238") @@ -20,19 +17,6 @@ var ( Red = lipgloss.Color("9") Green = lipgloss.Color("10") Yellow = lipgloss.Color("11") - - // styling - headerStyle = lipgloss.NewStyle(). - Background(Blue). - Foreground(White). - Padding(0, 1). - Italic(true) - infoStyle = headerStyle.Copy(). - Foreground(White). - Background(Gray) - subHeaderStyle = headerStyle.Copy(). - Background(lipgloss.NoColor{}) - errorStyle = lipgloss.NewStyle().Foreground(Red) ) type TableBuilder struct { @@ -84,9 +68,6 @@ func (t *TableBuilder) CreateTable() *table.Table { t.Table. BorderRow(false).BorderColumn(false). BorderLeft(false).BorderRight(false).BorderTop(false).BorderBottom(false). - //Border(lipgloss.RoundedBorder()). - //BorderRow(true). - //BorderStyle(BorderStyle). StyleFunc(func(row, col int) lipgloss.Style { re := lipgloss.NewRenderer(os.Stdout) if row == -1 { @@ -114,42 +95,6 @@ func (t *TableBuilder) Justify() { t.Table.Width(width) } -func Header(s string) string { - return headerStyle.Render(s) -} - -func SubHeader(s string) string { - return subHeaderStyle.Render(s) -} - -func Info(s string) string { - return infoStyle.Render(s) -} - -func StringBar(strs ...string) string { - return lipgloss.JoinHorizontal(lipgloss.Left, strs...) -} - -func ErrorBar(s string) string { - return errorStyle.Render(s) -} - func SetColor(color lipgloss.Color, str string) string { return lipgloss.NewStyle().Foreground(color).Render(str) } - -func Spinner(stop chan bool, description string) { - spinner := []rune{'|', '\\', '-', '/'} - for { - for _, r := range spinner { - select { - case <-stop: - fmt.Printf("\r") - return - default: - fmt.Printf("\r%c %s", r, description) - time.Sleep(100 * time.Millisecond) - } - } - } -}