diff --git a/.gitignore b/.gitignore index 50d43ce03..51890db3c 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ vendor/ # IDEs directories .idea .vscode +.env \ No newline at end of file diff --git a/cmd/accrual/accrual_windows_amd64 b/cmd/accrual/accrual_windows_amd64 deleted file mode 100755 index 7246a7547..000000000 Binary files a/cmd/accrual/accrual_windows_amd64 and /dev/null differ diff --git a/cmd/gophermart/main.go b/cmd/gophermart/main.go index 38dd16da6..11e788c05 100644 --- a/cmd/gophermart/main.go +++ b/cmd/gophermart/main.go @@ -1,3 +1,9 @@ package main -func main() {} +import ( + "github.com/botaevg/gophermart/internal/app" +) + +func main() { + app.StartApp() +} diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..3d07ad98d --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module github.com/botaevg/gophermart + +go 1.18 + +require ( + github.com/caarlos0/env/v6 v6.9.3 // indirect + github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 // indirect + github.com/go-chi/chi/v5 v5.0.7 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.12.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.11.0 // indirect + github.com/jackc/pgx/v4 v4.16.1 // indirect + github.com/jackc/puddle v1.2.1 // indirect + golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect + golang.org/x/text v0.3.7 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 000000000..8142f1577 --- /dev/null +++ b/go.sum @@ -0,0 +1,176 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/caarlos0/env/v6 v6.9.3 h1:Tyg69hoVXDnpO5Qvpsu8EoquarbPyQb+YwExWHP8wWU= +github.com/caarlos0/env/v6 v6.9.3/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 h1:CaO/zOnF8VvUfEbhRatPcwKVWamvbYd8tQGRWacE9kU= +github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= +github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.12.1 h1:rsDFzIpRk7xT4B8FufgpCCeyjdNpKyghZeSefViE5W8= +github.com/jackc/pgconn v1.12.1/go.mod h1:ZkhRC59Llhrq3oSfrikvwQ5NaxYExr6twkdkMLaKono= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +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/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.0 h1:brH0pCGBDkBW07HWlN/oSBXrmo3WB0UvZd1pIuDcL8Y= +github.com/jackc/pgproto3/v2 v2.3.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.11.0 h1:u4uiGPz/1hryuXzyaBhSk6dnIyyG2683olG2OV+UUgs= +github.com/jackc/pgtype v1.11.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.16.1 h1:JzTglcal01DrghUqt+PmzWsZx/Yh7SC/CTQmSBMTd0Y= +github.com/jackc/pgx/v4 v4.16.1/go.mod h1:SIhx0D5hoADaiXZVyv+3gSm3LCIIINTVO0PficsvWGQ= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/internal/ExternalService/clientRequest.go b/internal/ExternalService/clientRequest.go new file mode 100644 index 000000000..09812a11d --- /dev/null +++ b/internal/ExternalService/clientRequest.go @@ -0,0 +1,73 @@ +package externalservice + +import ( + "encoding/json" + "fmt" + "github.com/botaevg/gophermart/internal/models" + "github.com/botaevg/gophermart/internal/service" + "io" + "log" + "net/http" +) + +type ExternalService struct { + gophermart service.Gophermart + asyncExecution chan string + accrualSystemAddress string +} + +func NewES(accrualSystemAddress string, gophermart service.Gophermart, asyncExecution chan string) ExternalService { + return ExternalService{ + accrualSystemAddress: accrualSystemAddress, + gophermart: gophermart, + asyncExecution: asyncExecution, + } +} + +func (e ExternalService) AccrualPoints(orderID string) { + client := http.Client{} + + URL := fmt.Sprintf("%s/api/orders/%s", e.accrualSystemAddress, orderID) + log.Print(URL) + req, err := http.NewRequest("GET", URL, nil) + if err != nil { + log.Print(err) + return + } + + resp, err := client.Do(req) + if err != nil { + log.Print(err) + return + } + log.Print(resp.Status) + if resp.Status == "200 OK" { + respBody, err := io.ReadAll(resp.Body) + if err != nil { + log.Print(err) + return + } + defer resp.Body.Close() + + var Order models.OrderES + err = json.Unmarshal(respBody, &Order) + if err != nil { + log.Print(err) + } + log.Print(Order) + + e.gophermart.UpdateOrders(Order) + + if Order.Status == "PROCESSED" { + e.gophermart.AccrualRequest(Order) + } + + if Order.Status == "REGISTERED" || Order.Status == "PROCESSING" { + e.asyncExecution <- orderID + return + } + + } else { + e.asyncExecution <- orderID + } +} diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 000000000..bcf093143 --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,62 @@ +package app + +import ( + "github.com/botaevg/gophermart/internal/ExternalService" + "github.com/botaevg/gophermart/internal/apperror" + "github.com/botaevg/gophermart/internal/config" + "github.com/botaevg/gophermart/internal/handlers" + "github.com/botaevg/gophermart/internal/repositories" + "github.com/botaevg/gophermart/internal/service" + "github.com/botaevg/gophermart/pkg/postgre" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "log" + "net/http" +) + +func StartApp() { + cfg, err := config.GetConfig() + if err != nil { + log.Print("config error") + return + } + dbpool, err := postgre.NewClient(cfg.DataBaseDsn) + if err != nil { + log.Print("DB connect error") + return + } + + storage := repositories.NewDB(dbpool) + + auth := service.NewAuth(storage, cfg.SecretKey) + log.Print(cfg.SecretKey) + gophermart := service.NewGophermart(storage) + r := chi.NewRouter() + + asyncChannel := make(chan string) + externalService := externalservice.NewES(cfg.AccrualSystemAddress, gophermart, asyncChannel) + h := handlers.NewHandler(cfg, auth, gophermart, asyncChannel) + go func() { + for job := range asyncChannel { + log.Print("async go") + externalService.AccrualPoints(job) + } + }() + + authcookie := apperror.NewAuthMiddleware(cfg.SecretKey) + r.Use(authcookie.AuthCookie) + + r.Use(middleware.Logger) + + r.Post("/api/user/register", h.RegisterUser) + r.Post("/api/user/login", h.Login) + + r.Post("/api/user/orders", h.LoadOrder) + r.Get("/api/user/orders", h.GetListOrders) + + r.Get("/api/user/balance", h.BalanceUser) + r.Post("/api/user/balance/withdraw", h.WithdrawRequest) + r.Get("/api/user/withdrawals", h.ListWithdraw) + + log.Fatal(http.ListenAndServe(cfg.RunAddress, r)) +} diff --git a/internal/apperror/middleware.go b/internal/apperror/middleware.go new file mode 100644 index 000000000..f2410d4b0 --- /dev/null +++ b/internal/apperror/middleware.go @@ -0,0 +1,59 @@ +package apperror + +import ( + "context" + "errors" + "github.com/botaevg/gophermart/internal/service" + "github.com/dgrijalva/jwt-go/v4" + "log" + "net/http" +) + +type AuthMiddleware struct { + secretkey string +} + +func NewAuthMiddleware(key string) *AuthMiddleware { + return &AuthMiddleware{ + secretkey: key, + } +} + +type UserID string + +func (a AuthMiddleware) AuthCookie(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + openURL := []string{"/api/user/register", "/api/user/login"} + path := r.URL.Path + for _, v := range openURL { + if v == path { + next.ServeHTTP(w, r) + return + } + } + ctoken, err := r.Cookie("Bearer") + if err != nil { + http.Error(w, errors.New("unauthorized").Error(), http.StatusUnauthorized) + return + } + + tokenClaims := &service.Claims{} + token, err := jwt.ParseWithClaims(ctoken.Value, tokenClaims, func(token *jwt.Token) (interface{}, error) { + return []byte(a.secretkey), nil + }) + if err != nil { + http.Error(w, errors.New("token error").Error(), http.StatusUnauthorized) + return + } + if !token.Valid { + http.Error(w, errors.New("token disabled").Error(), http.StatusUnauthorized) + return + } + log.Print("userID middle") + log.Print(tokenClaims.UserID) + ctx := context.WithValue(r.Context(), UserID("username"), tokenClaims.UserID) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + + }) +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 000000000..d235750ec --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,30 @@ +package config + +import ( + "flag" + "github.com/caarlos0/env/v6" +) + +type Config struct { + RunAddress string `env:"RUN_ADDRESS"` + DataBaseDsn string `env:"DATABASE_URI"` + AccrualSystemAddress string `env:"ACCRUAL_SYSTEM_ADDRESS"` + SecretKey string `env:"SECRETKEY"` +} + +func GetConfig() (Config, error) { + cfg := Config{} + + flag.StringVar(&cfg.RunAddress, "a", "", "port start listen") + flag.StringVar(&cfg.DataBaseDsn, "d", "", "database dsn") + flag.StringVar(&cfg.AccrualSystemAddress, "r", "", "AccrualSystemAddress") + flag.StringVar(&cfg.SecretKey, "s", "", "salt") + flag.Parse() + //postgresql://postgres:sqllife@localhost:5434/gophermart + //:8080 :8080 + if err := env.Parse(&cfg); err != nil { + return Config{}, err + } + + return cfg, nil +} diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go new file mode 100644 index 000000000..9022a5fde --- /dev/null +++ b/internal/handlers/handlers.go @@ -0,0 +1,248 @@ +package handlers + +import ( + "encoding/json" + "errors" + "github.com/botaevg/gophermart/internal/apperror" + "github.com/botaevg/gophermart/internal/config" + "github.com/botaevg/gophermart/internal/models" + "github.com/botaevg/gophermart/internal/service" + "io" + "log" + "net/http" + "strconv" +) + +type handler struct { + config config.Config + auth service.Auth + gophermart service.Gophermart + asyncExecution chan string +} + +func NewHandler(config config.Config, auth service.Auth, gophermart service.Gophermart, asyncExecution chan string) *handler { + return &handler{ + config: config, + auth: auth, + gophermart: gophermart, + asyncExecution: asyncExecution, + } +} + +func (h *handler) RegisterUser(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer r.Body.Close() + + var UserAPI models.UserAPI + if err := json.Unmarshal(b, &UserAPI); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + token, err := h.auth.RegisterUser(UserAPI, "") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + http.SetCookie(w, &http.Cookie{ + Name: "Bearer", + Value: token, + }) + w.WriteHeader(http.StatusOK) + w.Write([]byte("JWT " + token)) +} + +func (h *handler) Login(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer r.Body.Close() + + var UserAPI models.UserAPI + if err := json.Unmarshal(b, &UserAPI); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + token, err := h.auth.AuthUser(UserAPI, "") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + http.SetCookie(w, &http.Cookie{ + Name: "Bearer", + Value: token, + }) + w.WriteHeader(http.StatusOK) + w.Write([]byte("JWT " + token)) +} + +func (h *handler) LoadOrder(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value(apperror.UserID("username")).(uint) + b, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + n, err := strconv.Atoi(string(b)) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + if !ValidLuna(n) { + http.Error(w, errors.New("no valid orders").Error(), http.StatusUnprocessableEntity) + return + } + + OrderUserID, err := h.gophermart.CheckOrder(string(b)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if OrderUserID == userID { + w.WriteHeader(http.StatusOK) + w.Write([]byte("already load")) + } else if OrderUserID != 0 { + w.WriteHeader(http.StatusConflict) + w.Write([]byte("already load another user")) + } + + err = h.gophermart.AddOrder(string(b), userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + h.asyncExecution <- string(b) + + w.WriteHeader(http.StatusAccepted) + w.Write([]byte("accept new order")) +} + +func (h *handler) GetListOrders(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value(apperror.UserID("username")).(uint) + + ListOrdersAPI, err := h.gophermart.GetListOrders(userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(ListOrdersAPI) == 0 { + w.WriteHeader(http.StatusNoContent) + w.Write([]byte("no content")) + } + b, err := json.Marshal(&ListOrdersAPI) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + log.Print(string(b)) + w.Write(b) + +} + +func (h *handler) BalanceUser(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value(apperror.UserID("username")).(uint) + + balance, err := h.gophermart.BalanceUser(userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + b, err := json.Marshal(&balance) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + log.Print(string(b)) + w.Write(b) +} + +func (h *handler) WithdrawRequest(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value(apperror.UserID("username")).(uint) + + b, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer r.Body.Close() + + var withdrawnreq models.Withdraw + + if err := json.Unmarshal(b, &withdrawnreq); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + var accept bool + accept, err = h.gophermart.WithdrawRequest(withdrawnreq, userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + if !accept { + http.Error(w, errors.New("low balance").Error(), http.StatusPaymentRequired) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("withdrawn accept")) + +} + +func (h *handler) ListWithdraw(w http.ResponseWriter, r *http.Request) { + log.Print("withdrawals") + userID := r.Context().Value(apperror.UserID("username")).(uint) + + list, err := h.gophermart.ListWithdraw(userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + if len(list) == 0 { + w.WriteHeader(http.StatusNoContent) + w.Write([]byte("no withdraw")) + } + b, err := json.Marshal(&list) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + log.Print(string(b)) + w.Write(b) +} + +// Valid check number is valid or not based on Luhn algorithm +func ValidLuna(number int) bool { + return (number%10+checksum(number/10))%10 == 0 +} + +func checksum(number int) int { + var luhn int + + for i := 0; number > 0; i++ { + cur := number % 10 + + if i%2 == 0 { // even + cur = cur * 2 + if cur > 9 { + cur = cur%10 + cur/10 + } + } + + luhn += cur + number = number / 10 + } + return luhn % 10 +} diff --git a/internal/models/models.go b/internal/models/models.go new file mode 100644 index 000000000..aff0a625c --- /dev/null +++ b/internal/models/models.go @@ -0,0 +1,54 @@ +package models + +type User struct { + ID uint + Username string + Password string +} + +type UserAPI struct { + Username string `json:"login"` + Password string `json:"password"` +} + +type Order struct { + ID uint + Date string + OrderNumber string + UserID uint + Status string + Accrual float32 +} + +type AccountBalance struct { + Date string + UserID uint + OrderNumber string + TypeMove string + SumAccrual float32 + Balance float32 +} + +type AccountBalanceAPI struct { + Current float32 `json:"current"` + Withdrawn float32 `json:"withdrawn"` +} + +type OrderAPI struct { + Number string `json:"number"` + Status string `json:"status"` + Accrual float32 `json:"accrual"` + UploadedAt string `json:"uploaded_at"` +} + +type Withdraw struct { + Order string `json:"order"` + Sum float32 `json:"sum"` + ProcessedAt string `json:"processed_at"` +} + +type OrderES struct { + Order string `json:"order"` + Status string `json:"status"` + Accrual float32 `json:"accrual"` +} diff --git a/internal/repositories/postres.go b/internal/repositories/postres.go new file mode 100644 index 000000000..3f43206c7 --- /dev/null +++ b/internal/repositories/postres.go @@ -0,0 +1 @@ +package repositories diff --git a/internal/repositories/storage.go b/internal/repositories/storage.go new file mode 100644 index 000000000..892281be2 --- /dev/null +++ b/internal/repositories/storage.go @@ -0,0 +1,256 @@ +package repositories + +import ( + "context" + "github.com/botaevg/gophermart/internal/models" + "github.com/jackc/pgx/v4/pgxpool" + "log" + "time" +) + +type DBpgx struct { + Conn *pgxpool.Pool +} + +type Storage interface { + CreateUser(user models.User) (uint, error) + GetUser(user models.User) (uint, error) + CheckOrder(number string) (uint, error) + AddOrder(number string, userID uint) error + GetListOrders(userid uint) ([]models.Order, error) + BalanceUser(userid uint) (float32, error) + SumWithdrawn(userid uint) (float32, error) + ChangeBalance(change models.AccountBalance) error + ListWithdraw(userid uint) ([]models.Withdraw, error) + UpdateOrders(order models.OrderES) error + OwnerOrders(numberOrder string) (uint, error) +} + +func NewDB(pool *pgxpool.Pool) *DBpgx { + return &DBpgx{Conn: pool} +} + +func (d DBpgx) CreateUser(user models.User) (uint, error) { + q := `INSERT INTO users (username, password) VALUES ($1, $2) RETURNING id;` + + rows, err := d.Conn.Query(context.Background(), q, user.Username, user.Password) + if err != nil { + log.Print(err) + log.Print("user not created") + return 0, err + } + defer rows.Close() + + var id uint + if rows.Next() { + log.Print("rows next") + err := rows.Scan(&id) + if err != nil { + log.Print(err) + return 0, err + } + + } + log.Print(id) + return id, nil +} + +func (d DBpgx) GetUser(user models.User) (uint, error) { + q := `SELECT id FROM users WHERE username = $1 and password = $2;` + row, err := d.Conn.Query(context.Background(), q, user.Username, user.Password) + if err != nil { + log.Print(err) + return 0, err + } + defer row.Close() + + var id uint + if row.Next() { + err := row.Scan(&id) + if err != nil { + log.Print(err) + return 0, err + } + log.Print("user found") + return id, nil + } + log.Print("user no found") + return 0, nil +} + +func (d DBpgx) CheckOrder(number string) (uint, error) { + q := `select userid from orders where ordernumber = $1` + row, err := d.Conn.Query(context.Background(), q, number) + if err != nil { + log.Print(err) + return 0, err + } + defer row.Close() + + var id uint + if row.Next() { + err := row.Scan(&id) + if err != nil { + log.Print(err) + return 0, err + } + log.Print("user found") + return id, nil + } + log.Print("user no found") + return 0, nil +} + +func (d DBpgx) AddOrder(number string, userID uint) error { + q := `INSERT INTO orders (ordernumber, date, userid, status, accrual) VALUES ($1, $2, $3, $4, $5);` + _, err := d.Conn.Exec(context.Background(), q, number, time.Now().Format(time.RFC3339), userID, "NEW", 0) + if err != nil { + return err + } + return err +} + +func (d DBpgx) GetListOrders(userid uint) ([]models.Order, error) { + q := `select ordernumber, status, date, accrual from orders where userid=$1;` + rows, err := d.Conn.Query(context.Background(), q, userid) + if err != nil { + return []models.Order{}, err + } + defer rows.Close() + + var ListOrders []models.Order + for rows.Next() { + x := models.Order{} + err := rows.Scan(&x.OrderNumber, &x.Status, &x.Date, &x.Accrual) + if err != nil { + return []models.Order{}, err + } + ListOrders = append(ListOrders, x) + } + if rows.Err() != nil { + return []models.Order{}, err + } + return ListOrders, err +} + +func (d DBpgx) BalanceUser(userid uint) (float32, error) { + q := `select id, balance from accountbalance where userid=$1 order by id DESC LIMIT 1;` + rows, err := d.Conn.Query(context.Background(), q, userid) + if err != nil { + log.Print(err) + return 0, err + } + defer rows.Close() + + var balance float32 + if rows.Next() { + x := 0 + err := rows.Scan(&x, &balance) + if err != nil { + log.Print(err) + return 0, err + } + } + log.Print("storage BalanceUser") + log.Print(balance) + return balance, err +} + +func (d DBpgx) SumWithdrawn(userid uint) (float32, error) { + q := `select userid, sum(sumaccrual) from accountbalance where userid = $1 and typemove = $2 group by userid;` + rows, err := d.Conn.Query(context.Background(), q, userid, "withdraw") + if err != nil { + log.Print(err) + return 0, err + } + defer rows.Close() + + var withdrawn float32 + if rows.Next() { + x := 0 + err := rows.Scan(&x, &withdrawn) + if err != nil { + log.Print(err) + return 0, err + } + } + log.Print("storage SumWithdrawn") + log.Print(withdrawn) + + return withdrawn, err +} + +func (d DBpgx) ChangeBalance(change models.AccountBalance) error { + q := `INSERT INTO accountbalance (userid, ordernumber, typemove, sumaccrual, date, balance) + VALUES ($1, $2, $3, $4, $5 ,$6);` + + _, err := d.Conn.Exec(context.Background(), q, change.UserID, change.OrderNumber, change.TypeMove, change.SumAccrual, time.Now().Format(time.RFC3339), change.Balance) + if err != nil { + log.Print(err) + return err + } + return err +} + +func (d DBpgx) ListWithdraw(userid uint) ([]models.Withdraw, error) { + q := `select ordernumber, sumaccrual, date from accountbalance where typemove = 'withdraw' and userid = $1;` + + rows, err := d.Conn.Query(context.Background(), q, userid) + if err != nil { + log.Print(err) + return []models.Withdraw{}, err + } + defer rows.Close() + + var ListWithdraw []models.Withdraw + for rows.Next() { + x := models.Withdraw{} + err := rows.Scan(&x.Order, &x.Sum, &x.ProcessedAt) + if err != nil { + return []models.Withdraw{}, err + } + ListWithdraw = append(ListWithdraw, x) + } + if rows.Err() != nil { + return []models.Withdraw{}, err + } + return ListWithdraw, err +} + +func (d DBpgx) UpdateOrders(order models.OrderES) error { + q := ` + update orders + set status = $1, accrual = $2 + where ordernumber = $3;` + log.Print("storage UpdateOrders") + log.Print(order.Accrual) + _, err := d.Conn.Exec(context.Background(), q, order.Status, order.Accrual, order.Order) + + if err != nil { + log.Print("Запись не обновлена") + log.Print(err) + return err + } + return nil +} + +func (d DBpgx) OwnerOrders(numberOrder string) (uint, error) { + q := `select userid from orders where ordernumber = $1` + rows, err := d.Conn.Query(context.Background(), q, numberOrder) + if err != nil { + log.Print(err) + return 0, err + } + defer rows.Close() + + var userID uint + if rows.Next() { + err := rows.Scan(&userID) + if err != nil { + log.Print(err) + return 0, err + } + + } + return userID, err +} diff --git a/internal/service/auth.go b/internal/service/auth.go new file mode 100644 index 000000000..3b9f0e6c2 --- /dev/null +++ b/internal/service/auth.go @@ -0,0 +1,87 @@ +package service + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "github.com/botaevg/gophermart/internal/models" + "github.com/botaevg/gophermart/internal/repositories" + "github.com/dgrijalva/jwt-go/v4" + "log" + "time" +) + +type Auth struct { + storage repositories.Storage + secretkey string +} + +type Claims struct { + jwt.StandardClaims + UserID uint `json:"user_id"` +} + +func GenerateHashForPass(password string, salt string) string { + h := hmac.New(sha256.New, []byte(salt)) + h.Write([]byte(password)) + sum := h.Sum(nil) + return hex.EncodeToString(sum) +} + +func NewAuth(storage repositories.Storage, secretkey string) Auth { + return Auth{ + storage: storage, + secretkey: secretkey, + } +} + +func (a Auth) RegisterUser(userAPI models.UserAPI, salt string) (string, error) { + var User models.User + User.Username = userAPI.Username + User.Password = GenerateHashForPass(userAPI.Password, salt) + + ID, err := a.storage.CreateUser(User) + if err != nil { + return "", err + } + User.ID = ID + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, &Claims{ + UserID: User.ID, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: jwt.At(time.Now().Add(600 * time.Minute)), + IssuedAt: jwt.At(time.Now()), + }, + }) + tokenSigned, err := token.SignedString([]byte(a.secretkey)) + if err != nil { + log.Print(err) + return "", err + } + return tokenSigned, err +} + +func (a Auth) AuthUser(userAPI models.UserAPI, salt string) (string, error) { + var User models.User + User.Username = userAPI.Username + User.Password = GenerateHashForPass(userAPI.Password, salt) + ID, err := a.storage.GetUser(User) + if err != nil { + return "", err + } + + User.ID = ID + token := jwt.NewWithClaims(jwt.SigningMethodHS256, &Claims{ + UserID: User.ID, + StandardClaims: jwt.StandardClaims{ + ExpiresAt: jwt.At(time.Now().Add(600 * time.Minute)), + IssuedAt: jwt.At(time.Now()), + }, + }) + tokenSigned, err := token.SignedString([]byte(a.secretkey)) + if err != nil { + log.Print("tokenSigned") + return "", err + } + return tokenSigned, err +} diff --git a/internal/service/gophermart.go b/internal/service/gophermart.go new file mode 100644 index 000000000..7b15b4cd5 --- /dev/null +++ b/internal/service/gophermart.go @@ -0,0 +1,117 @@ +package service + +import ( + "github.com/botaevg/gophermart/internal/models" + "github.com/botaevg/gophermart/internal/repositories" + "log" +) + +type Gophermart struct { + storage repositories.Storage +} + +func NewGophermart(storage repositories.Storage) Gophermart { + return Gophermart{storage: storage} +} + +func (g Gophermart) CheckOrder(number string) (uint, error) { + return g.storage.CheckOrder(number) +} + +func (g Gophermart) AddOrder(number string, userID uint) error { + return g.storage.AddOrder(number, userID) +} + +func (g Gophermart) GetListOrders(userid uint) ([]models.OrderAPI, error) { + ListOrders, err := g.storage.GetListOrders(userid) + if err != nil { + return nil, err + } + var ListOrdersAPI []models.OrderAPI + for _, v := range ListOrders { + x := models.OrderAPI{} + x.Number = v.OrderNumber + x.Status = v.Status + x.Accrual = v.Accrual + x.UploadedAt = v.Date + ListOrdersAPI = append(ListOrdersAPI, x) + } + return ListOrdersAPI, err +} + +func (g Gophermart) BalanceUser(userID uint) (models.AccountBalanceAPI, error) { + current, err := g.storage.BalanceUser(userID) + if err != nil { + return models.AccountBalanceAPI{}, err + } + withdrawn, err := g.storage.SumWithdrawn(userID) + if err != nil { + return models.AccountBalanceAPI{}, err + } + return models.AccountBalanceAPI{ + Current: current, + Withdrawn: withdrawn, + }, err +} + +func (g Gophermart) WithdrawRequest(withdrawnreq models.Withdraw, userID uint) (bool, error) { + + balance, err := g.storage.BalanceUser(userID) + if err != nil { + log.Print(err) + return false, err + } + if withdrawnreq.Sum > balance { + log.Print("sum > balance") + return false, err + } + + err = g.storage.ChangeBalance(models.AccountBalance{ + UserID: userID, + OrderNumber: withdrawnreq.Order, + TypeMove: "withdraw", + SumAccrual: withdrawnreq.Sum, + Balance: balance - withdrawnreq.Sum, + }) + if err != nil { + log.Print(err) + return false, err + } + return true, nil +} + +func (g Gophermart) ListWithdraw(userid uint) ([]models.Withdraw, error) { + return g.storage.ListWithdraw(userid) +} + +func (g Gophermart) UpdateOrders(order models.OrderES) error { + log.Print("g UpdateOrders") + return g.storage.UpdateOrders(order) +} + +func (g Gophermart) AccrualRequest(order models.OrderES) error { + log.Print("g AccrualRequest") + userID, err := g.storage.OwnerOrders(order.Order) + if err != nil { + log.Print(err) + return err + } + balance, err := g.storage.BalanceUser(userID) + if err != nil { + log.Print(err) + return err + } + + err = g.storage.ChangeBalance(models.AccountBalance{ + UserID: userID, + OrderNumber: order.Order, + TypeMove: "accrual", + SumAccrual: order.Accrual, + Balance: balance + order.Accrual, + }) + if err != nil { + log.Print(err) + return err + } + return err +} diff --git a/pkg/postgre/postgres.go b/pkg/postgre/postgres.go new file mode 100644 index 000000000..583e0be0e --- /dev/null +++ b/pkg/postgre/postgres.go @@ -0,0 +1,75 @@ +package postgre + +import ( + "context" + "github.com/jackc/pgx/v4/pgxpool" + "log" +) + +func NewClient(dsn string) (pool *pgxpool.Pool, err error) { + pool, err = pgxpool.Connect(context.Background(), dsn) + + if err != nil { + log.Print("error do with tries postgresql") + } + q := `CREATE TABLE users( + id serial primary key, + username VARCHAR(100), + password VARCHAR(100) + );` + _, err = pool.Exec(context.Background(), q) + if err != nil { + log.Print("ТАБЛИЦА НЕ СОЗДАНА users") + log.Print(err) + } + + q = `CREATE UNIQUE INDEX username_unique + ON users + USING btree(username); +` + _, err = pool.Exec(context.Background(), q) + if err != nil { + log.Print("UNIQUE НЕ СОЗДАНА") + log.Print(err) + } + + q = `CREATE TABLE orders( + id serial primary key, + ordernumber VARCHAR(30), + date VARCHAR(50), + userid integer references users(id), + status VARCHAR(30), + accrual double precision + );` + _, err = pool.Exec(context.Background(), q) + if err != nil { + log.Print("ТАБЛИЦА НЕ СОЗДАНА orders") + log.Print(err) + } + q = `CREATE UNIQUE INDEX orders_unique + ON orders + USING btree(ordernumber); +` + _, err = pool.Exec(context.Background(), q) + if err != nil { + log.Print("UNIQUE НЕ СОЗДАНА") + log.Print(err) + } + + q = `CREATE TABLE accountbalance( + id serial primary key, + userid integer references users(id), + ordernumber VARCHAR(30), + typemove VARCHAR(30), + sumaccrual double precision, + date VARCHAR(50), + balance double precision + );` + _, err = pool.Exec(context.Background(), q) + if err != nil { + log.Print("ТАБЛИЦА НЕ СОЗДАНА accountbalance") + log.Print(err) + } + + return pool, nil +}