diff --git a/enshi_back/go.mod b/enshi_back/go.mod index 0d738c0..d4efe37 100644 --- a/enshi_back/go.mod +++ b/enshi_back/go.mod @@ -3,45 +3,43 @@ module enshi go 1.23 require ( - github.com/gin-gonic/gin v1.10.0 - github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.7.0 + github.com/jackc/pgx/v5 v5.7.1 github.com/joho/godotenv v1.5.1 - golang.org/x/crypto v0.27.0 ) require ( - github.com/bytedance/sonic v1.12.2 // indirect - github.com/bytedance/sonic/loader v0.2.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect - github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.5 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.10.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.22.0 // indirect - github.com/goccy/go-json v0.10.3 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/google/uuid v1.6.0 // 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.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.10.9 // 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.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.10.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.27.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/enshi_back/go.sum b/enshi_back/go.sum index 5f69f1f..237fb04 100644 --- a/enshi_back/go.sum +++ b/enshi_back/go.sum @@ -1,41 +1,28 @@ -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= -github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg= -github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= -github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= -github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= -github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= 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.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= -github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= 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.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= -github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +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-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -43,26 +30,22 @@ 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-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= 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.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/pgx/v5 v5.7.0 h1:FG6VLIdzvAPhnYqP14sQ2xhFLkiUQHCs6ySqO91kF4g= -github.com/jackc/pgx/v5 v5.7.0/go.mod h1:awP1KNnjylvpxHuHP63gzjhnGkI1iw+PMoIwvoleN/8= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +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.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= 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/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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= @@ -70,48 +53,43 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w 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.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +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/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8= -golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= -golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 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= diff --git a/enshi_back/utils/auth.go b/enshi_back/utils/auth.go new file mode 100644 index 0000000..06ed3c7 --- /dev/null +++ b/enshi_back/utils/auth.go @@ -0,0 +1,78 @@ +package dependencies + +import ( + "fmt" + "time" + + "github.com/golang-jwt/jwt/v5" +) + +var ( + // Secret key to sign JWT-tokens + SecretKey string +) + +// Generating new token with user info +// +// userInfo = { "id": int, "name": string } +func CreateToken(userInfo map[string]interface{}) (string, error) { + // Create new token + token := jwt.New(jwt.SigningMethodHS256) + + // Get claims of this token (payload) + claims := token.Claims.(jwt.MapClaims) + + // Add some info to claims + claims["name"] = userInfo["name"] + claims["id"] = userInfo["id"] + claims["exp"] = time.Now().Add(time.Hour * 1).Unix() + + // Get string token that will be passed to user + // We sign this token with SecretKey + tokenString, err := token.SignedString([]byte(SecretKey)) + if err != nil { + return "", fmt.Errorf("error generate token: " + err.Error()) + } + + return tokenString, nil +} + +// # Returns (claims, nil) if token is valid and all good +// +// # Returns (nil, error) if token is invalid for some reason +// +// Claims consists of name, id, exp(expiration time) +func ValidateToken(tokenSting string) (jwt.MapClaims, error) { + // Parsing string version of token to *jwt.Token + token, err := jwt.Parse( + // First arg -> string to parse + tokenSting, + + // Second arg -> function that check hash method of token + // Return Secret key with what token string gonna be checked + func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("wrong hash method: %v", token.Header["alg"]) + } + + return []byte(SecretKey), nil + }) + + if err != nil { + return nil, fmt.Errorf("error in token: %v", err.Error()) + } + + // Check token expiration time and if it is valid + // Get claims from parsed token + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + expirationTime := time.Unix(int64(claims["exp"].(float64)), 0) + + if expirationTime.Before(time.Now()) { + return nil, fmt.Errorf("token has expired") + } + + return claims, nil + } else { + return nil, fmt.Errorf("invalid token") + } +} diff --git a/enshi_back/utils/colors.go b/enshi_back/utils/colors.go new file mode 100644 index 0000000..64febe3 --- /dev/null +++ b/enshi_back/utils/colors.go @@ -0,0 +1,11 @@ +package utils + +var ResetColor = "\033[0m" +var RedColor = "\033[31m" +var GreenColor = "\033[32m" +var YellowColor = "\033[33m" +var BlueColor = "\033[34m" +var MagentaColor = "\033[35m" +var CyanColor = "\033[36m" +var GrayColor = "\033[37m" +var WhiteColor = "\033[97m" diff --git a/enshi_back/utils/database.go b/enshi_back/utils/database.go new file mode 100644 index 0000000..859a6dd --- /dev/null +++ b/enshi_back/utils/database.go @@ -0,0 +1,31 @@ +package utils + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" +) + +// Pgx connection to database +var Dbx *pgxpool.Pool + +func SetupDatabase() error { + + // Url to connect + url := "postgres://root:JET159sam753@nekiiinkognito.ru:5432/recipes" + + var err error + + // Connecting to database + Dbx, err = pgxpool.New(context.Background(), url) + + if err != nil { + fmt.Println("Unable to connect") + fmt.Println(err) + } else { + fmt.Println("Connected successfully!") + } + + return err +} diff --git a/enshi_back/utils/passwordHashing.go b/enshi_back/utils/passwordHashing.go new file mode 100644 index 0000000..2e4705d --- /dev/null +++ b/enshi_back/utils/passwordHashing.go @@ -0,0 +1,158 @@ +package utils + +import ( + "bytes" + "crypto/rand" + "encoding/base64" + "fmt" + "strings" + + "github.com/joho/godotenv" + "golang.org/x/crypto/argon2" +) + +type Argon2Hash struct { + time uint32 + memory uint32 + threads uint8 + keyLen uint32 + saltLen uint32 +} + +type HashSalt struct { + hash []byte + salt []byte + stringToStore string +} + +// Initializer for algorithm +func NewArgon2Hash(time, keyLen, saltLen, memory uint32, threads uint8) *Argon2Hash { + return &Argon2Hash{ + time: time, + keyLen: keyLen, + saltLen: saltLen, + memory: memory, + threads: threads, + } +} + +// Generating salt (byte slice with pseudorandom symbols) +func SaltGen(length uint32) ([]byte, error) { + salt := make([]byte, length) + + // Read pseudorandom numbers and write them in salt + _, err := rand.Read(salt) + + if err != nil { + return nil, err + } + + return salt, nil + +} + +// Generating hash with given password and salt +func (a *Argon2Hash) HashGen(password, salt []byte) (*HashSalt, error) { + var err error + + // If salt len is zero => we hashing new password => we need new salt ;) + if len(salt) == 0 { + salt, err = SaltGen(a.saltLen) + } + + if err != nil { + return nil, err + } + + // Generating password hash with given params + hash := argon2.IDKey(password, salt, a.time, a.memory, a.threads, a.keyLen) + + // Salt and hash is byte slice and to properly read them (as string) we need to use this functions + saltDecoded := base64.RawStdEncoding.EncodeToString(salt) + hashDecoded := base64.RawStdEncoding.EncodeToString(hash) + + // Creating string with salt and password hash + stringToStore := fmt.Sprintf("$m=%d,t=%d$%s$%s", a.memory, a.time, saltDecoded, hashDecoded) + + // This is unnecessary structure i created following the guide 0_0 + return &HashSalt{hash: hash, salt: salt, stringToStore: stringToStore}, nil + +} + +// Returns error if password hashes are not equal +func (a *Argon2Hash) Compare(hash, salt, password []byte) error { + + // We hash given password with salt we created for original one + // so we can check wether hashes are equal + hashSalt, err := a.HashGen(password, salt) + + if err != nil { + return err + } + + // Comparing hashes + if !bytes.Equal(hash, hashSalt.hash) { + return fmt.Errorf("invalid password (hashes does not match)") + } + + return nil +} + +// Function to extract hash and salt from password string +// that is stored in db +// +// returns hash, salt, error +func DecodeArgon2String(passwordHash string) ([]byte, []byte, error) { + values := strings.Split(passwordHash, "$") + + // Transform string hash in byte slice + salt, err := base64.RawStdEncoding.Strict().DecodeString(values[2]) + if err != nil { + return nil, nil, err + } + + // Transform string hash in byte slice + hash, err := base64.RawStdEncoding.Strict().DecodeString(values[3]) + if err != nil { + return nil, nil, err + } + + return hash, salt, nil +} + +var Argon2Hasher = NewArgon2Hash(2, 100, 32, 64*1024, 1) + +func Test() { + pas := []byte("qwerty1") + + testDbString := "$m=65536,t=2$u0h2MoT48NXJFIRQXW9/i0tNDu427RJv3vIeZQIm8FU$QtUmjmPhsBgWGloNQVRoFkLkHnQwuCqRVfgKA0Sm2QNMPc86vSLxQ/c8JUXroO37qwXdfC9DNvTnm/OOi7GfTBGnJJotLBlonG/9epAMGY453s9UZVeghmvftCagXzPS9QB3cg" + + var salt []byte + + cringe, err := Argon2Hasher.HashGen(pas, salt) + + if err != nil { + return + } + + storedH, storedS, err := DecodeArgon2String(testDbString) + if err != nil { + fmt.Println("CRINGE", err) + return + } + + err = Argon2Hasher.Compare(storedH, storedS, []byte("qwerty1")) + if err != nil { + fmt.Println("DONT MATCH") + } else { + fmt.Println("MATCH") + } + + if err := godotenv.Load("secrets.env"); err != nil { + fmt.Println("Error load .env") + } + + // fmt.Println(testDbString) + fmt.Printf("%s", cringe.stringToStore) + fmt.Print("\n\n\n\n") +} diff --git a/enshi_back/utils/routesSetup.go b/enshi_back/utils/routesSetup.go new file mode 100644 index 0000000..eca6a32 --- /dev/null +++ b/enshi_back/utils/routesSetup.go @@ -0,0 +1,1108 @@ +package utils + +import ( + "context" + "fmt" + "math/rand" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +func testRoute1(c *gin.Context) { + testInfo := map[string]interface{}{ + "name": "Kyle", + "id": 1, + } + + newToken, err := CreateToken(testInfo) + + if err != nil { + c.IndentedJSON(401, gin.H{"error": err.Error()}) + return + } + + c.IndentedJSON(200, gin.H{"token": newToken}) +} + +func testRoute2(c *gin.Context) { + type content struct { + Email string + Name string + } + + // Check correct type of receiving data + if c.ContentType() != "application/json" { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "content type should by 'application/json'"}) + return + } + + var body content + + if err := c.BindJSON(&body); err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "invalid body of request" + err.Error()}) + return + } + + // Get data from token + temp, err := c.Get("claims") + + if !err { + c.IndentedJSON(http.StatusBadRequest, gin.H{"Error": "your token does not contain information needed"}) + return + } + + c.IndentedJSON(http.StatusOK, gin.H{"1": temp, "2": body}) +} + +func getRecipeInformation(c *gin.Context) { + + recipe_id, err := strconv.Atoi(strings.Trim(c.Param("recipe_id"), "/")) + if err != nil { + c.IndentedJSON(404, gin.H{"err": err.Error()}) + return + } + + returnData := map[string]interface{}{} + + recipe, err := Dbx.Query(context.Background(), "select * from recipes where recipe_id = $1", recipe_id) + if err != nil { + c.IndentedJSON(500, gin.H{"err": err.Error()}) + return + } + + isMore := recipe.Next() + + recipeFields := recipe.FieldDescriptions() + recipeValues, err := recipe.Values() + if err != nil { + c.IndentedJSON(500, gin.H{"err": err.Error()}) + return + } + + for i, recipeField := range recipeFields { + returnData[recipeField.Name] = recipeValues[i] + } + + if isMore { + fmt.Println("there is more than one items with this id: ", recipe_id) + } + + recipe.Close() + + // + // Adding ingredients + // + + ingredients, err := Dbx.Query(context.Background(), + "select a.ingredient_id, a.quantity, a.unit, b.name from recipe_ingredients a join "+ + "ingredients b on a.recipe_id = $1 and "+ + "a.ingredient_id = b.ingredient_id", recipe_id) + if err != nil { + c.IndentedJSON(500, gin.H{"err": err.Error()}) + return + } + + ingredientsSlice := []map[string]interface{}{} + + for ingredients.Next() { + + ingredientsFields := ingredients.FieldDescriptions() + ingredientsValues, err := ingredients.Values() + if err != nil { + c.IndentedJSON(500, gin.H{"err": err.Error()}) + return + } + + tempMap := map[string]interface{}{} + + for i, ingredient := range ingredientsFields { + tempMap[ingredient.Name] = ingredientsValues[i] + } + + ingredientsSlice = append(ingredientsSlice, tempMap) + + } + + returnData["proportions"] = ingredientsSlice + + ingredients.Close() + // + // End of adding ingredients + // + + // + // Start adding steps + // + + stepSlice := []map[string]interface{}{} + + steps, err := Dbx.Query(context.Background(), "select * from instructions where recipe_id = $1", recipe_id) + if err != nil { + c.IndentedJSON(500, gin.H{"err": err.Error()}) + return + } + + for steps.Next() { + tempStep := map[string]interface{}{} + + stepFields := steps.FieldDescriptions() + stepValues, err := steps.Values() + if err != nil { + c.IndentedJSON(500, gin.H{"err": err.Error()}) + return + } + + for i, step := range stepFields { + tempStep[step.Name] = stepValues[i] + } + + stepSlice = append(stepSlice, tempStep) + } + + returnData["steps"] = stepSlice + + steps.Close() + + // + // End adding steps + // + + c.IndentedJSON(200, returnData) +} + +// Register new user in the system and return JWT-token if +// all went well +func registerUser(c *gin.Context) { + type content struct { + Password string + Name string + Username string + } + + if c.ContentType() != "application/json" { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "content type should by 'application/json'"}) + return + } + + var body = content{} + + if err := c.BindJSON(&body); err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + newUuid := uuid.New().ID() + + hashedPassword, err := Argon2Hasher.HashGen([]byte(body.Password), []byte{}) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + rowsToClose, errr := Dbx.Query(context.Background(), "INSERT INTO users "+ + "(user_id, username, user_name, user_password) "+ + "VALUES($1, $2, $3, $4);", newUuid, body.Username, body.Name, hashedPassword.stringToStore) + + if errr != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": errr.Error()}) + return + } + + rowsToClose.Close() + + newToken, err := CreateToken(map[string]interface{}{"id": newUuid, "name": body.Name}) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.Header("Authorization", newToken) + c.IndentedJSON(200, gin.H{"newToken": newToken}) +} + +// Returns all available diets and ids of them +func getAllDiets(c *gin.Context) { + diets, err := Dbx.Query(context.Background(), "select * from diets") + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + sliceOfDiets := []map[string]interface{}{} + + for diets.Next() { + + temp := map[string]interface{}{} + + dietsRows := diets.FieldDescriptions() + dietsValues, err := diets.Values() + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + for i, value := range dietsRows { + temp[value.Name] = dietsValues[i] + } + + sliceOfDiets = append(sliceOfDiets, temp) + + } + + c.IndentedJSON(http.StatusOK, sliceOfDiets) +} + +// Returns slice of cuisines and they ids +func getAllCuisines(c *gin.Context) { + cuisines, err := Dbx.Query(context.Background(), "select * from cuisines") + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + cuisinesSlice := []map[string]interface{}{} + + for cuisines.Next() { + + cuisinesFields := cuisines.FieldDescriptions() + cuisinesValues, err := cuisines.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + temp := map[string]interface{}{} + + for i, cuisine := range cuisinesFields { + temp[cuisine.Name] = cuisinesValues[i] + } + + cuisinesSlice = append(cuisinesSlice, temp) + + } + + c.IndentedJSON(http.StatusOK, cuisinesSlice) + +} + +// Returns all ingredients with they ids +func getAllIngredients(c *gin.Context) { + cuisines, err := Dbx.Query(context.Background(), "select * from ingredients") + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + ingredientsSlice := []map[string]interface{}{} + + for cuisines.Next() { + + ingredientsFields := cuisines.FieldDescriptions() + ingredientsValues, err := cuisines.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + temp := map[string]interface{}{} + + for i, cuisine := range ingredientsFields { + temp[cuisine.Name] = ingredientsValues[i] + } + + ingredientsSlice = append(ingredientsSlice, temp) + + } + + c.IndentedJSON(http.StatusOK, ingredientsSlice) + +} + +func getRecipesByIngredient(c *gin.Context) { + ingredientId, err := strconv.Atoi(strings.Trim(c.Param("ingredientId"), "/")) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipes, err := Dbx.Query(context.Background(), + "select a.recipe_id, a.name, a.description, a.servings, a.prep_time, a.cook_time, a.image_url, a.source_url, a.author from recipes a join (select * from ingredients "+ + " b join recipe_ingredients c on b.ingredient_id = $1 where b.ingredient_id = c.ingredient_id) d "+ + " on a.recipe_id = d.recipe_id", + ingredientId) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipesSlice := []map[string]interface{}{} + + for recipes.Next() { + temp := map[string]interface{}{} + + recipeFields := recipes.FieldDescriptions() + recipeValues, err := recipes.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + for i, field := range recipeFields { + temp[field.Name] = recipeValues[i] + } + + recipesSlice = append(recipesSlice, temp) + + } + + c.IndentedJSON(http.StatusOK, recipesSlice) +} + +func getRecipesByCuisine(c *gin.Context) { + cuisineId, err := strconv.Atoi(strings.Trim(c.Param("cuisineId"), "/")) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipes, err := Dbx.Query(context.Background(), + "select a.recipe_id, a.name, a.description, a.servings, a.prep_time, a.cook_time, a.image_url, a.source_url, a.author from recipes a join (select * from cuisines "+ + " b join recipe_cuisines c on b.cuisine_id = $1 where b.cuisine_id = c.cuisine_id) d "+ + " on a.recipe_id = d.recipe_id", + cuisineId) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + cuisinesSlice := []map[string]interface{}{} + + for recipes.Next() { + temp := map[string]interface{}{} + + recipeFields := recipes.FieldDescriptions() + recipeValues, err := recipes.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + for i, field := range recipeFields { + temp[field.Name] = recipeValues[i] + } + + cuisinesSlice = append(cuisinesSlice, temp) + + } + + c.IndentedJSON(http.StatusOK, cuisinesSlice) +} + +func conditionalRollback(b *bool) { + if *b { + _, err := Dbx.Exec(context.Background(), + "ROLLBACK;") + if err != nil { + fmt.Println("Failed to rollback. Data in database could be invalid.") + return + } + fmt.Println(RedColor + strings.ToUpper("Some things went wrong. Rollback.") + ResetColor) + } +} + +func addRecipe(c *gin.Context) { + type ingredient struct { + IngredientId uint32 + Name string + Quantity float64 + Unit string + } + + type instruction struct { + InstructionId uint32 + Step_number uint16 + InstructionText string + InstructionTime string + } + + type cuisine struct { + CuisineId uint32 + Name string + } + + type diet struct { + DietId uint32 + Name string + } + + type content struct { + + // Recipe information we receive from client + Name string + Description string + Servings uint8 + Prep_time uint16 + Cook_time uint16 + Image_url string + Source_url string + + // Ingredient list + IngredientList []ingredient // Done + + // Instruction list + InstructionList []instruction // Done + + // Other information + Cuisines []cuisine // Client side + Diets []diet // Client side + } + + usrID, idExists := c.Get("id") + if !idExists { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in token"}) + return + } + + // Id of user that created recipe + userId, err := strconv.Atoi(usrID.(string)) + if err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in body of the request"}) + return + } + + var body content + + err = c.BindJSON(&body) + if err != nil { + fmt.Println(err.Error()) + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in body of the request " + err.Error()}) + return + } + + recipeUuid := uuid.New() + + // New recipe uuid + var recipeUuidInt = recipeUuid.ID() + + // Set author ID as user ID request came from + + // Set new uuid for recipe + + // Ingredient to be added to the db + newIngredients := []ingredient{} + + needToRollback := false + _, err = Dbx.Exec(context.Background(), + "BEGIN;") + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "failed to begin transaction"}) + return + } + defer conditionalRollback(&needToRollback) + + // Check if ingredient already in database + // if not we give it a uuid and add to slice to add it to db later + // Also we change name of ingredient to lower case + for i, ingredient := range body.IngredientList { + temp, err := Dbx.Query(context.Background(), + "select find_id_in_the_table_by_name_ingredient($1)", + strings.ToLower(ingredient.Name)) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + needToRollback = true + return + } + + temp.Next() + if t, err := temp.Values(); err == nil && t[0].(int64) < 0 { + + uid := uuid.New() + body.IngredientList[i].IngredientId = uid.ID() + body.IngredientList[i].Name = strings.ToLower(body.IngredientList[i].Name) + newIngredients = append(newIngredients, body.IngredientList[i]) + + } else if err == nil && t[0].(int64) > 0 { + + ttemp := t[0].(int64) + body.IngredientList[i].IngredientId = uint32(ttemp) + body.IngredientList[i].Name = strings.ToLower(body.IngredientList[i].Name) + + } else if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error in loop": err.Error()}) + needToRollback = true + return + } + temp.Close() + } + + // Give every instruction uuid + for i := range body.InstructionList { + body.InstructionList[i].InstructionId = uuid.New().ID() + } + + // Adding new ingredients to db + for _, newIngredient := range newIngredients { + _, err := Dbx.Exec(context.Background(), + "INSERT INTO ingredients "+ + `(ingredient_id, "name") `+ + "VALUES($1, $2);", + newIngredient.IngredientId, newIngredient.Name) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + needToRollback = true + return + } + + } + + // Adding recipe with it information + _, err = Dbx.Exec(context.Background(), + "INSERT INTO recipes "+ + `(recipe_id, "name", description, servings, prep_time, cook_time, image_url, source_url, author) `+ + "VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9);", + recipeUuidInt, body.Name, body.Description, body.Servings, body.Prep_time, body.Cook_time, body.Image_url, body.Source_url, userId) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error adding recipe": err.Error() + fmt.Sprint(userId)}) + needToRollback = true + return + } + + var counter = 0 + // Adding recipe steps to db + for _, instruction := range body.InstructionList { + counter++ + _, err := Dbx.Exec(context.Background(), + "INSERT INTO instructions "+ + "(instruction_id, recipe_id, step_number, instruction_text, instruction_time) "+ + "VALUES($1, $2, $3, $4, $5);", + instruction.InstructionId, recipeUuidInt, instruction.Step_number, instruction.InstructionText, instruction.InstructionTime) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error adding recipe step": err.Error() + fmt.Sprint(counter)}) + needToRollback = true + return + } + } + + // Link this recipe with stuff + + // Link recipe with ingredients + for _, ingredient := range body.IngredientList { + _, err := Dbx.Exec(context.Background(), + "INSERT INTO recipe_ingredients "+ + "(recipe_id, ingredient_id, quantity, unit) "+ + "VALUES($1, $2, $3, $4);", + recipeUuidInt, ingredient.IngredientId, ingredient.Quantity, ingredient.Unit) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error() + fmt.Sprint(recipeUuidInt, ingredient.IngredientId)}) + needToRollback = true + return + } + } + + // Link with cuisines + for _, cuisine := range body.Cuisines { + _, err := Dbx.Exec(context.Background(), + "INSERT INTO recipe_cuisines "+ + "(recipe_id, cuisine_id) "+ + "VALUES($1, $2);", + recipeUuidInt, cuisine.CuisineId) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + needToRollback = true + return + } + } + + // Link with diets + for _, diet := range body.Diets { + _, err := Dbx.Exec(context.Background(), + "INSERT INTO recipe_diets "+ + "(recipe_id, diet_id) "+ + "VALUES($1, $2);", + recipeUuidInt, diet.DietId) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + needToRollback = true + return + } + } + // + //Todo: Add transactions, so bad could not pass to db + + _, err = Dbx.Exec(context.Background(), + "COMMIT;") + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Failed to COMMIT " + err.Error()}) + needToRollback = true + return + } + + c.IndentedJSON(http.StatusOK, gin.H{"message": "all good"}) +} + +func findRecipesByIngredients(c *gin.Context) { + type content struct { + Ingredients []string + } + + if c.ContentType() != "application/json" { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "content_type is not 'application/json'"}) + return + } + + var body content + + if err := c.BindJSON(&body); err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error 1": `Invalid body of the request`}) + return + } + + recipe_ids, err := Dbx.Query(context.Background(), + `select recipe_id from + recipe_ingredients ri + join ingredients i on i.ingredient_id = ri.ingredient_id + where i.name in ('`+strings.Join(body.Ingredients[:], "','")+`') + group by recipe_id + having count(distinct name) = `+strconv.Itoa(len(body.Ingredients))) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error 2": err.Error()}) + return + } + + ids := []uint32{} + + for recipe_ids.Next() { + values, err := recipe_ids.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error 3": err.Error()}) + return + } + + ids = append(ids, uint32(values[0].(int64))) + } + + c.IndentedJSON(http.StatusOK, gin.H{"ids": ids}) +} + +func getRandomRecipes(c *gin.Context) { + recipes, err := Dbx.Query(context.Background(), + `select * from recipes`) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipeNumber := 15 + recipesToReturn := make([]map[string]interface{}, recipeNumber) + + recipeCounter := 0 + r := rand.New(rand.NewSource(time.Now().Unix())) + for recipes.Next() { + b := r.Intn(10) + + if b >= 9 { + temp := map[string]interface{}{} + + fields := recipes.FieldDescriptions() + val, err := recipes.Values() + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + for i, field := range fields { + temp[field.Name] = val[i] + } + recipesToReturn[recipeCounter] = temp + + recipeCounter++ + + if recipeCounter >= recipeNumber { + break + } + } + + } + + recipes.Close() + + c.IndentedJSON(http.StatusOK, recipesToReturn) +} + +func isAuth(c *gin.Context) { + _, err := ValidateToken(c.GetHeader("Authorization")) + + if err != nil { + c.IndentedJSON(http.StatusUnauthorized, gin.H{"message": "invalid token"}) + return + } + + c.IndentedJSON(http.StatusOK, gin.H{"message": "valid token"}) +} + +func addFavoriteRecipe(c *gin.Context) { + + usrID, exists := c.Get("id") + if !exists { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in token"}) + return + } + + recipeId, err := strconv.Atoi(strings.Trim(c.Param("recipeId"), "/")) + if err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing recipe id"}) + return + } + + _, err = Dbx.Exec(context.Background(), `INSERT INTO user_favorite_recipe + (user_id, recipe_id) + VALUES($1, $2);`, usrID, recipeId) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error/inserting": err.Error()}) + return + } + + c.IndentedJSON(http.StatusOK, gin.H{"message": "all good"}) + +} + +func removeFavoriteRecipe(c *gin.Context) { + + usrID, exists := c.Get("id") + if !exists { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in token"}) + return + } + + recipeId, err := strconv.Atoi(strings.Trim(c.Param("recipeId"), "/")) + if err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing recipe id"}) + return + } + + _, err = Dbx.Exec(context.Background(), `DELETE from user_favorite_recipe where + user_id = $1 and recipe_id = $2`, usrID, recipeId) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error/inserting": err.Error()}) + return + } + + c.IndentedJSON(http.StatusOK, gin.H{"message": "all good"}) + +} + +func getAllFavoriteRecipes(c *gin.Context) { + usrID, exists := c.Get("id") + if !exists { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in token"}) + return + } + + recipeIdsRows, err := Dbx.Query(context.Background(), `select * from recipes r + join user_favorite_recipe ufr on r.recipe_id = ufr.recipe_id + where user_id = $1`, usrID) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error/selecting": err.Error()}) + return + } + + recipeIds := make([]any, 0) + for recipeIdsRows.Next() { + fieldNames := recipeIdsRows.FieldDescriptions() + temp, err := recipeIdsRows.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error/selecting": err.Error()}) + return + } + tempMap := map[string]any{} + for i, fieldName := range fieldNames { + tempMap[fieldName.Name] = temp[i] + } + recipeIds = append(recipeIds, tempMap) + } + + c.IndentedJSON(http.StatusOK, gin.H{"values": recipeIds}) +} + +func isFavorite(c *gin.Context) { + usrID, exists := c.Get("id") + if !exists { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "missing required data in token"}) + return + } + + recipeId, err := strconv.Atoi(strings.Trim(c.Param("recipeId"), "/")) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + rows, err := Dbx.Query(context.Background(), `SELECT user_id, recipe_id + FROM user_favorite_recipe where user_id = $1 and recipe_id = $2`, usrID, recipeId) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error/selecting": err.Error()}) + return + } + + flag := false + for rows.Next() { + flag = true + } + + c.IndentedJSON(http.StatusOK, flag) +} + +func getRecipesByName(c *gin.Context) { + recipeName := strings.Trim(c.Param("recipeName"), "/") + + rows, err := Dbx.Query(context.Background(), `SELECT * + FROM recipes where "name" LIKE $1;`, fmt.Sprint(`%`, recipeName, `%`)) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error in select (getRecipesByName)": err.Error()}) + return + } + + recipesToReturn := []map[string]any{} + + for rows.Next() { + fields := rows.FieldDescriptions() + values, err := rows.Values() + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipeInfo := map[string]any{} + for i, field := range fields { + recipeInfo[field.Name] = values[i] + } + + recipesToReturn = append(recipesToReturn, recipeInfo) + } + + c.IndentedJSON(http.StatusOK, recipesToReturn) +} + +func deleteRecipe(c *gin.Context) { + fmt.Println(c.Param("recipe_id")) + recipe_id, err := strconv.Atoi(strings.Trim(c.Param("recipe_id"), "/")) + if err != nil { + c.IndentedJSON(404, gin.H{"err": err.Error()}) + return + } + + userID, exists := c.Get("id") + if !exists { + c.IndentedJSON(404, gin.H{"err": "no valid token provided"}) + return + } + + var count int + + Dbx.QueryRow(context.Background(), `select count(*) from recipes where author = $1 and recipe_id = $2`, userID, recipe_id).Scan(&count) + + if count > 0 { + _, err := Dbx.Exec(context.Background(), `DELETE FROM recipes + WHERE recipe_id = $1`, recipe_id) + + if err != nil { + c.IndentedJSON(404, gin.H{"err": err.Error()}) + return + } + } else { + c.IndentedJSON(http.StatusUnauthorized, gin.H{"message": "Could not delete recipe. ID: " + strconv.Itoa(recipe_id)}) + return + } + + c.IndentedJSON(http.StatusOK, gin.H{"message": "Successfully deleted recipe. ID: " + strconv.Itoa(recipe_id)}) +} + +func getAllUserRecipes(c *gin.Context) { + userID, exists := c.Get("id") + if !exists { + c.IndentedJSON(404, gin.H{"err": "no valid token provided"}) + return + } + + rows, err := Dbx.Query(context.Background(), `select * from recipes where author = $1`, userID) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipesToReturn := []map[string]any{} + + for rows.Next() { + fields := rows.FieldDescriptions() + values, err := rows.Values() + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + recipeInfo := map[string]any{} + for i, field := range fields { + recipeInfo[field.Name] = values[i] + } + + recipesToReturn = append(recipesToReturn, recipeInfo) + } + + c.IndentedJSON(http.StatusOK, recipesToReturn) + +} + +func SetupRotes(g *gin.Engine) error { + g.Use(CORSMiddleware()) + + freeGroup := g.Group("/") + + // Free group routes + freeGroup.GET("recipe_info/:recipe_id", getRecipeInformation) + freeGroup.GET("get_all_diets", getAllDiets) + freeGroup.GET("get_all_cuisines", getAllCuisines) + freeGroup.GET("get_all_ingredients", getAllIngredients) + freeGroup.GET("get_recipes_by_ingredient/:ingredientId", getRecipesByIngredient) + freeGroup.GET("get_recipes_by_cuisine/:cuisineId", getRecipesByCuisine) + freeGroup.GET("get_recipes_by_name/:recipeName", getRecipesByName) + freeGroup.GET("getRandomRecipes", getRandomRecipes) + freeGroup.GET("isAuth", isAuth) + freeGroup.POST("register_user", registerUser) + freeGroup.POST("login", login) + freeGroup.POST("get_recipes_by_ingredients", findRecipesByIngredients) + + authGroup := g.Group("/") + authGroup.Use(AuthMiddleware()) + + // Auth group routes + authGroup.GET("test1", testRoute1) + authGroup.GET("addToFavorite/:recipeId", addFavoriteRecipe) + authGroup.GET("removeFromFavorite/:recipeId", removeFavoriteRecipe) + authGroup.GET("getAllFavorite", getAllFavoriteRecipes) + authGroup.GET("isFavorite/:recipeId", isFavorite) + authGroup.GET("getAllUserRecipes", getAllUserRecipes) + authGroup.POST("test2", testRoute2) + authGroup.POST("add_recipe", addRecipe) + authGroup.DELETE("deleteRecipe/:recipe_id", deleteRecipe) + + return nil +} + +func login(c *gin.Context) { + type content struct { + Nickname string + Password string + } + + var body content + + err := c.BindJSON(&body) + if err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error 1st": err.Error()}) + return + } + + hash, err := Dbx.Query(context.Background(), + "select * from users where username = $1;", + body.Nickname) + + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error 2nd": err.Error()}) + return + } + + hash.Next() + values, err := hash.Values() + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + user_id := strconv.FormatInt(values[0].(int64), 10) + user_name := values[2].(string) + user_hash := values[3].(string) + hash.Close() + + user_hash_, salt, err := DecodeArgon2String(user_hash) + if err != nil { + c.IndentedJSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + err = Argon2Hasher.Compare(user_hash_, salt, []byte(body.Password)) + if err != nil { + c.IndentedJSON(http.StatusUnauthorized, gin.H{"error": err.Error()}) + return + } + + user_info := map[string]interface{}{ + "id": user_id, + "name": user_name, + } + + token, err := CreateToken(user_info) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.Header("Authorization", token) + c.IndentedJSON(http.StatusOK, gin.H{"token": token}) + +} + +func CORSMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, authorization, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") + c.Writer.Header().Set("Access-Control-Expose-Headers", "Access-Token, Uid, Authorization") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} + +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + + token := c.GetHeader("Authorization") + + claims, err := ValidateToken(token) + if err != nil { + c.IndentedJSON(http.StatusUnauthorized, gin.H{"error auth": err.Error()}) + c.Abort() + return + } + + // Claims -> data stored in token + c.Set("id", claims["id"]) + c.Set("claims", claims) + c.Next() + + } +}