first time go in to cloud repo

This commit is contained in:
Trần Duy Linh 2023-12-03 10:46:00 +07:00
commit 594788380c
15 changed files with 1133 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

5
.env Normal file
View File

@ -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"

8
.idea/.gitignore vendored Normal file
View File

@ -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

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@ -0,0 +1,31 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownAttribute" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="src" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="img" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
</profile>
</component>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/go-restapi-parttern.iml" filepath="$PROJECT_DIR$/.idea/go-restapi-parttern.iml" />
</modules>
</component>
</project>

340
Template/email.html Normal file
View File

@ -0,0 +1,340 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>JS Bin</title>
</head>
<body style="background-color: #f2f5f8;">
<div style="max-width: 700px; margin: 0 auto">
<table
align="center"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
width="100%"
style="margin: auto"
>
<tbody>
<tr>
<td>
<table
align="center"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
width="100%"
style="margin: auto"
>
<tbody>
<tr>
<td style="padding: 30px 0; text-align: center"></td>
</tr>
<tr>
<td
style="
height: 4px;
text-align: center;
background: rgb(255, 122, 89);
--darkreader-inline-bgimage: initial;
--darkreader-inline-bgcolor: #971e00;
"
data-darkreader-inline-bgimage=""
data-darkreader-inline-bgcolor=""
></td>
</tr>
<tr>
<td
style="
background: rgb(255, 255, 255);
width: 100%;
padding: 40px 0;
min-width: 100%;
color: rgb(255, 255, 255);
--darkreader-inline-bgimage: initial;
--darkreader-inline-bgcolor: #181a1b;
--darkreader-inline-color: #e8e6e3;
"
data-darkreader-inline-bgimage=""
data-darkreader-inline-bgcolor=""
data-darkreader-inline-color=""
>
<img
src="https://pos.nvncdn.net/d0f3ca-7136/store/20230906_9BHMhQjv.png"
width="120"
height=""
alt="alt_text"
border="0"
style="
height: auto;
font-family: sans-serif;
font-size: 15px;
line-height: 15px;
color: rgb(85, 85, 85);
margin: auto;
display: block;
--darkreader-inline-color: #b2aca2;
"
class="CToWUd"
data-bit="iit"
data-darkreader-inline-color=""
/>
</td>
</tr>
<tr>
<td
style="
height: 1px;
background: rgb(255, 255, 255);
--darkreader-inline-bgimage: initial;
--darkreader-inline-bgcolor: #181a1b;
"
data-darkreader-inline-bgimage=""
data-darkreader-inline-bgcolor=""
>
<div
style="
height: 1px;
max-width: 500px;
margin: auto;
background: rgb(234, 240, 246);
--darkreader-inline-bgimage: initial;
--darkreader-inline-bgcolor: #202325;
"
data-darkreader-inline-bgimage=""
data-darkreader-inline-bgcolor=""
></div>
</td>
</tr>
<tr>
<td
style="
background-color: rgb(255, 255, 255);
--darkreader-inline-bgcolor: #181a1b;
"
data-darkreader-inline-bgcolor=""
>
<table
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
width="100%"
style="max-width: 500px; margin: auto"
>
<tbody></tbody>
<tbody>
<tr>
<td
style="
padding: 0;
font-family: sans-serif;
font-size: 14px;
line-height: 20px;
color: rgb(66, 91, 118);
--darkreader-inline-color: #97b0c5;
"
data-darkreader-inline-color=""
>
<h1
style="
text-align: center;
margin: 30px 0;
font-family: 'Helvetica Neue', 'Lexend Deca',
Arial, Helvetica, sans-serif;
font-size: 24px;
line-height: 30px;
color: rgb(66, 91, 118);
font-weight: bold;
--darkreader-inline-color: #97b0c5;
"
data-darkreader-inline-color=""
>
Verify your email.
</h1>
</td>
</tr>
<tr
style="
width: 100vw;
max-width: 700px;
height: auto;
display: flex;
justify-content: center;
align-items: center;
"
>
<img
src="https://res.cloudinary.com/practicaldev/image/fetch/s--1II67h1R--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://faruknasir.com/images/blog/2021/verification_url.png"
alt="verifi-piture"
width="420"
height=""
style="margin: auto"
/>
</tr>
<tr>
<td
style="
padding: 0;
font-family: 'Helvetica Neue', 'Lexend Deca',
Arial, Helvetica, sans-serif;
font-size: 16px;
line-height: 24px;
color: rgb(66, 91, 118);
--darkreader-inline-color: #97b0c5;
"
data-darkreader-inline-color=""
>
<p style="text-align: center; margin: 0">
Enter this code in your browser to verify your
email:
</p>
<p
style="
margin: 30px 0 0;
font-size: 40px;
line-height: 40px;
letter-spacing: 4px;
text-decoration: none;
display: block;
text-align: center;
"
>
{{.Otp}}
</p>
<p
style="
margin: 10px 0 30px;
font-size: 12px;
line-height: 12px;
text-decoration: none;
display: block;
text-align: center;
color: rgb(81, 111, 144);
--darkreader-inline-color: #88a4bc;
"
data-darkreader-inline-color=""
>
Code will expire in 24 hours.
</p>
<p style="text-align: center">
Having trouble with the code? Use
<a
href="{{.AlternativeLink}}"
style="
color: rgb(0, 141, 163);
--darkreader-inline-color: #5ae9ff;
"
target="_blank"
data-saferedirecturl=""
data-darkreader-inline-color="{{.AlternativeLink}}"
>this link</a
>
instead.
</p>
</td>
</tr>
<tr>
<td
aria-hidden="true"
height="50"
style="
font-size: 0;
line-height: 0;
display: block;
"
>
&nbsp;
</td>
</tr>
<tr>
<td
style="
display: none;
padding: 40px 60px 0;
font-family: 'Helvetica Neue', 'Lexend Deca',
Arial, Helvetica, sans-serif;
font-size: 14px;
line-height: 24px;
color: rgb(45, 62, 80);
--darkreader-inline-color: #aec2d2;
"
data-darkreader-inline-color=""
></td>
</tr>
<tr>
<td
aria-hidden="true"
height="50"
style="font-size: 0; line-height: 0"
>
&nbsp;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table
align="center"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
width="100%"
style="margin: auto"
>
<tbody>
<tr>
<td
style="
padding: 20px 10%;
font-family: 'Helvetica Neue', 'Lexend Deca', Arial,
Helvetica, sans-serif;
font-size: 14px;
line-height: 24px;
text-align: center;
color: rgb(124, 152, 182);
--darkreader-inline-color: #83a1ba;
"
data-darkreader-inline-color=""
>
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.
</td>
</tr>
<tr>
<td
style="
padding: 20px;
font-family: 'Helvetica Neue', 'Lexend Deca', Arial,
Helvetica, sans-serif;
font-size: 12px;
line-height: 24px;
text-align: center;
color: rgb(124, 152, 182);
--darkreader-inline-color: #83a1ba;
"
data-darkreader-inline-color=""
>
WliafDew, Inc.<br /><span
>72 Truong Quyen, 3nd Floor<br />Tp. Ho Chi Minh, VN
020319</span
>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</body>
</html>

33
go.mod Normal file
View File

@ -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
)

101
go.sum Normal file
View File

@ -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=

38
main.go Normal file
View File

@ -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()
}

85
models/models.go Normal file
View File

@ -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"`
}

380
rest-api/init-api.go Normal file
View File

@ -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(&registerInfo)
//call function check info user type in
validRegisterInfo := checkAndValidDataFiled(&registerInfo, 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]
}

29
utils/mail-template.go Normal file
View File

@ -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()
}

35
utils/mongodb.go Normal file
View File

@ -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
}

31
utils/redisdb.go Normal file
View File

@ -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
}