cli example
This commit is contained in:
parent
9c813d4754
commit
f9b31f5dc4
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
39
back/go.mod
39
back/go.mod
|
@ -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
|
|
||||||
)
|
|
88
back/go.sum
88
back/go.sum
|
@ -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=
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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...
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
BIN
cli/awesome_cli
BIN
cli/awesome_cli
Binary file not shown.
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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())
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
module awesome_cli
|
module awesome_cli
|
||||||
|
|
||||||
go 1.25
|
go 1.25.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/huh v0.7.0
|
github.com/charmbracelet/huh v0.7.0
|
||||||
|
@ -12,6 +12,7 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
code.linberg.su/linberg/awesome-back v0.0.3 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // indirect
|
github.com/atotto/clipboard v0.1.4 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/catppuccin/go v0.3.0 // indirect
|
github.com/catppuccin/go v0.3.0 // indirect
|
|
@ -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 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -1,18 +1,15 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/charmbracelet/lipgloss/table"
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
terminal "golang.org/x/term"
|
terminal "golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// ANSI256 256 colors, 8-bit
|
// ANSI256 256 colors, 8-bit
|
||||||
|
var (
|
||||||
Blue = lipgloss.Color("21")
|
Blue = lipgloss.Color("21")
|
||||||
Magenta = lipgloss.Color("13")
|
Magenta = lipgloss.Color("13")
|
||||||
Gray = lipgloss.Color("238")
|
Gray = lipgloss.Color("238")
|
||||||
|
@ -20,19 +17,6 @@ var (
|
||||||
Red = lipgloss.Color("9")
|
Red = lipgloss.Color("9")
|
||||||
Green = lipgloss.Color("10")
|
Green = lipgloss.Color("10")
|
||||||
Yellow = lipgloss.Color("11")
|
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 {
|
type TableBuilder struct {
|
||||||
|
@ -84,9 +68,6 @@ func (t *TableBuilder) CreateTable() *table.Table {
|
||||||
t.Table.
|
t.Table.
|
||||||
BorderRow(false).BorderColumn(false).
|
BorderRow(false).BorderColumn(false).
|
||||||
BorderLeft(false).BorderRight(false).BorderTop(false).BorderBottom(false).
|
BorderLeft(false).BorderRight(false).BorderTop(false).BorderBottom(false).
|
||||||
//Border(lipgloss.RoundedBorder()).
|
|
||||||
//BorderRow(true).
|
|
||||||
//BorderStyle(BorderStyle).
|
|
||||||
StyleFunc(func(row, col int) lipgloss.Style {
|
StyleFunc(func(row, col int) lipgloss.Style {
|
||||||
re := lipgloss.NewRenderer(os.Stdout)
|
re := lipgloss.NewRenderer(os.Stdout)
|
||||||
if row == -1 {
|
if row == -1 {
|
||||||
|
@ -114,42 +95,6 @@ func (t *TableBuilder) Justify() {
|
||||||
t.Table.Width(width)
|
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 {
|
func SetColor(color lipgloss.Color, str string) string {
|
||||||
return lipgloss.NewStyle().Foreground(color).Render(str)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue