commit 594788380c22bbb837494b480acb12ba976b6dc7 Author: linhdevtran99 Date: Sun Dec 3 10:46:00 2023 +0700 first time go in to cloud repo diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..685250b Binary files /dev/null and b/.DS_Store differ diff --git a/.env b/.env new file mode 100644 index 0000000..b7451ca --- /dev/null +++ b/.env @@ -0,0 +1,5 @@ +MONGO_URI="mongodb://adminLinh:linhporo1@localhost:27017/linhporo1?authSource=admin" +API_TEST_PORT=":8080" +REDIS_URI="redis://localhost:6379/0" +STMP_PASS="btmp judz ebys pfxw" +EMAIL_VERIFY_SECRET="WDc&4+&vYP(n'}?LHNE#5M?IE|g(c812" \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/go-restapi-parttern.iml b/.idea/go-restapi-parttern.iml new file mode 100644 index 0000000..5e764c4 --- /dev/null +++ b/.idea/go-restapi-parttern.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..21a006a --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,31 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..3f38615 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Template/email.html b/Template/email.html new file mode 100644 index 0000000..8dbd674 --- /dev/null +++ b/Template/email.html @@ -0,0 +1,340 @@ + + + + + + JS Bin + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ alt_text +
+
+
+ + + + + + + + verifi-piture + + + + + + + + + + + + + + +
+

+ Verify your email. +

+
+

+ Enter this code in your browser to verify your + email: +

+

+ {{.Otp}} +

+

+ Code will expire in 24 hours. +

+

+ Having trouble with the code? Use + this link + instead. + +

+
+
+ + + + + + + + + +
+ Not you? If you didn't request a code to sign up for + HubSpot, you can safely ignore this email. An account was + not created. +
+ WliafDew, Inc.
72 Truong Quyen, 3nd Floor
Tp. Ho Chi Minh, VN + 020319
+
+
+
+ + diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2f166e1 --- /dev/null +++ b/go.mod @@ -0,0 +1,33 @@ +module linhdevtran99/rest-api + +go 1.21.0 + +require ( + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/go-mail/mail v2.3.1+incompatible // 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.16.0 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/pquerna/otp v1.4.0 // indirect + github.com/redis/go-redis/v9 v9.3.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + go.mongodb.org/mongo-driver v1.13.0 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d403131 --- /dev/null +++ b/go.sum @@ -0,0 +1,101 @@ +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/go-mail/mail v2.3.1+incompatible h1:UzNOn0k5lpfVtO31cK3hn6I4VEVGhe3lX8AJBAxXExM= +github.com/go-mail/mail v2.3.1+incompatible/go.mod h1:VPWjmmNyRsWXQZHVHT3g0YbIINUkSmuKOiLIDkWbL6M= +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.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= +github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= +github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= +github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.13.0 h1:67DgFFjYOCMWdtTEmKFpV3ffWlFnh+CYZ8ZS/tXWUfY= +go.mongodb.org/mongo-driver v1.13.0/go.mod h1:/rGBTebI3XYboVmgz+Wv3Bcbl3aD0QF9zl6kDDw18rQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= +gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= +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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..b5e1fd8 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "fmt" + "github.com/go-playground/validator/v10" + "github.com/joho/godotenv" + "linhdevtran99/rest-api/models" + "linhdevtran99/rest-api/rest-api" + "linhdevtran99/rest-api/utils" + "log" + "os" +) + +func main() { + if err := godotenv.Load(); err != nil { + fmt.Println("No .env file found") + panic(err) + } + + redisUri := os.Getenv("REDIS_URI") + apiTestPort := os.Getenv("API_TEST_PORT") + mongoUri := os.Getenv("MONGO_URI") + + if redisUri == "" || apiTestPort == "" || mongoUri == "" { + fmt.Println("Redis URI:", redisUri) + fmt.Println("API test Port:", apiTestPort) + fmt.Println("Mongodb Uri:", mongoUri) + log.Fatal("Error in some value") + } + + models.Validate = validator.New(validator.WithRequiredStructEnabled()) + + utils.InitMongoDriver(mongoUri) + + server := rest_api.NewAPIServer(apiTestPort) + utils.RedisClientDriver(redisUri) + server.Run() +} diff --git a/models/models.go b/models/models.go new file mode 100644 index 0000000..f3789c4 --- /dev/null +++ b/models/models.go @@ -0,0 +1,85 @@ +package models + +import ( + "github.com/go-playground/validator/v10" + "go.mongodb.org/mongo-driver/bson/primitive" + "strings" + "time" + "unicode" +) + +// Mongodb Type +type PreusersMongo struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Username string `bson:"username" json:"username" ` + Email string `bson:"email" json:"email"` + HashPassword string `bson:"hash_password" json:"hash_password"` + PhoneNumber string `bson:"phone_number" json:"phone_number"` + CreatedDate time.Time `bson:"created_date" json:"created_date"` + VerifySentCount int `bson:"verify_sent_count" json:"verify_sent_count"` +} + +type UsersMongo struct { + ID primitive.ObjectID `bson:"_id,omitempty" json:"id,omitempty"` + Username string `bson:"username" json:"username"` + Email string `bson:"email" json:"email"` + HashPassword string `bson:"hash_password" json:"hash_password"` + PhoneNumber string `bson:"phone_number" json:"phone_number"` + Active bool `bson:"active" json:"active"` + CreatedDate time.Time `bson:"created_date" json:"created_date"` + VerifySentCount int `bson:"verify_sent_count" json:"verify_sent_count"` +} + +var Validate *validator.Validate + +// API Type +type CreateUser struct { + Username string `json:"username" validate:"required,min=8,max=20"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required,min=8,max=20,customPassword"` + ConfirmPassword string `json:"confirm_password" validate:"required,eqfield=Password"` + PhoneNumber string `json:"phone_number" validate:"required,len=10"` +} + +type Users struct { + Username string `json:"username"` + Email string `json:"email"` + HashPassword string `json:"hash_password"` + PhoneNumber string `json:"phone_number"` + Active bool `json:"active"` + CreatedDate string `json:"created_date"` + ValidDate int `json:"verify_sent_count"` +} + +// custom validator list + +func PasswordValidator(fl validator.FieldLevel) bool { + password := fl.Field().String() + + // Flags to track if at least one symbol and one number are found + hasSymbol := false + hasNumber := false + + // Check if the password contains at least one symbol and one number + for _, char := range password { + if strings.ContainsRune("!@#$%^&*()-_=+[]{}|;:'\"<>,.?/~`", char) { + hasSymbol = true + } else if unicode.IsNumber(char) { + hasNumber = true + } + + // Break early if both conditions are met + if hasSymbol && hasNumber { + return true + } + } + + return false +} + +//Interal Type + +type EmailTemplate struct { + Otp string `json:"otp"` + AlternativeLink string `json:"alternativeLink"` +} diff --git a/rest-api/init-api.go b/rest-api/init-api.go new file mode 100644 index 0000000..b5eb0de --- /dev/null +++ b/rest-api/init-api.go @@ -0,0 +1,380 @@ +package rest_api + +import ( + "bytes" + "context" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base32" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/go-playground/validator/v10" + "github.com/gorilla/mux" + "github.com/pquerna/otp" + "github.com/pquerna/otp/hotp" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "golang.org/x/crypto/bcrypt" + "linhdevtran99/rest-api/models" + "linhdevtran99/rest-api/utils" + "log" + "net/http" + "os" +) + +type APIServer struct { + listenAddr string +} + +func WriteJSON(w http.ResponseWriter, status int, v any) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + return json.NewEncoder(w).Encode(v) +} + +type ApiError struct { + Error string +} + +// define function apiFn +type apiFunc func(http.ResponseWriter, *http.Request) error + +// makeHTTPHandlerFn fn +func makeHTTPHandlerFn(fn apiFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + if err := WriteJSON(w, http.StatusInternalServerError, ApiError{Error: err.Error()}); err != nil { + fmt.Print(err) + } + } + } +} + +func NewAPIServer(listenAddr string) *APIServer { + return &APIServer{ + listenAddr: listenAddr, + } +} + +func startMuxServer(s *APIServer, router *mux.Router) { + log.Println("Listening on", s.listenAddr) + + if err := http.ListenAndServe(s.listenAddr, router); err != nil { + log.Fatal(err) + } +} + +func (s *APIServer) Run() { + router := mux.NewRouter() + + router.HandleFunc("/account/register", makeHTTPHandlerFn(s.registerNewAccount)) + router.HandleFunc("/account", makeHTTPHandlerFn(s.handleAccount)) + router.HandleFunc("/test", makeHTTPHandlerFn(s.testCheckUserAndPass)) + + startMuxServer(s, router) +} + +func (s *APIServer) registerNewAccount(w http.ResponseWriter, r *http.Request) error { + if r.Method == http.MethodGet { + fmt.Println("API Route Healthy") + } + + return nil +} + +func (s *APIServer) testCheckUserAndPass(w http.ResponseWriter, r *http.Request) error { + w.Header().Set("Content-Type", "application/json") + client := utils.MongoDB + + if r.Method == http.MethodGet { + fmt.Println("hit") + + //m := mail.NewMessage() + // + //emailBody := utils.BuildEmail() + //m.SetHeader("From", "kotomi.poro1@gmail.com") + //m.SetHeader("To", "nhocdl.poro1@gmail.com") + //m.SetHeader("Subject", "Hello!") + //m.SetBody("text/html", emailBody) + // + //d := mail.NewDialer("smtp.gmail.com", 587, "kotomi.poro1@gmail.com", "btmpjudzebyspfxw") + //d.StartTLSPolicy = mail.MandatoryStartTLS + // + //// Send the email to Bob, Cora and Dan. + //if err := d.DialAndSend(m); err != nil { + // panic(err) + //} + + serect := os.Getenv("EMAIL_VERIFY_SECRET") + //isPass, _ := generatorOtp("hello", "nhocdl.poro1@gmail.com", 12, serect) + //fmt.Println(isPass) + cipherBase64 := createLinkVerify(&models.CreateUser{ + Username: "thewind121212", + Email: "nhocdl.poro1@gmail.com", + Password: "linhporoQ1@", + ConfirmPassword: "linhporoQ1@", + }, serect) + + key := []byte(serect) + + i, err := decrypt(cipherBase64, key) + if err != nil { + return err + } + + fmt.Println(string(i)) + + } + + if r.Method == http.MethodPost { + var registerInfo models.CreateUser + + _ = json.NewDecoder(r.Body).Decode(®isterInfo) + //call function check info user type in + validRegisterInfo := checkAndValidDataFiled(®isterInfo, w) + //call function check data user use in past or not + isValidData := checkAccountExist(client, registerInfo.Username, registerInfo.Email, w) + + if isValidData == false || validRegisterInfo == false { + return errors.New("USER DON'T HAVE VALID INFO FOR REGISTER ACCOUNT") + } + + } + + return nil +} + +func (s *APIServer) handleAccount(w http.ResponseWriter, r *http.Request) error { + if r.Method == http.MethodGet { + ctx := context.Background() + + res, err := utils.Redis.Ping(ctx).Result() + + if err != nil { + fmt.Println(err) + } + + fmt.Println(res) + + } + if r.Method == http.MethodPost { + fmt.Println("POST") + } + if r.Method == http.MethodDelete { + fmt.Println("DELETE") + } + if r.Method == http.MethodPut { + fmt.Println("PUT") + } + if r.Method == http.MethodPatch { + fmt.Println("PATCH") + } + + return nil +} + +//register group-func + +//checking is user create that have account before + +func checkAndValidDataFiled(registerData *models.CreateUser, w http.ResponseWriter) bool { + _ = models.Validate.RegisterValidation("customPassword", models.PasswordValidator) + errs := models.Validate.Struct(registerData) + var errStack []string + if errs != nil { + for _, err := range errs.(validator.ValidationErrors) { + switch err.Field() { + case "Email": + { + fmt.Println("Email không hợp lệ") + errStack = append(errStack, "Email") + } + case "Username": + { + fmt.Println("User không hợp lệ") + errStack = append(errStack, "User") + } + case "Password": + { + fmt.Println("Password không hợp lệ") + errStack = append(errStack, "Password") + } + case "ConfirmPassword": + { + fmt.Println("Nhập lại mật khẩu sai") + errStack = append(errStack, "ConfirmPassword") + } + case "PhoneNumber": + { + fmt.Println("SĐT không hợp lệ") + errStack = append(errStack, "PhoneNumber") + } + } + } + //handle repose error + w.WriteHeader(http.StatusBadRequest) + err := json.NewEncoder(w).Encode(errStack) + + if err != nil { + fmt.Println("There is error in write reponse at checking info user") + } + return false + } + + return true + +} + +func checkAccountExist(mongoClient *mongo.Client, userName string, email string, w http.ResponseWriter) bool { + //filter in mongodb + var isValid bool + + filter := bson.D{ + {"$or", bson.A{ + bson.D{{"username", userName}}, + bson.D{{"email", email}}, + }}, + } + + userData := mongoClient.Database("Totoday-shop").Collection("users") + + var result models.UsersMongo + err := userData.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + isValid = true + } + if err == nil { + fmt.Println("Email or username already had register") + w.WriteHeader(http.StatusBadRequest) + err := json.NewEncoder(w).Encode("Your username or email had been register before") + if err != nil { + fmt.Println("There is error in write reponse at checking data user register") + } + isValid = false + } + + return isValid +} + +//after cheking register data generate otp and send email write valid time to confirm in redis +// noice this will do concurency for speed reason + +type otpGenerate struct { + pureOTP string + hashOTP string +} + +func generatorOtp(userName string, email string, counter uint64, serect string) (bool, *otpGenerate) { + + serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + userName + email)) + passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ + Digits: 6, + Algorithm: otp.AlgorithmSHA256, + }) + + if err != nil { + fmt.Println("Fail to create OTP") + return false, &otpGenerate{ + pureOTP: "none", + hashOTP: "none", + } + } + + hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) + if err != nil { + fmt.Println("Fail to create OTP") + return false, &otpGenerate{ + pureOTP: "none", + hashOTP: "none", + } + } + + return true, &otpGenerate{ + pureOTP: passCode, + hashOTP: string(hashed), + } +} + +func createLinkVerify(registerInfo *models.CreateUser, secrect string) string { + + data, _ := json.Marshal(registerInfo) + key := []byte(secrect) + + ciphertext, _ := encrypt(data, key) + + cipherTextBase64 := base64.StdEncoding.EncodeToString(ciphertext) + + return cipherTextBase64 +} + +func encrypt(data []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Generate a random IV (Initialization Vector) + iv := make([]byte, aes.BlockSize) + if _, err := rand.Read(iv); err != nil { + fmt.Println("error generating IV:", err) + } + + // Pad the data to a multiple of the block size + data = pkcs7Pad(data, aes.BlockSize) + + // Create a CBC mode cipher block + mode := cipher.NewCBCEncrypter(block, iv) + + // Encrypt the data + ciphertext := make([]byte, len(data)) + mode.CryptBlocks(ciphertext, data) + + // Prepend the IV to the ciphertext + ciphertext = append(iv, ciphertext...) + + return ciphertext, nil +} + +func decrypt(base64Ciphertext string, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Decode base64 + ciphertext, err := base64.StdEncoding.DecodeString(base64Ciphertext) + if err != nil { + fmt.Println("error decoding base64:", err) + } + + // Extract the IV from the ciphertext + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + // Create a CBC mode cipher block + mode := cipher.NewCBCDecrypter(block, iv) + + // Decrypt the data + mode.CryptBlocks(ciphertext, ciphertext) + + // Remove padding + ciphertext = pkcs7Unpad(ciphertext) + + return ciphertext, nil +} + +// pkcs7Pad pads the input to a multiple of blockSize using PKCS#7 padding +func pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padText...) +} + +// pkcs7Unpad removes PKCS#7 padding from the input +func pkcs7Unpad(data []byte) []byte { + padding := int(data[len(data)-1]) + return data[:len(data)-padding] +} diff --git a/utils/mail-template.go b/utils/mail-template.go new file mode 100644 index 0000000..1062835 --- /dev/null +++ b/utils/mail-template.go @@ -0,0 +1,29 @@ +package utils + +import ( + "bytes" + "fmt" + "html/template" + "linhdevtran99/rest-api/models" +) + +func BuildEmail() string { + + data := models.EmailTemplate{ + Otp: "11017", + AlternativeLink: "https://www.google.com.vn", + } + + tmpl, err := template.ParseFiles("./Template/email.html") + + if err != nil { + fmt.Println("Cant achieve file") + } + + var result bytes.Buffer + _ = tmpl.Execute(&result, data) + + fmt.Println(result.String()) + return result.String() + +} diff --git a/utils/mongodb.go b/utils/mongodb.go new file mode 100644 index 0000000..d9412a0 --- /dev/null +++ b/utils/mongodb.go @@ -0,0 +1,35 @@ +package utils + +import ( + "context" + "fmt" + "time" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + "go.mongodb.org/mongo-driver/mongo/readpref" +) + +var MongoDB *mongo.Client + +func InitMongoDriver(mongoUri string) *mongo.Client { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + client, err := mongo.Connect(ctx, options.Client().ApplyURI(mongoUri)) + + if err != nil { + panic(err) + } + + err = client.Ping(ctx, readpref.Primary()) + + if err != nil { + panic(err) + } else { + fmt.Println("MongoDB is ready") + } + + MongoDB = client + + return client +} diff --git a/utils/redisdb.go b/utils/redisdb.go new file mode 100644 index 0000000..9a138e0 --- /dev/null +++ b/utils/redisdb.go @@ -0,0 +1,31 @@ +package utils + +import ( + "context" + "fmt" + "github.com/redis/go-redis/v9" +) + +var Redis *redis.Client + +func RedisClientDriver(redisUri string) *redis.Client { + ctx := context.Background() + opt, err := redis.ParseURL(redisUri) + if err != nil { + panic(err) + } + redisClient := redis.NewClient(opt) + res, err := redisClient.Ping(ctx).Result() + + if err != nil { + ctx.Done() + panic(err) + } + + if res == "PONG" { + fmt.Println("Redis is ready") + } + + Redis = redisClient + return redisClient +}