From 65c3add95e817e8a2ee3c94b73c3ca232fd713a9 Mon Sep 17 00:00:00 2001 From: "i.smyshlyaev" Date: Thu, 16 Oct 2025 12:55:36 +0300 Subject: [PATCH] first commit --- .idea/.gitignore | 8 ++ .idea/awesome-back.iml | 9 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + cmd/main.go | 60 +++++++++ go.mod | 39 ++++++ go.sum | 88 +++++++++++++ internal/datastore/mock_coffee_repository.go | 34 +++++ internal/datastore/mock_order_repository.go | 82 ++++++++++++ internal/datastore/mock_pastry_repository.go | 51 ++++++++ internal/domain/entities/coffee.go | 9 ++ internal/domain/entities/order.go | 14 +++ internal/domain/entities/pastry.go | 9 ++ .../domain/repositories/coffee_repository.go | 8 ++ .../domain/repositories/order_repository.go | 9 ++ .../domain/repositories/pastry_repository.go | 9 ++ internal/handler/coffee_handler.go | 45 +++++++ internal/handler/order_handler.go | 62 +++++++++ internal/handler/pastry_handler.go | 61 +++++++++ internal/handler/response.go | 55 ++++++++ internal/service/coffee_service.go | 39 ++++++ internal/service/order_service.go | 57 +++++++++ internal/service/pastry_service.go | 52 ++++++++ pkg/errors/errors.go | 118 ++++++++++++++++++ 24 files changed, 932 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/awesome-back.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/datastore/mock_coffee_repository.go create mode 100644 internal/datastore/mock_order_repository.go create mode 100644 internal/datastore/mock_pastry_repository.go create mode 100644 internal/domain/entities/coffee.go create mode 100644 internal/domain/entities/order.go create mode 100644 internal/domain/entities/pastry.go create mode 100644 internal/domain/repositories/coffee_repository.go create mode 100644 internal/domain/repositories/order_repository.go create mode 100644 internal/domain/repositories/pastry_repository.go create mode 100644 internal/handler/coffee_handler.go create mode 100644 internal/handler/order_handler.go create mode 100644 internal/handler/pastry_handler.go create mode 100644 internal/handler/response.go create mode 100644 internal/service/coffee_service.go create mode 100644 internal/service/order_service.go create mode 100644 internal/service/pastry_service.go create mode 100644 pkg/errors/errors.go diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/awesome-back.iml b/.idea/awesome-back.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/awesome-back.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..22862e2 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..3871d72 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/datastore" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/handler" + "code.linberg.su/linberg/awesome-cli/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/go.mod b/go.mod new file mode 100644 index 0000000..bb3a973 --- /dev/null +++ b/go.mod @@ -0,0 +1,39 @@ +module code.linberg.su/linberg/awesome-cli/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/go.sum b/go.sum new file mode 100644 index 0000000..a25fff5 --- /dev/null +++ b/go.sum @@ -0,0 +1,88 @@ +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/internal/datastore/mock_coffee_repository.go b/internal/datastore/mock_coffee_repository.go new file mode 100644 index 0000000..ef8264c --- /dev/null +++ b/internal/datastore/mock_coffee_repository.go @@ -0,0 +1,34 @@ +package datastore + +import ( + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/repositories" + "code.linberg.su/linberg/awesome-cli/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/internal/datastore/mock_order_repository.go b/internal/datastore/mock_order_repository.go new file mode 100644 index 0000000..5cbf8eb --- /dev/null +++ b/internal/datastore/mock_order_repository.go @@ -0,0 +1,82 @@ +package datastore + +import ( + "time" + + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/repositories" + "code.linberg.su/linberg/awesome-cli/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/internal/datastore/mock_pastry_repository.go b/internal/datastore/mock_pastry_repository.go new file mode 100644 index 0000000..e712221 --- /dev/null +++ b/internal/datastore/mock_pastry_repository.go @@ -0,0 +1,51 @@ +package datastore + +import ( + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/repositories" + "code.linberg.su/linberg/awesome-cli/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/internal/domain/entities/coffee.go b/internal/domain/entities/coffee.go new file mode 100644 index 0000000..fb1f8f0 --- /dev/null +++ b/internal/domain/entities/coffee.go @@ -0,0 +1,9 @@ +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/internal/domain/entities/order.go b/internal/domain/entities/order.go new file mode 100644 index 0000000..68e6888 --- /dev/null +++ b/internal/domain/entities/order.go @@ -0,0 +1,14 @@ +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/internal/domain/entities/pastry.go b/internal/domain/entities/pastry.go new file mode 100644 index 0000000..0a2f4b4 --- /dev/null +++ b/internal/domain/entities/pastry.go @@ -0,0 +1,9 @@ +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/internal/domain/repositories/coffee_repository.go b/internal/domain/repositories/coffee_repository.go new file mode 100644 index 0000000..62189bc --- /dev/null +++ b/internal/domain/repositories/coffee_repository.go @@ -0,0 +1,8 @@ +package repositories + +import "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + +type CoffeeRepository interface { + FindAll() ([]entities.Coffee, error) + FindByID(id int) (*entities.Coffee, error) +} diff --git a/internal/domain/repositories/order_repository.go b/internal/domain/repositories/order_repository.go new file mode 100644 index 0000000..01b68b3 --- /dev/null +++ b/internal/domain/repositories/order_repository.go @@ -0,0 +1,9 @@ +package repositories + +import "code.linberg.su/linberg/awesome-cli/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/internal/domain/repositories/pastry_repository.go b/internal/domain/repositories/pastry_repository.go new file mode 100644 index 0000000..d48c9e8 --- /dev/null +++ b/internal/domain/repositories/pastry_repository.go @@ -0,0 +1,9 @@ +package repositories + +import "code.linberg.su/linberg/awesome-cli/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/internal/handler/coffee_handler.go b/internal/handler/coffee_handler.go new file mode 100644 index 0000000..69d170e --- /dev/null +++ b/internal/handler/coffee_handler.go @@ -0,0 +1,45 @@ +package handler + +import ( + "strconv" + + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/service" + "code.linberg.su/linberg/awesome-cli/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/internal/handler/order_handler.go b/internal/handler/order_handler.go new file mode 100644 index 0000000..5d4218f --- /dev/null +++ b/internal/handler/order_handler.go @@ -0,0 +1,62 @@ +package handler + +import ( + "strconv" + + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/service" + "code.linberg.su/linberg/awesome-cli/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/internal/handler/pastry_handler.go b/internal/handler/pastry_handler.go new file mode 100644 index 0000000..6a1a717 --- /dev/null +++ b/internal/handler/pastry_handler.go @@ -0,0 +1,61 @@ +package handler + +import ( + "strconv" + + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/service" + "code.linberg.su/linberg/awesome-cli/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/internal/handler/response.go b/internal/handler/response.go new file mode 100644 index 0000000..0c1d091 --- /dev/null +++ b/internal/handler/response.go @@ -0,0 +1,55 @@ +package handler + +import ( + "errors" + "net/http" + + appErr "code.linberg.su/linberg/awesome-cli/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/internal/service/coffee_service.go b/internal/service/coffee_service.go new file mode 100644 index 0000000..8ac96b3 --- /dev/null +++ b/internal/service/coffee_service.go @@ -0,0 +1,39 @@ +package service + +import ( + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/repositories" + "code.linberg.su/linberg/awesome-cli/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/internal/service/order_service.go b/internal/service/order_service.go new file mode 100644 index 0000000..240894b --- /dev/null +++ b/internal/service/order_service.go @@ -0,0 +1,57 @@ +// internal/service/order_service.go +package service + +import ( + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/repositories" + "code.linberg.su/linberg/awesome-cli/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/internal/service/pastry_service.go b/internal/service/pastry_service.go new file mode 100644 index 0000000..8fc0fdd --- /dev/null +++ b/internal/service/pastry_service.go @@ -0,0 +1,52 @@ +package service + +import ( + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/entities" + "code.linberg.su/linberg/awesome-cli/awesome-back/internal/domain/repositories" + "code.linberg.su/linberg/awesome-cli/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/pkg/errors/errors.go b/pkg/errors/errors.go new file mode 100644 index 0000000..1d27f64 --- /dev/null +++ b/pkg/errors/errors.go @@ -0,0 +1,118 @@ +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) +}