diff --git a/.gitignore b/.gitignore index 9f6baf7..c715c95 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ coverage *.njsproj *.sln *.sw? +.windsurf/* diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..27a444d --- /dev/null +++ b/backend/.env.example @@ -0,0 +1 @@ +DATABASE_URL=postgres://egor:barsik@localhost:5432/delivery_tracker \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..e69de29 diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go new file mode 100644 index 0000000..5236404 --- /dev/null +++ b/backend/cmd/api/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "context" + "log" + "net/http" + "os" + + db "github.com/chedius/delivery-tracker/internal/db/sqlc" + "github.com/chedius/delivery-tracker/internal/delivery" + "github.com/gin-gonic/gin" + "github.com/jackc/pgx/v5/pgxpool" + "github.com/joho/godotenv" +) + +func main() { + ctx := context.Background() + godotenv.Load() + + dsn := os.Getenv("DATABASE_URL") + pool, err := pgxpool.New(ctx, dsn) + if err != nil { + log.Fatalf("db connect: %v", err) + } + defer pool.Close() + + queries := db.New(pool) + h := delivery.NewHandler(queries) + + r := gin.Default() + + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) + + r.GET("/api/deliveries", h.GetDeliveries) + r.GET("/api/deliveries/:id", h.GetDeliveryByID) + r.POST("/api/deliveries", h.CreateDelivery) + r.PATCH("/api/deliveries/:id", h.UpdateDelivery) + r.DELETE("/api/deliveries/:id", h.DeleteDelivery) + + r.Run(":8080") +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..4b23fca --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,47 @@ +module github.com/chedius/delivery-tracker + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.12.0 + github.com/jackc/pgx/v5 v5.9.1 +) + +require github.com/google/uuid v1.6.0 + +require ( + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // 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.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/joho/godotenv v1.5.1 + 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-20180306012644-bacd9c7ef1dd // 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.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/crypto v0.48.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..5ff2e1b --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,104 @@ +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +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.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +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.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +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.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc= +github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.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.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +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.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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/backend/internal/db/migrations/000001_create_deliveries_table.down.sql b/backend/internal/db/migrations/000001_create_deliveries_table.down.sql new file mode 100644 index 0000000..696756e --- /dev/null +++ b/backend/internal/db/migrations/000001_create_deliveries_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS deliveries; \ No newline at end of file diff --git a/backend/internal/db/migrations/000001_create_deliveries_table.up.sql b/backend/internal/db/migrations/000001_create_deliveries_table.up.sql new file mode 100644 index 0000000..d689ad4 --- /dev/null +++ b/backend/internal/db/migrations/000001_create_deliveries_table.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE deliveries ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + date date NOT NULL, -- хранить как date, отдавать как DD-MM-YYYY + pickup_location varchar(20) NOT NULL, -- warehouse|symbat|nursaya|galaktika + product_name text NOT NULL, + address text NOT NULL, + phone varchar(30) NOT NULL, + additional_phone varchar(30), + has_elevator boolean NOT NULL DEFAULT false, + comment text, + status varchar(20) NOT NULL DEFAULT 'new', -- new|delivered + created_at timestamptz DEFAULT now(), + updated_at timestamptz DEFAULT now() +); \ No newline at end of file diff --git a/backend/internal/db/migrations/000002_create_users_table.down.sql b/backend/internal/db/migrations/000002_create_users_table.down.sql new file mode 100644 index 0000000..365a210 --- /dev/null +++ b/backend/internal/db/migrations/000002_create_users_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS users; \ No newline at end of file diff --git a/backend/internal/db/migrations/000002_create_users_table.up.sql b/backend/internal/db/migrations/000002_create_users_table.up.sql new file mode 100644 index 0000000..1265488 --- /dev/null +++ b/backend/internal/db/migrations/000002_create_users_table.up.sql @@ -0,0 +1,6 @@ +CREATE TABLE users ( + id uuid PRIMARY KEY DEFAULT gen_random_uuid(), + username VARCHAR(50) NOT NULL UNIQUE, + password_hash VARCHAR(255) NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/backend/internal/db/queries/query.sql b/backend/internal/db/queries/query.sql new file mode 100644 index 0000000..fb42e5f --- /dev/null +++ b/backend/internal/db/queries/query.sql @@ -0,0 +1,16 @@ +-- name: GetDeliveriesByDate :many +SELECT * FROM deliveries WHERE date = $1; + +-- name: CreateDelivery :one +INSERT INTO deliveries (date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +RETURNING *; + +-- name: GetDeliveryByID :one +SELECT * FROM deliveries WHERE id = $1; + +-- name: DeleteDelivery :exec +DELETE FROM deliveries WHERE id = $1; + +-- name: UpdateDelivery :exec +UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9; \ No newline at end of file diff --git a/backend/internal/db/sqlc/db.go b/backend/internal/db/sqlc/db.go new file mode 100644 index 0000000..9d485b5 --- /dev/null +++ b/backend/internal/db/sqlc/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +type DBTX interface { + Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) + Query(context.Context, string, ...interface{}) (pgx.Rows, error) + QueryRow(context.Context, string, ...interface{}) pgx.Row +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/backend/internal/db/sqlc/models.go b/backend/internal/db/sqlc/models.go new file mode 100644 index 0000000..ec79662 --- /dev/null +++ b/backend/internal/db/sqlc/models.go @@ -0,0 +1,31 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "github.com/jackc/pgx/v5/pgtype" +) + +type Delivery struct { + ID pgtype.UUID `db:"id" json:"id"` + Date pgtype.Date `db:"date" json:"date"` + PickupLocation string `db:"pickup_location" json:"pickup_location"` + ProductName string `db:"product_name" json:"product_name"` + Address string `db:"address" json:"address"` + Phone string `db:"phone" json:"phone"` + AdditionalPhone pgtype.Text `db:"additional_phone" json:"additional_phone"` + HasElevator bool `db:"has_elevator" json:"has_elevator"` + Comment pgtype.Text `db:"comment" json:"comment"` + Status string `db:"status" json:"status"` + CreatedAt pgtype.Timestamptz `db:"created_at" json:"created_at"` + UpdatedAt pgtype.Timestamptz `db:"updated_at" json:"updated_at"` +} + +type User struct { + ID pgtype.UUID `db:"id" json:"id"` + Username string `db:"username" json:"username"` + PasswordHash string `db:"password_hash" json:"password_hash"` + CreatedAt pgtype.Timestamp `db:"created_at" json:"created_at"` +} diff --git a/backend/internal/db/sqlc/querier.go b/backend/internal/db/sqlc/querier.go new file mode 100644 index 0000000..ff9900d --- /dev/null +++ b/backend/internal/db/sqlc/querier.go @@ -0,0 +1,21 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +type Querier interface { + CreateDelivery(ctx context.Context, arg CreateDeliveryParams) (Delivery, error) + DeleteDelivery(ctx context.Context, id pgtype.UUID) error + GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([]Delivery, error) + GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error) + UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) error +} + +var _ Querier = (*Queries)(nil) diff --git a/backend/internal/db/sqlc/query.sql.go b/backend/internal/db/sqlc/query.sql.go new file mode 100644 index 0000000..e385b14 --- /dev/null +++ b/backend/internal/db/sqlc/query.sql.go @@ -0,0 +1,159 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: query.sql + +package db + +import ( + "context" + + "github.com/jackc/pgx/v5/pgtype" +) + +const createDelivery = `-- name: CreateDelivery :one +INSERT INTO deliveries (date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment) +VALUES ($1, $2, $3, $4, $5, $6, $7, $8) +RETURNING id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at +` + +type CreateDeliveryParams struct { + Date pgtype.Date `db:"date" json:"date"` + PickupLocation string `db:"pickup_location" json:"pickup_location"` + ProductName string `db:"product_name" json:"product_name"` + Address string `db:"address" json:"address"` + Phone string `db:"phone" json:"phone"` + AdditionalPhone pgtype.Text `db:"additional_phone" json:"additional_phone"` + HasElevator bool `db:"has_elevator" json:"has_elevator"` + Comment pgtype.Text `db:"comment" json:"comment"` +} + +func (q *Queries) CreateDelivery(ctx context.Context, arg CreateDeliveryParams) (Delivery, error) { + row := q.db.QueryRow(ctx, createDelivery, + arg.Date, + arg.PickupLocation, + arg.ProductName, + arg.Address, + arg.Phone, + arg.AdditionalPhone, + arg.HasElevator, + arg.Comment, + ) + var i Delivery + err := row.Scan( + &i.ID, + &i.Date, + &i.PickupLocation, + &i.ProductName, + &i.Address, + &i.Phone, + &i.AdditionalPhone, + &i.HasElevator, + &i.Comment, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const deleteDelivery = `-- name: DeleteDelivery :exec +DELETE FROM deliveries WHERE id = $1 +` + +func (q *Queries) DeleteDelivery(ctx context.Context, id pgtype.UUID) error { + _, err := q.db.Exec(ctx, deleteDelivery, id) + return err +} + +const getDeliveriesByDate = `-- name: GetDeliveriesByDate :many +SELECT id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at FROM deliveries WHERE date = $1 +` + +func (q *Queries) GetDeliveriesByDate(ctx context.Context, date pgtype.Date) ([]Delivery, error) { + rows, err := q.db.Query(ctx, getDeliveriesByDate, date) + if err != nil { + return nil, err + } + defer rows.Close() + items := []Delivery{} + for rows.Next() { + var i Delivery + if err := rows.Scan( + &i.ID, + &i.Date, + &i.PickupLocation, + &i.ProductName, + &i.Address, + &i.Phone, + &i.AdditionalPhone, + &i.HasElevator, + &i.Comment, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getDeliveryByID = `-- name: GetDeliveryByID :one +SELECT id, date, pickup_location, product_name, address, phone, additional_phone, has_elevator, comment, status, created_at, updated_at FROM deliveries WHERE id = $1 +` + +func (q *Queries) GetDeliveryByID(ctx context.Context, id pgtype.UUID) (Delivery, error) { + row := q.db.QueryRow(ctx, getDeliveryByID, id) + var i Delivery + err := row.Scan( + &i.ID, + &i.Date, + &i.PickupLocation, + &i.ProductName, + &i.Address, + &i.Phone, + &i.AdditionalPhone, + &i.HasElevator, + &i.Comment, + &i.Status, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const updateDelivery = `-- name: UpdateDelivery :exec +UPDATE deliveries SET date = $1, pickup_location = $2, product_name = $3, address = $4, phone = $5, additional_phone = $6, has_elevator = $7, comment = $8, updated_at = NOW() WHERE id = $9 +` + +type UpdateDeliveryParams struct { + Date pgtype.Date `db:"date" json:"date"` + PickupLocation string `db:"pickup_location" json:"pickup_location"` + ProductName string `db:"product_name" json:"product_name"` + Address string `db:"address" json:"address"` + Phone string `db:"phone" json:"phone"` + AdditionalPhone pgtype.Text `db:"additional_phone" json:"additional_phone"` + HasElevator bool `db:"has_elevator" json:"has_elevator"` + Comment pgtype.Text `db:"comment" json:"comment"` + ID pgtype.UUID `db:"id" json:"id"` +} + +func (q *Queries) UpdateDelivery(ctx context.Context, arg UpdateDeliveryParams) error { + _, err := q.db.Exec(ctx, updateDelivery, + arg.Date, + arg.PickupLocation, + arg.ProductName, + arg.Address, + arg.Phone, + arg.AdditionalPhone, + arg.HasElevator, + arg.Comment, + arg.ID, + ) + return err +} diff --git a/backend/internal/delivery/handler.go b/backend/internal/delivery/handler.go new file mode 100644 index 0000000..0355d87 --- /dev/null +++ b/backend/internal/delivery/handler.go @@ -0,0 +1,181 @@ +package delivery + +import ( + "net/http" + "time" + + sqlc "github.com/chedius/delivery-tracker/internal/db/sqlc" + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "github.com/jackc/pgx/v5/pgtype" +) + +type Handler struct { + queries *sqlc.Queries +} + +// DeliveryRequest represents the request body for creating or updating a delivery +type DeliveryRequest struct { + Date string `json:"date" binding:"required"` // DD-MM-YYYY + PickupLocation string `json:"pickup_location" binding:"required,oneof=warehouse symbat nursaya galaktika"` + ProductName string `json:"product_name" binding:"required"` + Address string `json:"address" binding:"required"` + Phone string `json:"phone" binding:"required"` + AdditionalPhone string `json:"additional_phone"` + HasElevator bool `json:"has_elevator"` + Comment string `json:"comment"` +} + +func NewHandler(queries *sqlc.Queries) *Handler { + return &Handler{queries: queries} +} + +// GET /api/deliveries/:id +func (h *Handler) GetDeliveryByID(c *gin.Context) { + id := c.Param("id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID is required"}) + return + } + parsedID, err := uuid.Parse(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid UUID format", "details": err.Error()}) + return + } + + delivery, err := h.queries.GetDeliveryByID(c.Request.Context(), pgtype.UUID{Bytes: parsedID, Valid: true}) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get delivery", "details": err.Error(), "id": id, "bytes": [16]byte([]byte(id))}) + return + } + + c.JSON(http.StatusOK, gin.H{"delivery": delivery}) +} + +// GET /api/deliveries?date=DD-MM-YYYY +func (h *Handler) GetDeliveries(c *gin.Context) { + t, err := parseDate(c.Query("date")) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid date format", "details": err.Error()}) + return + } + date := pgtype.Date{Time: t, Valid: true} + deliveries, err := h.queries.GetDeliveriesByDate(c.Request.Context(), date) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get deliveries", "details": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"deliveries": deliveries}) +} + +// POST /api/deliveries +func (h *Handler) CreateDelivery(c *gin.Context) { + var req DeliveryRequest = DeliveryRequest{} + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()}) + return + } + + // Parse date from DD-MM-YYYY + t, err := parseDate(req.Date) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid date format", "details": err.Error()}) + return + } + + params := sqlc.CreateDeliveryParams{ + Date: pgtype.Date{Time: t, Valid: true}, + PickupLocation: req.PickupLocation, + ProductName: req.ProductName, + Address: req.Address, + Phone: req.Phone, + AdditionalPhone: pgtype.Text{String: req.AdditionalPhone, Valid: req.AdditionalPhone != ""}, + HasElevator: req.HasElevator, + Comment: pgtype.Text{String: req.Comment, Valid: true}, + } + res, err := h.queries.CreateDelivery(c.Request.Context(), params) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create delivery", "details": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Delivery created", "id": res.ID.String()}) +} + +// PATCH /api/deliveries/:id +func (h *Handler) UpdateDelivery(c *gin.Context) { + var req DeliveryRequest = DeliveryRequest{} + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()}) + return + } + + id := c.Param("id") + + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID is required"}) + return + } + + parsedID, err := uuid.Parse(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid UUID format", "details": err.Error()}) + return + } + t, err := parseDate(req.Date) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid date format", "details": err.Error()}) + return + } + + if err := h.queries.UpdateDelivery(c.Request.Context(), sqlc.UpdateDeliveryParams{ + ID: pgtype.UUID{Bytes: parsedID, Valid: true}, + Date: pgtype.Date{Time: t, Valid: true}, + PickupLocation: req.PickupLocation, + ProductName: req.ProductName, + Address: req.Address, + Phone: req.Phone, + AdditionalPhone: pgtype.Text{String: req.AdditionalPhone, Valid: req.AdditionalPhone != ""}, + HasElevator: req.HasElevator, + Comment: pgtype.Text{String: req.Comment, Valid: true}, + }); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update delivery", "details": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Delivery updated"}) +} + +// DELETE /api/deliveries/:id +func (h *Handler) DeleteDelivery(c *gin.Context) { + id := c.Param("id") + + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "ID is required"}) + return + } + + parsedID, err := uuid.Parse(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid UUID format", "details": err.Error()}) + return + } + + if err := h.queries.DeleteDelivery(c.Request.Context(), pgtype.UUID{Bytes: parsedID, Valid: true}); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete delivery", "details": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Delivery deleted"}) +} + +func parseDate(dateStr string) (time.Time, error) { + t, err := time.Parse("02-01-2006", dateStr) + if err != nil { + return time.Time{}, err + } + return t, nil +} diff --git a/backend/sqlc.yaml b/backend/sqlc.yaml new file mode 100644 index 0000000..34a1a85 --- /dev/null +++ b/backend/sqlc.yaml @@ -0,0 +1,17 @@ +version: "2" +sql: + - engine: "postgresql" # СУБД: postgresql, mysql или sqlite + queries: "internal/db/queries/" # Путь к .sql файлам с вашими SELECT/INSERT/UPDATE + schema: "internal/db/migrations/" # Путь к файлам схемы (миграциям) + gen: + go: + package: "db" # Имя Go-пакета для сгенерированного кода + out: "internal/db/sqlc" # Директория, куда sqlc положит файлы + sql_package: "pgx/v5" # Драйвер: pgx/v5 (рекомендуется) или database/sql + + # Дополнительные полезные опции: + emit_json_tags: true # Добавить json-теги в структуры моделей + emit_db_tags: true # Добавить db-теги + emit_interface: true # Создать интерфейс Querier (удобно для моков в тестах) + emit_exact_table_names: false # Если true, имена структур будут как в БД (users -> Users) + emit_empty_slices: true # Возвращать [] вместо nil для пустых результатов diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..53a30f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +services: + # Development service with hot reload + frontend-dev: + image: node:20-alpine + working_dir: /app + volumes: + - ./frontend:/app + - node_modules:/app/node_modules + ports: + - "5173:5173" + environment: + - NODE_ENV=development + command: sh -c "yarn install && yarn dev --host" + stdin_open: true + tty: true + + # Production frontend service + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + target: production + ports: + - "8080:80" + restart: unless-stopped + +volumes: + node_modules: diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..ace6a06 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,17 @@ +node_modules +npm-debug.log +yarn-error.log +dist +.git +.gitignore +README.md +.env +.env.local +.DS_Store +.vscode +.idea +*.md +Dockerfile +docker-compose.yml +.dockerignore +nginx.conf diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..dd00ae3 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,29 @@ +# syntax=docker/dockerfile:1 + +# Stage 1: Build +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy package files +COPY package.json yarn.lock ./ +RUN yarn install --frozen-lockfile + +# Copy source code +COPY . . + +# Build application +RUN yarn build + +# Stage 2: Production +FROM nginx:alpine AS production + +# Copy custom nginx config +COPY nginx.conf /etc/nginx/conf.d/default.conf + +# Copy built files from builder stage +COPY --from=builder /app/dist /usr/share/nginx/html + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/eslint.config.js b/frontend/eslint.config.js similarity index 100% rename from eslint.config.js rename to frontend/eslint.config.js diff --git a/index.html b/frontend/index.html similarity index 100% rename from index.html rename to frontend/index.html diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..52c5cda --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,30 @@ +server { + listen 80; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } + + # Handle client-side routing + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} diff --git a/package.json b/frontend/package.json similarity index 100% rename from package.json rename to frontend/package.json diff --git a/public/favicon.svg b/frontend/public/favicon.svg similarity index 100% rename from public/favicon.svg rename to frontend/public/favicon.svg diff --git a/public/icons.svg b/frontend/public/icons.svg similarity index 100% rename from public/icons.svg rename to frontend/public/icons.svg diff --git a/public/manifest.json b/frontend/public/manifest.json similarity index 100% rename from public/manifest.json rename to frontend/public/manifest.json diff --git a/src/App.tsx b/frontend/src/App.tsx similarity index 100% rename from src/App.tsx rename to frontend/src/App.tsx diff --git a/src/assets/hero.png b/frontend/src/assets/hero.png similarity index 100% rename from src/assets/hero.png rename to frontend/src/assets/hero.png diff --git a/src/assets/vite.svg b/frontend/src/assets/vite.svg similarity index 100% rename from src/assets/vite.svg rename to frontend/src/assets/vite.svg diff --git a/src/components/delivery/DeliveryCard.tsx b/frontend/src/components/delivery/DeliveryCard.tsx similarity index 100% rename from src/components/delivery/DeliveryCard.tsx rename to frontend/src/components/delivery/DeliveryCard.tsx diff --git a/src/components/delivery/DeliveryForm.tsx b/frontend/src/components/delivery/DeliveryForm.tsx similarity index 100% rename from src/components/delivery/DeliveryForm.tsx rename to frontend/src/components/delivery/DeliveryForm.tsx diff --git a/src/components/delivery/DeliveryList.tsx b/frontend/src/components/delivery/DeliveryList.tsx similarity index 100% rename from src/components/delivery/DeliveryList.tsx rename to frontend/src/components/delivery/DeliveryList.tsx diff --git a/src/components/delivery/DeliveryRow.tsx b/frontend/src/components/delivery/DeliveryRow.tsx similarity index 100% rename from src/components/delivery/DeliveryRow.tsx rename to frontend/src/components/delivery/DeliveryRow.tsx diff --git a/src/components/delivery/StatusBadge.tsx b/frontend/src/components/delivery/StatusBadge.tsx similarity index 100% rename from src/components/delivery/StatusBadge.tsx rename to frontend/src/components/delivery/StatusBadge.tsx diff --git a/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx similarity index 100% rename from src/components/ui/Button.tsx rename to frontend/src/components/ui/Button.tsx diff --git a/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx similarity index 100% rename from src/components/ui/Card.tsx rename to frontend/src/components/ui/Card.tsx diff --git a/src/components/ui/Input.tsx b/frontend/src/components/ui/Input.tsx similarity index 100% rename from src/components/ui/Input.tsx rename to frontend/src/components/ui/Input.tsx diff --git a/src/components/ui/Modal.tsx b/frontend/src/components/ui/Modal.tsx similarity index 100% rename from src/components/ui/Modal.tsx rename to frontend/src/components/ui/Modal.tsx diff --git a/src/components/ui/Select.tsx b/frontend/src/components/ui/Select.tsx similarity index 100% rename from src/components/ui/Select.tsx rename to frontend/src/components/ui/Select.tsx diff --git a/src/components/ui/index.ts b/frontend/src/components/ui/index.ts similarity index 100% rename from src/components/ui/index.ts rename to frontend/src/components/ui/index.ts diff --git a/src/hooks/useWebSocket.ts b/frontend/src/hooks/useWebSocket.ts similarity index 100% rename from src/hooks/useWebSocket.ts rename to frontend/src/hooks/useWebSocket.ts diff --git a/src/index.css b/frontend/src/index.css similarity index 100% rename from src/index.css rename to frontend/src/index.css diff --git a/src/main.tsx b/frontend/src/main.tsx similarity index 100% rename from src/main.tsx rename to frontend/src/main.tsx diff --git a/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx similarity index 100% rename from src/pages/Dashboard.tsx rename to frontend/src/pages/Dashboard.tsx diff --git a/src/pages/DeliveryListPage.tsx b/frontend/src/pages/DeliveryListPage.tsx similarity index 100% rename from src/pages/DeliveryListPage.tsx rename to frontend/src/pages/DeliveryListPage.tsx diff --git a/src/stores/deliveryStore.ts b/frontend/src/stores/deliveryStore.ts similarity index 100% rename from src/stores/deliveryStore.ts rename to frontend/src/stores/deliveryStore.ts diff --git a/src/types/index.ts b/frontend/src/types/index.ts similarity index 100% rename from src/types/index.ts rename to frontend/src/types/index.ts diff --git a/src/utils/mockData.ts b/frontend/src/utils/mockData.ts similarity index 100% rename from src/utils/mockData.ts rename to frontend/src/utils/mockData.ts diff --git a/tsconfig.app.json b/frontend/tsconfig.app.json similarity index 100% rename from tsconfig.app.json rename to frontend/tsconfig.app.json diff --git a/tsconfig.json b/frontend/tsconfig.json similarity index 100% rename from tsconfig.json rename to frontend/tsconfig.json diff --git a/tsconfig.node.json b/frontend/tsconfig.node.json similarity index 100% rename from tsconfig.node.json rename to frontend/tsconfig.node.json diff --git a/vite.config.ts b/frontend/vite.config.ts similarity index 100% rename from vite.config.ts rename to frontend/vite.config.ts diff --git a/yarn.lock b/frontend/yarn.lock similarity index 100% rename from yarn.lock rename to frontend/yarn.lock