feat: user & article: dev complete
This commit is contained in:
@@ -10,6 +10,7 @@ type ServerConfig struct {
|
||||
MySQL MySQLConfig `yaml:"mysql"`
|
||||
Redis RedisConfig `yaml:"redis"`
|
||||
Jwt JwtConfig `yaml:"jwt"`
|
||||
Email EmailConfig `yaml:"email"`
|
||||
}
|
||||
|
||||
type MySQLConfig struct {
|
||||
@@ -31,3 +32,10 @@ type JwtConfig struct {
|
||||
RenewExpireDays uint `yaml:"renewExpireDays"`
|
||||
RenewAheadDays uint `yaml:"renewAheadDays"`
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
Host string `yaml:"host"`
|
||||
Port int `yaml:"port"`
|
||||
Account string `yaml:"account"`
|
||||
Password string `yaml:"password"`
|
||||
}
|
||||
|
||||
569
docs/docs.go
569
docs/docs.go
@@ -16,6 +16,228 @@ const docTemplate_swagger = `{
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/article": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "save article",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "article",
|
||||
"name": "Article",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article/list": {
|
||||
"get": {
|
||||
"description": "Admin can get not published article",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "get all articles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article/{id}": {
|
||||
"get": {
|
||||
"description": "Admin can get not published article",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "get all articles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "delete an article",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article/{id}/publish": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "get all articles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/statistics/china": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@@ -31,7 +253,7 @@ const docTemplate_swagger = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -76,7 +298,7 @@ const docTemplate_swagger = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -110,7 +332,7 @@ const docTemplate_swagger = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -144,7 +366,7 @@ const docTemplate_swagger = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -192,7 +414,7 @@ const docTemplate_swagger = `{
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -210,6 +432,236 @@ const docTemplate_swagger = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/approve": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "admin approve account, user can use account after approved",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserApprove"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/chpwd": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "change user's password",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserChangePwd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/code/{email}/{code}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "send verify code",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "email",
|
||||
"name": "email",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "code",
|
||||
"name": "code",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/login": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "user login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserLogin"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/register": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "user register account",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserRegister"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/registers": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "list register infos, which is to be approved",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/{code}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "send verify code",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "email",
|
||||
"name": "email",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -273,6 +725,55 @@ const docTemplate_swagger = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.BackArticle": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "文章内容(如有需要可迁移至对象存储)",
|
||||
"type": "string"
|
||||
},
|
||||
"cover": {
|
||||
"description": "文章封面",
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"description": "文章新建时间",
|
||||
"type": "string"
|
||||
},
|
||||
"createUser": {
|
||||
"description": "文章创建者id",
|
||||
"type": "string"
|
||||
},
|
||||
"isDelete": {
|
||||
"description": "删除标志",
|
||||
"type": "integer"
|
||||
},
|
||||
"isPublish": {
|
||||
"description": "发布状态(0:未发布, 1: 发布)",
|
||||
"type": "integer"
|
||||
},
|
||||
"modifyTime": {
|
||||
"description": "文章最后更新时间",
|
||||
"type": "string"
|
||||
},
|
||||
"modifyUser": {
|
||||
"description": "文章最后更新者id",
|
||||
"type": "string"
|
||||
},
|
||||
"resume": {
|
||||
"description": "文章简述",
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"description": "文章Tag",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "文章标题",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ChinaAdd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -369,7 +870,63 @@ const docTemplate_swagger = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.GinResponse": {
|
||||
"models.UserApprove": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"pass": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserChangePwd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"newPassword": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserLogin": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserRegister": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aptitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils.GinResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
|
||||
@@ -6,6 +6,228 @@
|
||||
"version": "1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/article": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "save article",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "article",
|
||||
"name": "Article",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article/list": {
|
||||
"get": {
|
||||
"description": "Admin can get not published article",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "get all articles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article/{id}": {
|
||||
"get": {
|
||||
"description": "Admin can get not published article",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "get all articles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/models.BackArticle"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "delete an article",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/article/{id}/publish": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Article"
|
||||
],
|
||||
"summary": "get all articles",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "id",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/statistics/china": {
|
||||
"get": {
|
||||
"produces": [
|
||||
@@ -21,7 +243,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -66,7 +288,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -100,7 +322,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -134,7 +356,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -182,7 +404,7 @@
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.GinResponse"
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
@@ -200,6 +422,236 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/approve": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "admin approve account, user can use account after approved",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserApprove"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/chpwd": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "change user's password",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserChangePwd"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/code/{email}/{code}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "send verify code",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "email",
|
||||
"name": "email",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "code",
|
||||
"name": "code",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/login": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "user login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserLogin"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/register": {
|
||||
"post": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "user register account",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "json",
|
||||
"name": "json",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.UserRegister"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/registers": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "list register infos, which is to be approved",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "token",
|
||||
"name": "Token",
|
||||
"in": "header",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/{code}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"User"
|
||||
],
|
||||
"summary": "send verify code",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "email",
|
||||
"name": "email",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/utils.GinResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -263,6 +715,55 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.BackArticle": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"content": {
|
||||
"description": "文章内容(如有需要可迁移至对象存储)",
|
||||
"type": "string"
|
||||
},
|
||||
"cover": {
|
||||
"description": "文章封面",
|
||||
"type": "string"
|
||||
},
|
||||
"createTime": {
|
||||
"description": "文章新建时间",
|
||||
"type": "string"
|
||||
},
|
||||
"createUser": {
|
||||
"description": "文章创建者id",
|
||||
"type": "string"
|
||||
},
|
||||
"isDelete": {
|
||||
"description": "删除标志",
|
||||
"type": "integer"
|
||||
},
|
||||
"isPublish": {
|
||||
"description": "发布状态(0:未发布, 1: 发布)",
|
||||
"type": "integer"
|
||||
},
|
||||
"modifyTime": {
|
||||
"description": "文章最后更新时间",
|
||||
"type": "string"
|
||||
},
|
||||
"modifyUser": {
|
||||
"description": "文章最后更新者id",
|
||||
"type": "string"
|
||||
},
|
||||
"resume": {
|
||||
"description": "文章简述",
|
||||
"type": "string"
|
||||
},
|
||||
"tags": {
|
||||
"description": "文章Tag",
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"description": "文章标题",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ChinaAdd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -359,7 +860,63 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.GinResponse": {
|
||||
"models.UserApprove": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"pass": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserChangePwd": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"newPassword": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserLogin": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"account": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserRegister": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"aptitude": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"phone": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"utils.GinResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
|
||||
@@ -38,6 +38,42 @@ definitions:
|
||||
wzz:
|
||||
type: integer
|
||||
type: object
|
||||
models.BackArticle:
|
||||
properties:
|
||||
content:
|
||||
description: 文章内容(如有需要可迁移至对象存储)
|
||||
type: string
|
||||
cover:
|
||||
description: 文章封面
|
||||
type: string
|
||||
createTime:
|
||||
description: 文章新建时间
|
||||
type: string
|
||||
createUser:
|
||||
description: 文章创建者id
|
||||
type: string
|
||||
isDelete:
|
||||
description: 删除标志
|
||||
type: integer
|
||||
isPublish:
|
||||
description: '发布状态(0:未发布, 1: 发布)'
|
||||
type: integer
|
||||
modifyTime:
|
||||
description: 文章最后更新时间
|
||||
type: string
|
||||
modifyUser:
|
||||
description: 文章最后更新者id
|
||||
type: string
|
||||
resume:
|
||||
description: 文章简述
|
||||
type: string
|
||||
tags:
|
||||
description: 文章Tag
|
||||
type: string
|
||||
title:
|
||||
description: 文章标题
|
||||
type: string
|
||||
type: object
|
||||
models.ChinaAdd:
|
||||
properties:
|
||||
confirm:
|
||||
@@ -101,7 +137,43 @@ definitions:
|
||||
suspect:
|
||||
type: integer
|
||||
type: object
|
||||
models.GinResponse:
|
||||
models.UserApprove:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
pass:
|
||||
type: boolean
|
||||
type: object
|
||||
models.UserChangePwd:
|
||||
properties:
|
||||
code:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
newPassword:
|
||||
type: string
|
||||
type: object
|
||||
models.UserLogin:
|
||||
properties:
|
||||
account:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
models.UserRegister:
|
||||
properties:
|
||||
aptitude:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
phone:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
utils.GinResponse:
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
@@ -113,6 +185,143 @@ info:
|
||||
title: nCov Tracker
|
||||
version: "1.0"
|
||||
paths:
|
||||
/article:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: article
|
||||
in: body
|
||||
name: Article
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.BackArticle'
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.BackArticle'
|
||||
type: object
|
||||
summary: save article
|
||||
tags:
|
||||
- Article
|
||||
/article/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
required: true
|
||||
type: string
|
||||
- description: id
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: delete an article
|
||||
tags:
|
||||
- Article
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Admin can get not published article
|
||||
parameters:
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
type: string
|
||||
- description: id
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.BackArticle'
|
||||
type: object
|
||||
summary: get all articles
|
||||
tags:
|
||||
- Article
|
||||
/article/{id}/publish:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
required: true
|
||||
type: string
|
||||
- description: id
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: get all articles
|
||||
tags:
|
||||
- Article
|
||||
/article/list:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Admin can get not published article
|
||||
parameters:
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/models.BackArticle'
|
||||
type: array
|
||||
type: object
|
||||
summary: get all articles
|
||||
tags:
|
||||
- Article
|
||||
/statistics/china:
|
||||
get:
|
||||
produces:
|
||||
@@ -122,7 +331,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.GinResponse'
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/models.ChinaData'
|
||||
@@ -149,7 +358,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.GinResponse'
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -168,7 +377,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.GinResponse'
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -187,7 +396,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.GinResponse'
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -216,7 +425,7 @@ paths:
|
||||
description: OK
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.GinResponse'
|
||||
- $ref: '#/definitions/utils.GinResponse'
|
||||
- properties:
|
||||
data:
|
||||
items:
|
||||
@@ -226,4 +435,152 @@ paths:
|
||||
summary: province statistics
|
||||
tags:
|
||||
- Statistics
|
||||
/user/{code}:
|
||||
get:
|
||||
parameters:
|
||||
- description: email
|
||||
in: path
|
||||
name: email
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: send verify code
|
||||
tags:
|
||||
- User
|
||||
/user/approve:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
required: true
|
||||
type: string
|
||||
- description: json
|
||||
in: body
|
||||
name: json
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserApprove'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: admin approve account, user can use account after approved
|
||||
tags:
|
||||
- User
|
||||
/user/chpwd:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: json
|
||||
in: body
|
||||
name: json
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserChangePwd'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: change user's password
|
||||
tags:
|
||||
- User
|
||||
/user/code/{email}/{code}:
|
||||
get:
|
||||
parameters:
|
||||
- description: email
|
||||
in: path
|
||||
name: email
|
||||
required: true
|
||||
type: string
|
||||
- description: code
|
||||
in: path
|
||||
name: code
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: send verify code
|
||||
tags:
|
||||
- User
|
||||
/user/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: json
|
||||
in: body
|
||||
name: json
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserLogin'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: user login
|
||||
tags:
|
||||
- User
|
||||
/user/register:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: json
|
||||
in: body
|
||||
name: json
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.UserRegister'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: user register account
|
||||
tags:
|
||||
- User
|
||||
/user/registers:
|
||||
get:
|
||||
parameters:
|
||||
- description: token
|
||||
in: header
|
||||
name: Token
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/utils.GinResponse'
|
||||
summary: list register infos, which is to be approved
|
||||
tags:
|
||||
- User
|
||||
swagger: "2.0"
|
||||
|
||||
@@ -36,4 +36,8 @@ func GetHttpClient(key string) (*http.Client, error) {
|
||||
const (
|
||||
CHINA_NCOV_STATISTIC_URL = "https://view.inews.qq.com/g2/getOnsInfo?name=disease_h5"
|
||||
ENV_NOLOG = "nolog"
|
||||
TOKEN_EXPIRE_DAYS = 15
|
||||
|
||||
REGISTER_REDIS_KEY = "register_key"
|
||||
CHANGEPWD_REDIS_KEY = "changepwd_key"
|
||||
)
|
||||
|
||||
2
go.mod
2
go.mod
@@ -30,9 +30,11 @@ require (
|
||||
github.com/go-sql-driver/mysql v1.6.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.4 // indirect
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -172,6 +172,8 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe
|
||||
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
|
||||
@@ -188,6 +190,8 @@ github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkr
|
||||
github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
|
||||
@@ -8,53 +8,108 @@ import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// SaveArticleHandler save an article
|
||||
// @Tags Article
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary save article
|
||||
// @Success 200 {object} utils.GinResponse{data=models.BackArticle}
|
||||
// @Router /article [post]
|
||||
// @Param Article body models.BackArticle true "article"
|
||||
// @Param Token header string true "token"
|
||||
func SaveArticleHandler(c *gin.Context) {
|
||||
var articleSave models.BackArticle
|
||||
err := c.ShouldBindJSON(&articleSave)
|
||||
if err != nil {
|
||||
var requestBody []byte
|
||||
_, err := c.Request.Body.Read(requestBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
utils.RequestErr(c, requestBody)
|
||||
jsonMap := bindJson(c)
|
||||
if jsonMap == nil {
|
||||
return
|
||||
}
|
||||
if ok := article.SaveArticle(&articleSave); !ok {
|
||||
utils.ServerErr(c, "Save Failed")
|
||||
colMap := models.MapJ2c[models.BackArticle](jsonMap, true)
|
||||
if ok := article.SaveArticle(colMap); !ok {
|
||||
ServerErr(c, "Save Failed")
|
||||
return
|
||||
}
|
||||
utils.Succ(c, articleSave)
|
||||
utils.Succ(c, jsonMap)
|
||||
}
|
||||
|
||||
// GetAllArticlesHandler get all article
|
||||
// @Tags Article
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary get all articles
|
||||
// @Description Admin can get not published article
|
||||
// @Success 200 {object} utils.GinResponse{data=[]models.BackArticle}
|
||||
// @Router /article/list [get]
|
||||
// @Param Token header string false "token"
|
||||
func GetAllArticlesHandler(c *gin.Context) {
|
||||
articles := article.GetArticleList()
|
||||
// TODO: admin need to show more articles
|
||||
articles := article.ListAllArticles()
|
||||
utils.Succ(c, articles)
|
||||
}
|
||||
|
||||
// DeleteArticleHandler delete article
|
||||
// @Tags Article
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary delete an article
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /article/{id} [delete]
|
||||
// @Param Token header string true "token"
|
||||
// @Param id path string true "id"
|
||||
func DeleteArticleHandler(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
utils.RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
|
||||
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
|
||||
return
|
||||
}
|
||||
if ok := article.DeleteArticle(id); !ok {
|
||||
utils.DataNotFound(c, "The article not found id = "+strconv.Itoa(id))
|
||||
ServerErr(c, "Can't delete the article")
|
||||
return
|
||||
}
|
||||
utils.Succ(c, nil)
|
||||
}
|
||||
|
||||
// GetArticleHandler get an article
|
||||
// @Tags Article
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary get all articles
|
||||
// @Description Admin can get not published article
|
||||
// @Success 200 {object} utils.GinResponse{data=models.BackArticle}
|
||||
// @Router /article/{id} [get]
|
||||
// @Param Token header string false "token"
|
||||
// @Param id path string true "id"
|
||||
func GetArticleHandler(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
utils.RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
|
||||
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
|
||||
return
|
||||
}
|
||||
res := article.GetArticleById(id)
|
||||
//TODO: if not admin, will not show not published article
|
||||
if res == nil {
|
||||
utils.DataNotFound(c, nil)
|
||||
DataNotFound(c, nil)
|
||||
return
|
||||
}
|
||||
utils.Succ(c, res)
|
||||
}
|
||||
|
||||
// PublishArticleHandler publish an article
|
||||
// @Tags Article
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary get all articles
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /article/{id}/publish [post]
|
||||
// @Param Token header string true "token"
|
||||
// @Param id path string true "id"
|
||||
func PublishArticleHandler(c *gin.Context) {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
|
||||
return
|
||||
}
|
||||
if ok := article.PublishArticle(id); !ok {
|
||||
ServerErr(c, "Can't publish the article")
|
||||
return
|
||||
}
|
||||
utils.Succ(c, nil)
|
||||
}
|
||||
|
||||
33
handler/errors.go
Normal file
33
handler/errors.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"nCovTrack-Backend/utils"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// This file is define some business error
|
||||
|
||||
const (
|
||||
BAD_REQUEST = "Bad Request"
|
||||
DATA_NOT_FOUND = "Data not Found"
|
||||
STATUS_DATA_NOT_FOUND = 210
|
||||
)
|
||||
|
||||
func RequestError(c *gin.Context, code int, data interface{}) {
|
||||
utils.Error(c, http.StatusBadRequest, code, BAD_REQUEST, data)
|
||||
}
|
||||
|
||||
func RequestErr(c *gin.Context, data interface{}) {
|
||||
RequestError(c, http.StatusBadRequest, data)
|
||||
}
|
||||
func ServerError(c *gin.Context, code int, msg interface{}) {
|
||||
utils.Err(c, http.StatusInternalServerError, code, msg)
|
||||
}
|
||||
|
||||
func ServerErr(c *gin.Context, msg interface{}) {
|
||||
ServerError(c, http.StatusInternalServerError, msg)
|
||||
}
|
||||
func DataNotFound(c *gin.Context, data interface{}) {
|
||||
utils.Success(c, http.StatusOK, STATUS_DATA_NOT_FOUND, DATA_NOT_FOUND, data)
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Summary province statistics
|
||||
// @Success 200 {object} models.GinResponse{data=[]models.AreaInfo}
|
||||
// @Success 200 {object} utils.GinResponse{data=[]models.AreaInfo}
|
||||
// @Router /statistics/province/{sort} [get]
|
||||
// @Param sort path string false "data sorted by" Enums(today, total, now, default)
|
||||
func ProvinceDataHandler(c *gin.Context) {
|
||||
@@ -25,7 +25,7 @@ func ProvinceDataHandler(c *gin.Context) {
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Summary city statistics
|
||||
// @Success 200 {object} models.GinResponse{data=[]models.AreaInfo}
|
||||
// @Success 200 {object} utils.GinResponse{data=[]models.AreaInfo}
|
||||
// @Router /statistics/city/{sort} [get]
|
||||
// @Param sort path string false "data sorted by" Enums(today, total, now, default)
|
||||
func CityDataHandler(c *gin.Context) {
|
||||
@@ -38,7 +38,7 @@ func CityDataHandler(c *gin.Context) {
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Summary country statistics
|
||||
// @Success 200 {object} models.GinResponse{data=[]models.AreaInfo}
|
||||
// @Success 200 {object} utils.GinResponse{data=[]models.AreaInfo}
|
||||
// @Router /statistics/country/child [get]
|
||||
// @Router /statistics/country [get]
|
||||
func CountryDataHandler(c *gin.Context) {
|
||||
@@ -51,7 +51,7 @@ func CountryDataHandler(c *gin.Context) {
|
||||
// @Tags Statistics
|
||||
// @Produce json
|
||||
// @Summary china data
|
||||
// @Success 200 {object} models.GinResponse{data=models.ChinaData}
|
||||
// @Success 200 {object} utils.GinResponse{data=models.ChinaData}
|
||||
// @Router /statistics/china [get]
|
||||
func ChinaDataHandler(c *gin.Context) {
|
||||
data := service.GetChinaNCovStatistic()
|
||||
|
||||
144
handler/user.go
Normal file
144
handler/user.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"nCovTrack-Backend/models"
|
||||
"nCovTrack-Backend/service/user"
|
||||
"nCovTrack-Backend/utils"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
//UserRegisterHandler user register
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary user register account
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/register [post]
|
||||
// @Param json body models.UserRegister true "json"
|
||||
func UserRegisterHandler(c *gin.Context) {
|
||||
jsonMap := bindJsonStruct[models.UserRegister](c)
|
||||
if jsonMap == nil {
|
||||
return
|
||||
}
|
||||
registered := user.NoDuplicatePhoneOrEmail(jsonMap["phone"].(string), jsonMap["email"].(string))
|
||||
if registered {
|
||||
utils.Success(c, 200, 200, "Registered", nil)
|
||||
}
|
||||
colMap := models.MapJ2c[models.BackUser](jsonMap, true)
|
||||
user.Register(colMap)
|
||||
}
|
||||
|
||||
//UserApproveHandler admin approve account
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary admin approve account, user can use account after approved
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/approve [post]
|
||||
// @Param Token header string true "token"
|
||||
// @Param json body models.UserApprove true "json"
|
||||
func UserApproveHandler(c *gin.Context) {
|
||||
//TODO: auth user is admin or not
|
||||
jsonMap := bindJsonStruct[models.UserApprove](c)
|
||||
if jsonMap == nil {
|
||||
return
|
||||
}
|
||||
if !user.ApproveRegister(jsonMap["email"].(string), jsonMap["pass"].(bool)) {
|
||||
RequestErr(c, "approve failed")
|
||||
return
|
||||
}
|
||||
utils.Succ(c, nil)
|
||||
}
|
||||
|
||||
//UserLoginHandler admin approve account
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary user login
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/login [post]
|
||||
// @Param json body models.UserLogin true "json"
|
||||
func UserLoginHandler(c *gin.Context) {
|
||||
jsonMap := bindJsonStruct[models.UserLogin](c)
|
||||
if jsonMap == nil {
|
||||
return
|
||||
}
|
||||
token := user.Login(jsonMap)
|
||||
if token == "" {
|
||||
// TODO: change to request error
|
||||
utils.Succ(c, map[string]interface{}{"msg": "failed"})
|
||||
return
|
||||
}
|
||||
c.Writer.Header().Set("X-Token", token)
|
||||
utils.Succ(c, nil)
|
||||
}
|
||||
|
||||
//ListRegisterUserHandler list register infos
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Summary list register infos, which is to be approved
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/registers [get]
|
||||
// @Param Token header string true "token"
|
||||
func ListRegisterUserHandler(c *gin.Context) {
|
||||
registers := user.ListRegister()
|
||||
utils.Succ(c, registers)
|
||||
}
|
||||
|
||||
//SendEmailCodeHandler send verify code
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Summary send verify code
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/{code} [get]
|
||||
// @Param email path string true "email"
|
||||
func SendEmailCodeHandler(c *gin.Context) {
|
||||
email := c.Param("email")
|
||||
match, _ := regexp.Match("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", []byte(email))
|
||||
if !match {
|
||||
RequestErr(c, map[string]interface{}{"email": email})
|
||||
return
|
||||
}
|
||||
if ok := user.SendEmailCode(email); !ok {
|
||||
ServerErr(c, "Send Email Failed")
|
||||
return
|
||||
}
|
||||
utils.Succ(c, nil)
|
||||
}
|
||||
|
||||
//VerifyEmailCodeHandler verify code
|
||||
// @Tags User
|
||||
// @Produce json
|
||||
// @Summary send verify code
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/code/{email}/{code} [get]
|
||||
// @Param email path string true "email"
|
||||
// @Param code path string true "code"
|
||||
func VerifyEmailCodeHandler(c *gin.Context) {
|
||||
email := c.Param("email")
|
||||
code := c.Param("code")
|
||||
emailMatch, _ := regexp.Match("^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$", []byte(email))
|
||||
codeMatch, _ := regexp.Match("^[\\w]{6}$", []byte(code))
|
||||
if !codeMatch || !emailMatch {
|
||||
RequestErr(c, map[string]interface{}{"email": email, "code": code})
|
||||
return
|
||||
}
|
||||
utils.Succ(c, user.VerifyEmailCode(email, code))
|
||||
}
|
||||
|
||||
//ChangePasswordHandler change user's password
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Summary change user's password
|
||||
// @Success 200 {object} utils.GinResponse{}
|
||||
// @Router /user/chpwd [post]
|
||||
// @Param json body models.UserChangePwd true "json"
|
||||
func ChangePasswordHandler(c *gin.Context) {
|
||||
jsonMap := bindJsonStruct[models.UserChangePwd](c)
|
||||
if jsonMap == nil {
|
||||
return
|
||||
}
|
||||
utils.Succ(c, map[string]interface{}{"success": user.ChangePassword(jsonMap)})
|
||||
}
|
||||
48
handler/utils.go
Normal file
48
handler/utils.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// bindJson bind body as a map
|
||||
func bindJson(c *gin.Context) map[string]interface{} {
|
||||
var jsonMap map[string]interface{}
|
||||
err := c.ShouldBindJSON(&jsonMap)
|
||||
if err != nil {
|
||||
var requestBody []byte
|
||||
_, err := c.Request.Body.Read(requestBody)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RequestErr(c, requestBody)
|
||||
return nil
|
||||
}
|
||||
if jsonMap == nil || len(jsonMap) == 0 {
|
||||
RequestErr(c, map[string]interface{}{"Body": nil})
|
||||
return nil
|
||||
}
|
||||
return jsonMap
|
||||
}
|
||||
|
||||
// bindJsonStruct bind json as a struct, and convert to map
|
||||
func bindJsonStruct[T any](c *gin.Context) map[string]interface{} {
|
||||
var bindObj T
|
||||
err := c.ShouldBind(&bindObj)
|
||||
if err != nil {
|
||||
var requestBody []byte
|
||||
_, err := c.Request.Body.Read(requestBody)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
RequestErr(c, requestBody)
|
||||
return nil
|
||||
}
|
||||
jsonStr, _ := json.Marshal(bindObj)
|
||||
var jsonMap map[string]interface{}
|
||||
_ = json.Unmarshal(jsonStr, &jsonMap)
|
||||
if jsonMap == nil || len(jsonMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
return jsonMap
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func initLogger() {
|
||||
zapConfig := zap.NewProductionConfig()
|
||||
zapConfig.OutputPaths = []string{
|
||||
fmt.Sprintf("%slog_%s.log", global.ServerSettings.LogPath, utils.FormateDate(time.Now())),
|
||||
fmt.Sprintf("%slog_%s.log", global.ServerSettings.LogPath, utils.FormatDate(time.Now())),
|
||||
"stdout",
|
||||
}
|
||||
logger, err := zapConfig.Build()
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"nCovTrack-Backend/global"
|
||||
"nCovTrack-Backend/utils"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const UNAUTH_MSG = "unauthorized"
|
||||
@@ -17,8 +22,15 @@ func Auth() gin.HandlerFunc {
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
// Write the field of token to request header
|
||||
claims := utils.ParseClaims(oldToken[0])
|
||||
c.Request.Header.Set("role", fmt.Sprint(claims["role"]))
|
||||
c.Request.Header.Set("email", claims["email"].(string))
|
||||
c.Request.Header.Set("id", fmt.Sprint(claims["id"]))
|
||||
|
||||
// renew token, and judge the token's iat is expired or not
|
||||
renewToken := utils.RenewToken(oldToken[0])
|
||||
if renewToken == "" {
|
||||
if renewToken == "" || !validAccountIssue(claims) {
|
||||
utils.Err(c, http.StatusUnauthorized, http.StatusUnauthorized, UNAUTH_MSG)
|
||||
c.Abort()
|
||||
return
|
||||
@@ -27,3 +39,23 @@ func Auth() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// validAccountIssue validate token is valid or not
|
||||
// If user change password, or logoff on all password, we need to judge use's token is valid?
|
||||
// Due to the token is no status, so we need to record something on the server-end.
|
||||
// We use the "IssueAt" field of token, to judge token expired or not.
|
||||
// TODO: Move this to jwt utils
|
||||
func validAccountIssue(claims jwt.MapClaims) bool {
|
||||
iafStr := global.Redis.HGet(global.CHANGEPWD_REDIS_KEY, claims["email"].(string)).Val()
|
||||
if iafStr == "" {
|
||||
return true
|
||||
}
|
||||
iaf, _ := strconv.Atoi(iafStr)
|
||||
// Due to we allow token renew, although it was expired, so the token validity period will more than token's validity period
|
||||
tokenMaxValidSeconds := (global.TOKEN_EXPIRE_DAYS + global.ServerSettings.Jwt.RenewExpireDays) * 24 * 60 * 60
|
||||
if time.Now().Unix()-int64(iaf) > int64(tokenMaxValidSeconds) {
|
||||
global.Redis.HDel(global.CHANGEPWD_REDIS_KEY, claims["email"].(string))
|
||||
return true
|
||||
}
|
||||
return int(claims["iat"].(float64)) > iaf
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// BackArticle article struct
|
||||
type BackArticle struct {
|
||||
ID int `gorm:"primaryKey;column:id" json:"id"` // 文章id
|
||||
ID int `gorm:"primaryKey;column:id" json:"-"` // 文章id
|
||||
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 文章新建时间
|
||||
CreateUser string `gorm:"column:create_user" json:"createUser"` // 文章创建者id
|
||||
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 文章最后更新时间
|
||||
@@ -13,4 +16,20 @@ type BackArticle struct {
|
||||
Resume string `gorm:"column:resume" json:"resume"` // 文章简述
|
||||
Cover string `gorm:"column:cover" json:"cover"` // 文章封面
|
||||
Content string `gorm:"column:content" json:"content"` // 文章内容(如有需要可迁移至对象存储)
|
||||
IsPublish int8 `gorm:"column:is_publish" json:"isPublish"` // 发布状态(0:未发布, 1: 发布)
|
||||
IsDelete int8 `gorm:"column:is_delete" json:"isDelete"` // 删除标志
|
||||
}
|
||||
|
||||
func init() {
|
||||
initJcMap[BackArticle]()
|
||||
}
|
||||
|
||||
//func ArticleMapJ2c(jsonMap map[string]interface{}, ignoreNil bool) map[string]interface{} {
|
||||
// colMap := make(map[string]interface{})
|
||||
// for k, v := range jsonMap {
|
||||
// if colKey := colKey != "" && (!ignoreNil && v == nil) {
|
||||
// colMap[colKey] = v
|
||||
// }
|
||||
// }
|
||||
// return colMap
|
||||
//}
|
||||
|
||||
@@ -3,13 +3,43 @@ package models
|
||||
import "time"
|
||||
|
||||
type BackUser struct {
|
||||
ID int `gorm:"primaryKey;column:id" json:"-"` // 用户ID
|
||||
Username string `gorm:"column:username" json:"username"` // 用户真实姓名
|
||||
Password string `gorm:"column:password" json:"password"` // 用户密码
|
||||
Role int `gorm:"column:role" json:"role"` // 用户角色
|
||||
Email string `gorm:"unique;column:email" json:"email"` // 用户邮箱
|
||||
Phone string `gorm:"unique;column:phone" json:"phone"` // 用户手机号码
|
||||
Aptitude string `gorm:"column:aptitude" json:"aptitude"` // 用户资质证明(图片URL)
|
||||
RegisterTime time.Time `gorm:"column:register_time" json:"registerTime"` // 用户注册时间
|
||||
Approver int `gorm:"column:approver" json:"approver"` // 注册审核人ID
|
||||
ID int `gorm:"primaryKey;column:id" json:"-"` // 用户ID
|
||||
Username string `gorm:"column:username" json:"username"` // 用户真实姓名
|
||||
Password string `gorm:"column:password" json:"password"` // 用户密码
|
||||
Role int `gorm:"column:role" json:"role"` // 用户角色
|
||||
Email string `gorm:"column:email" json:"email"` // 用户邮箱
|
||||
Phone string `gorm:"column:phone" json:"phone"` // 用户手机号码
|
||||
Aptitude string `gorm:"column:aptitude" json:"aptitude"` // 用户资质证明(图片URL)
|
||||
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 用户注册时间
|
||||
Approver int `gorm:"column:approver" json:"approver"` // 注册审核人ID
|
||||
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"`
|
||||
IsDelete int8 `gorm:"column:is_delete" json:"isDelete"` // 删除标志
|
||||
}
|
||||
|
||||
type UserLogin struct {
|
||||
Account string `json:"account"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type UserRegister struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Aptitude string `json:"aptitude"`
|
||||
}
|
||||
|
||||
type UserChangePwd struct {
|
||||
Email string `json:"email"`
|
||||
Code string `json:"code"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
}
|
||||
|
||||
type UserApprove struct {
|
||||
Email string `json:"email"`
|
||||
Pass bool `json:"pass"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
initJcMap[BackUser]()
|
||||
}
|
||||
|
||||
185
models/utils.go
185
models/utils.go
@@ -1,14 +1,181 @@
|
||||
package models
|
||||
|
||||
type UtilRequestInfo struct {
|
||||
Url string
|
||||
Header string
|
||||
Body string
|
||||
Timeout int
|
||||
import (
|
||||
"fmt"
|
||||
"gorm.io/gorm"
|
||||
"nCovTrack-Backend/global"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var colNameReg, _ = regexp.Compile(".*column:(.*);?")
|
||||
var j2cMap = make(map[string]map[string]string)
|
||||
var c2jMap = make(map[string]map[string]string)
|
||||
|
||||
const IS_DELETE = "is_delete"
|
||||
|
||||
// initJcMap the gorm models need to call this function in init function
|
||||
func initJcMap[T any]() {
|
||||
t := reflect.TypeOf(new(T)).Elem()
|
||||
tJ2cMap, tC2jMap := make(map[string]string), make(map[string]string)
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
colName := columnName(t.Field(i).Tag.Get("gorm"))
|
||||
// TODO: Deal with (-)
|
||||
jsonName := t.Field(i).Tag.Get("json")
|
||||
if colName == "" || jsonName == "" {
|
||||
continue
|
||||
}
|
||||
tJ2cMap[jsonName] = colName
|
||||
tC2jMap[colName] = jsonName
|
||||
}
|
||||
j2cMap[t.Name()] = tJ2cMap
|
||||
c2jMap[t.Name()] = tC2jMap
|
||||
}
|
||||
|
||||
type GinResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg interface{} `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
// columnName get the mysql column name of the tag
|
||||
func columnName(gormTag string) string {
|
||||
colNames := colNameReg.FindSubmatch([]byte(gormTag))
|
||||
if len(colNames) != 2 {
|
||||
panic("Model tag regex error")
|
||||
}
|
||||
return string(colNames[1])
|
||||
}
|
||||
|
||||
// MapJ2c convert jsonMap to colMap, which will used by gorm
|
||||
func MapJ2c[T any](jsonMap map[string]interface{}, ignoreNil bool) (colMap map[string]interface{}) {
|
||||
tName := reflect.TypeOf(new(T)).Elem().Name()
|
||||
tJ2cMap := j2cMap[tName]
|
||||
if tJ2cMap == nil {
|
||||
panic(tName + " is not init registered int j2cMap")
|
||||
}
|
||||
|
||||
colMap = make(map[string]interface{})
|
||||
for k, v := range jsonMap {
|
||||
//TODO 无法转换
|
||||
if colName := tJ2cMap[k]; colName != "" && (!ignoreNil || v != nil) {
|
||||
colMap[colName] = v
|
||||
}
|
||||
}
|
||||
return colMap
|
||||
}
|
||||
|
||||
// BeforeSave need to set some field while insert or update
|
||||
func BeforeSave(colMap map[string]interface{}, user int) {
|
||||
if colMap["id"] == nil {
|
||||
colMap["create_time"] = time.Now()
|
||||
if user != -1 {
|
||||
colMap["create_user"] = user
|
||||
}
|
||||
}
|
||||
colMap["modify_time"] = time.Now()
|
||||
if user != -1 {
|
||||
colMap["modify_user"] = user
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------<Gorm functions>-----------------------------------------------------------*/
|
||||
// Due to gorm can't deal with the zero value, so we use gorm with map.
|
||||
// The generic will make the function is generally used to gorm models
|
||||
// TODO: add uniqueKey map, which can be used when Upsert
|
||||
|
||||
func Upsert[T any](colMap map[string]interface{}) (ok bool, rowsAffected int64) {
|
||||
var tx *gorm.DB
|
||||
if colMap["id"] == nil {
|
||||
tx = global.Db.Model(new(T)).Create(colMap)
|
||||
} else {
|
||||
tx = global.Db.Model(new(T)).Where("id = ?", colMap["id"]).Updates(colMap)
|
||||
}
|
||||
if tx.Error != nil {
|
||||
fmt.Println(tx.Error)
|
||||
return false, 0
|
||||
}
|
||||
return true, tx.RowsAffected
|
||||
}
|
||||
|
||||
// DeleteById will delete by id, not delete the record from database, only set the field "is_delete" as 1
|
||||
func DeleteById[T any](id int) (ok bool, rowsAffected int64) {
|
||||
tx := global.Db.Model(new(T)).Where("id = ?", id).Update("is_delete", 1)
|
||||
if tx.Error != nil {
|
||||
return false, 0
|
||||
}
|
||||
return true, rowsAffected
|
||||
}
|
||||
|
||||
func List[T any](queryMap []map[string]interface{}) *[]map[string]interface{} {
|
||||
tx := global.Db.Model(new(T))
|
||||
for _, e := range queryMap {
|
||||
e[IS_DELETE] = 0
|
||||
tx = tx.Or(e)
|
||||
}
|
||||
return ListByOrm(tx)
|
||||
}
|
||||
|
||||
func ListField[T any](queryMap []map[string]interface{}, isOmit bool, queryField ...string) *[]map[string]interface{} {
|
||||
tx := global.Db.Model(new(T))
|
||||
for _, e := range queryMap {
|
||||
e[IS_DELETE] = 0
|
||||
tx = tx.Or(e)
|
||||
}
|
||||
if len(queryMap) == 0 {
|
||||
return ListByOrm(tx)
|
||||
}
|
||||
if isOmit {
|
||||
tx = tx.Omit(queryField...)
|
||||
} else {
|
||||
tx = tx.Select(queryField[0], queryField[1:])
|
||||
}
|
||||
return ListByOrm(tx)
|
||||
}
|
||||
|
||||
func ListByOrm(tx *gorm.DB) *[]map[string]interface{} {
|
||||
var res []map[string]interface{}
|
||||
tx.Find(&res)
|
||||
return &res
|
||||
}
|
||||
func Get[T any](queryMap []map[string]interface{}) map[string]interface{} {
|
||||
tx := global.Db.Model(new(T))
|
||||
for _, e := range queryMap {
|
||||
e[IS_DELETE] = 0
|
||||
tx = tx.Or(e)
|
||||
}
|
||||
return GetByOrm(tx)
|
||||
}
|
||||
|
||||
func GetField[T any](queryMap []map[string]interface{}, isOmit bool, queryField ...string) map[string]interface{} {
|
||||
tx := global.Db.Model(new(T))
|
||||
for _, e := range queryMap {
|
||||
e[IS_DELETE] = 0
|
||||
tx = tx.Or(e)
|
||||
}
|
||||
if len(queryMap) == 0 {
|
||||
return GetByOrm(tx)
|
||||
}
|
||||
if isOmit {
|
||||
tx = tx.Omit(queryField...)
|
||||
} else {
|
||||
tx = tx.Select(queryField[0], queryField[1:])
|
||||
}
|
||||
return GetByOrm(tx)
|
||||
}
|
||||
|
||||
func GetByOrm(tx *gorm.DB) map[string]interface{} {
|
||||
var res map[string]interface{}
|
||||
tx.Limit(1).Find(&res)
|
||||
return res
|
||||
}
|
||||
|
||||
func Count[T any](queryMap []map[string]interface{}) int64 {
|
||||
tx := global.Db.Model(new(T))
|
||||
for _, e := range queryMap {
|
||||
e[IS_DELETE] = 0
|
||||
tx = tx.Or(e)
|
||||
}
|
||||
return CountByOrm(tx)
|
||||
}
|
||||
|
||||
func CountByOrm(tx *gorm.DB) int64 {
|
||||
var count int64
|
||||
tx.Count(&count)
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -8,15 +8,16 @@ import (
|
||||
func articlePrivateRouter(router *gin.RouterGroup) {
|
||||
articleRouter := router.Group("/article")
|
||||
{
|
||||
articleRouter.POST("/:id", handler.SaveArticleHandler)
|
||||
articleRouter.DELETE("/:id", handler.DeleteArticleHandler)
|
||||
articleRouter.POST("/:id/publish", handler.PublishArticleHandler)
|
||||
}
|
||||
}
|
||||
|
||||
func articlePublicRouter(router *gin.RouterGroup) {
|
||||
articleRouter := router.Group("/article")
|
||||
{
|
||||
articleRouter.POST("", handler.SaveArticleHandler)
|
||||
articleRouter.GET("/list", handler.GetAllArticlesHandler)
|
||||
articleRouter.DELETE("/:id", handler.DeleteArticleHandler)
|
||||
articleRouter.GET("/:id", handler.GetArticleHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,12 @@ func BusiRouter() {
|
||||
{
|
||||
statisticRouter(publicRouter)
|
||||
articlePublicRouter(publicRouter)
|
||||
userPublicRouter(publicRouter)
|
||||
}
|
||||
|
||||
// Private
|
||||
{
|
||||
articlePrivateRouter(privateRouter)
|
||||
userPrivateRouter(privateRouter)
|
||||
}
|
||||
}
|
||||
|
||||
24
router/user.go
Normal file
24
router/user.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"nCovTrack-Backend/handler"
|
||||
)
|
||||
|
||||
func userPublicRouter(router *gin.RouterGroup) {
|
||||
userRouter := router.Group("/user")
|
||||
{
|
||||
userRouter.POST("/register", handler.UserRegisterHandler)
|
||||
userRouter.POST("/login", handler.UserLoginHandler)
|
||||
userRouter.GET("/code/:email", handler.SendEmailCodeHandler)
|
||||
userRouter.GET("/code/:email/:code", handler.VerifyEmailCodeHandler)
|
||||
userRouter.POST("/chpwd", handler.ChangePasswordHandler)
|
||||
}
|
||||
}
|
||||
func userPrivateRouter(router *gin.RouterGroup) {
|
||||
userRouter := router.Group("/user")
|
||||
{
|
||||
userRouter.POST("/approve", handler.UserApproveHandler)
|
||||
userRouter.GET("/registers", handler.ListRegisterUserHandler)
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,49 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"nCovTrack-Backend/global"
|
||||
"nCovTrack-Backend/models"
|
||||
"nCovTrack-Backend/utils"
|
||||
)
|
||||
|
||||
func GetArticleList() *[]models.BackArticle {
|
||||
var articles []models.BackArticle
|
||||
global.Db.Omit("content").Find(&articles)
|
||||
return &articles
|
||||
//ListPublishedArticles list the articles published, use to show the articles to all people
|
||||
func ListPublishedArticles() *[]map[string]interface{} {
|
||||
article := models.ListField[models.BackArticle]([]map[string]interface{}{{"is_publish": 0}}, true, "content")
|
||||
if *article == nil {
|
||||
article = &[]map[string]interface{}{}
|
||||
}
|
||||
return article
|
||||
}
|
||||
|
||||
func SaveArticle(article *models.BackArticle) (ok bool) {
|
||||
return utils.Upsert(article)
|
||||
//ListAllArticles list all articles, will show the articles not published of the user
|
||||
// TODO: need only show the user's not published article
|
||||
func ListAllArticles() *[]map[string]interface{} {
|
||||
article := models.ListField[models.BackArticle]([]map[string]interface{}{{}}, true, "content")
|
||||
if *article == nil {
|
||||
article = &[]map[string]interface{}{}
|
||||
}
|
||||
return article
|
||||
}
|
||||
|
||||
//SaveArticle save the articles
|
||||
func SaveArticle(article map[string]interface{}) (ok bool) {
|
||||
models.BeforeSave(article, -1)
|
||||
ok, rows := models.Upsert[models.BackArticle](article)
|
||||
return ok && rows != 0
|
||||
}
|
||||
|
||||
//DeleteArticle delete article by id
|
||||
func DeleteArticle(id int) (ok bool) {
|
||||
tx := global.Db.Delete(&models.BackArticle{}, id)
|
||||
if tx.Error != nil {
|
||||
panic(tx.Error)
|
||||
}
|
||||
if tx.RowsAffected == 0 {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
ok, rowsAffected := models.DeleteById[models.BackArticle](id)
|
||||
return ok && rowsAffected != 0
|
||||
}
|
||||
|
||||
func GetArticleById(id int) *models.BackArticle {
|
||||
var article models.BackArticle
|
||||
tx := global.Db.Limit(1).Find(&article, id)
|
||||
if tx.RowsAffected == 0 {
|
||||
return nil
|
||||
}
|
||||
return &article
|
||||
//GetArticleById get an article
|
||||
func GetArticleById(id int) map[string]interface{} {
|
||||
return models.Get[models.BackArticle]([]map[string]interface{}{{"id": id}})
|
||||
}
|
||||
|
||||
//PublishArticle publish an article
|
||||
func PublishArticle(id int) (ok bool) {
|
||||
colMap := map[string]interface{}{"id": id, "is_publish": 1}
|
||||
ok, rowsAffected := models.Upsert[models.BackArticle](colMap)
|
||||
return ok && rowsAffected != 0
|
||||
}
|
||||
|
||||
140
service/user/user.go
Normal file
140
service/user/user.go
Normal file
@@ -0,0 +1,140 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
"nCovTrack-Backend/global"
|
||||
"nCovTrack-Backend/models"
|
||||
"nCovTrack-Backend/utils"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
EMAIL_CODE_REDIS_KEY = "verify_code_"
|
||||
)
|
||||
|
||||
// Login if login success, will return token
|
||||
func Login(user map[string]interface{}) (token string) {
|
||||
account := user["account"].(string)
|
||||
var queryMap []map[string]interface{}
|
||||
if strings.Contains(account, "@") {
|
||||
queryMap = append(queryMap, map[string]interface{}{"email": account})
|
||||
} else {
|
||||
queryMap = append(queryMap, map[string]interface{}{"phone": account})
|
||||
}
|
||||
userInfo := models.Get[models.BackUser](queryMap)
|
||||
if userInfo == nil {
|
||||
return ""
|
||||
}
|
||||
if !utils.PasswordCompare(user["password"].(string), userInfo["password"].(string)) {
|
||||
return ""
|
||||
}
|
||||
claims := jwt.MapClaims{
|
||||
"id": userInfo["id"],
|
||||
"username": userInfo["username"],
|
||||
"role": userInfo["role"],
|
||||
"email": userInfo["email"],
|
||||
}
|
||||
return utils.GenerateToken(claims)
|
||||
}
|
||||
|
||||
// Register user register, user can use account after approved
|
||||
func Register(user map[string]interface{}) {
|
||||
user["password"] = utils.PasswordEncrypt(user["password"].(string))
|
||||
userStr, _ := json.Marshal(user)
|
||||
// insert into redis, wait for approve
|
||||
cmd := global.Redis.HMSet(global.REGISTER_REDIS_KEY, map[string]interface{}{user["email"].(string): userStr})
|
||||
if cmd.Err() != nil {
|
||||
panic(cmd.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// ListRegister list the registers in the redis to be approved
|
||||
func ListRegister() *[]map[string]interface{} {
|
||||
applyStrMap := global.Redis.HGetAll(global.REGISTER_REDIS_KEY).Val()
|
||||
var applies []map[string]interface{}
|
||||
for _, v := range applyStrMap {
|
||||
var apply map[string]interface{}
|
||||
_ = json.Unmarshal([]byte(v), &apply)
|
||||
applies = append(applies, apply)
|
||||
}
|
||||
if applies == nil {
|
||||
applies = []map[string]interface{}{}
|
||||
}
|
||||
return &applies
|
||||
}
|
||||
|
||||
// ApproveRegister approve a register
|
||||
func ApproveRegister(email string, pass bool) bool {
|
||||
if !pass {
|
||||
rowsAffected := global.Redis.HDel(global.REGISTER_REDIS_KEY, email).Val()
|
||||
return rowsAffected != 0
|
||||
}
|
||||
// if pass, will get the register info from redis, and the insert into mysql, this mean user is register success
|
||||
applyStr := global.Redis.HGet(global.REGISTER_REDIS_KEY, email).Val()
|
||||
rowsAffected := global.Redis.HDel(global.REGISTER_REDIS_KEY, email).Val()
|
||||
if rowsAffected == 0 {
|
||||
return false
|
||||
}
|
||||
var apply map[string]interface{}
|
||||
_ = json.Unmarshal([]byte(applyStr), &apply)
|
||||
if !NoDuplicatePhoneOrEmail(apply["phone"].(string), apply["email"].(string)) {
|
||||
return false
|
||||
}
|
||||
colMap := models.MapJ2c[models.BackUser](apply, true)
|
||||
ok, rowsAffected := models.Upsert[models.BackUser](colMap)
|
||||
return ok && rowsAffected != 0
|
||||
}
|
||||
|
||||
// ChangePassword user change password, or user forgot password
|
||||
func ChangePassword(changePwd map[string]interface{}) bool {
|
||||
match := VerifyEmailCode(changePwd["email"].(string), changePwd["code"].(string))["match"].(bool)
|
||||
if !match {
|
||||
return false
|
||||
}
|
||||
newPassword := utils.PasswordEncrypt(changePwd["newPassword"].(string))
|
||||
colMap := map[string]interface{}{
|
||||
"id": 1,
|
||||
"password": newPassword,
|
||||
}
|
||||
models.BeforeSave(colMap, -1)
|
||||
delete(colMap, "id")
|
||||
rowAffected := global.Db.Model(models.BackUser{}).Where("email = ?", changePwd["email"]).Updates(colMap).RowsAffected
|
||||
if rowAffected == 0 {
|
||||
return false
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
global.Redis.HSet(global.CHANGEPWD_REDIS_KEY, changePwd["email"].(string), now)
|
||||
return true
|
||||
}
|
||||
|
||||
// NoDuplicatePhoneOrEmail detect the phone or email is registered or not
|
||||
func NoDuplicatePhoneOrEmail(phone string, email string) bool {
|
||||
var queryMap []map[string]interface{}
|
||||
if phone != "" {
|
||||
queryMap = append(queryMap, map[string]interface{}{"phone": phone})
|
||||
}
|
||||
if email != "" {
|
||||
queryMap = append(queryMap, map[string]interface{}{"email": email})
|
||||
}
|
||||
return len(queryMap) != 0 && models.Count[models.BackUser](queryMap) == 0
|
||||
}
|
||||
|
||||
// SendEmailCode used to send email verify code
|
||||
func SendEmailCode(email string) bool {
|
||||
code := uuid.New().String()[0:6]
|
||||
text := fmt.Sprintf("Your Verify Code is :%s, Will Expire After 10 Minutes", code)
|
||||
subject := "nCovTrack Verify"
|
||||
// only set expired, not limit the frequency of use
|
||||
global.Redis.Set(EMAIL_CODE_REDIS_KEY+email, code, 10*time.Minute)
|
||||
return utils.SendEmail(subject, text, email)
|
||||
}
|
||||
|
||||
// VerifyEmailCode use to verify user's verify code is correct or not
|
||||
func VerifyEmailCode(email string, code string) map[string]interface{} {
|
||||
verifyCode := global.Redis.Get(EMAIL_CODE_REDIS_KEY + email).Val()
|
||||
return map[string]interface{}{"match": verifyCode == code}
|
||||
}
|
||||
@@ -21,3 +21,9 @@ jwt:
|
||||
secret: bWF5YmVJYWxzb3NhbWV0b2JlZm9yZe
|
||||
renewExpireDays: 7
|
||||
renewAheadDays: 3
|
||||
|
||||
email:
|
||||
host: smtp.qq.com
|
||||
port: 587
|
||||
account: fallen-angle@foxmail.com
|
||||
password: hxrisxltxsjvieec
|
||||
26
utils/email.go
Normal file
26
utils/email.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/jordan-wright/email"
|
||||
"nCovTrack-Backend/global"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
func SendEmail(subject string, text string, to ...string) bool {
|
||||
//TODO: add logs
|
||||
e := email.Email{
|
||||
From: "nCovTrack Server<1853633282@qq.com>",
|
||||
To: to,
|
||||
Subject: subject,
|
||||
Text: []byte(text),
|
||||
}
|
||||
err := e.Send(
|
||||
fmt.Sprintf("%s:%d", global.ServerSettings.Email.Host, global.ServerSettings.Email.Port),
|
||||
smtp.PlainAuth("", global.ServerSettings.Email.Account, global.ServerSettings.Email.Password, global.ServerSettings.Email.Host),
|
||||
)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
23
utils/encrypt.go
Normal file
23
utils/encrypt.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
func PasswordEncrypt(password string) string {
|
||||
salt := uuid.New().String()[24:]
|
||||
password = password + salt
|
||||
shaRes := hex.EncodeToString(sha256.New().Sum([]byte(password)))
|
||||
encryptPwd := fmt.Sprintf("ncov$%s$%s", shaRes[0:24], salt)
|
||||
return encryptPwd
|
||||
}
|
||||
|
||||
func PasswordCompare(plaintext string, ciphertext string) (ok bool) {
|
||||
salt := ciphertext[30:]
|
||||
password := plaintext + salt
|
||||
shaRes := hex.EncodeToString(sha256.New().Sum([]byte(password)))
|
||||
return shaRes[0:24] == ciphertext[5:29]
|
||||
}
|
||||
17
utils/jwt.go
17
utils/jwt.go
@@ -58,7 +58,7 @@ func RenewToken(tokenStr string) string {
|
||||
}
|
||||
fmt.Println(expireDuration)
|
||||
|
||||
claims["exp"] = time.Now().Add(15 * 24 * time.Hour).Unix()
|
||||
claims["exp"] = time.Now().Add(global.TOKEN_EXPIRE_DAYS * 24 * time.Hour).Unix()
|
||||
token = jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
tokenStr, err = token.SignedString(JWT_KEY)
|
||||
if err != nil {
|
||||
@@ -66,3 +66,18 @@ func RenewToken(tokenStr string) string {
|
||||
}
|
||||
return tokenStr
|
||||
}
|
||||
|
||||
func ParseClaims(tokenStr string) jwt.MapClaims {
|
||||
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (interface{}, error) {
|
||||
return JWT_KEY, nil
|
||||
})
|
||||
if err != nil {
|
||||
switch err.(*jwt.ValidationError).Errors {
|
||||
case jwt.ValidationErrorSignatureInvalid:
|
||||
return nil
|
||||
case jwt.ValidationErrorIssuedAt:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return token.Claims.(jwt.MapClaims)
|
||||
}
|
||||
|
||||
14
utils/models.go
Normal file
14
utils/models.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
type UtilRequestInfo struct {
|
||||
Url string
|
||||
Header string
|
||||
Body string
|
||||
Timeout int
|
||||
}
|
||||
|
||||
type GinResponse struct {
|
||||
Code int `json:"code"`
|
||||
Msg interface{} `json:"msg"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
67
utils/orm.go
67
utils/orm.go
@@ -1,67 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"gorm.io/gorm/clause"
|
||||
"nCovTrack-Backend/global"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
var colNameReg, _ = regexp.Compile(".*column:(.*);?")
|
||||
var uniqueKeyReg, _ = regexp.Compile(".*(primaryKey|unique).*")
|
||||
|
||||
func getNotZeroFields[T any](model T) []string {
|
||||
t := reflect.TypeOf(model)
|
||||
v := reflect.ValueOf(model)
|
||||
var notZeroFields []string
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
if !v.Field(i).IsZero() {
|
||||
colName := colNameReg.FindSubmatch([]byte(t.Field(i).Tag.Get("gorm")))
|
||||
if len(colName) != 2 {
|
||||
panic("Model Tag regex error")
|
||||
}
|
||||
notZeroFields = append(notZeroFields, string(colName[1]))
|
||||
}
|
||||
}
|
||||
return notZeroFields
|
||||
}
|
||||
|
||||
func Upsert[T any](model *T, forceUpdateFiled ...string) (ok bool) {
|
||||
t := reflect.TypeOf(model).Elem()
|
||||
v := reflect.ValueOf(model).Elem()
|
||||
var uniqueKeyField []clause.Column
|
||||
notZeroField := NewSet(forceUpdateFiled...).Add("modify_time")
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
gormTag := t.Field(i).Tag.Get("gorm")
|
||||
if uniqueKey(gormTag) {
|
||||
uniqueKeyField = append(uniqueKeyField, clause.Column{Name: columnName(gormTag)})
|
||||
continue
|
||||
}
|
||||
if !v.Field(i).IsZero() {
|
||||
notZeroField.Add(columnName(gormTag))
|
||||
} else if t.Field(i).Type.Name() == "Time" {
|
||||
v.Field(i).Set(reflect.ValueOf(time.Now()))
|
||||
}
|
||||
}
|
||||
tx := global.Db.Clauses(clause.OnConflict{
|
||||
Columns: uniqueKeyField,
|
||||
DoUpdates: clause.AssignmentColumns(notZeroField.ToSlice()),
|
||||
}, clause.Returning{}).Create(model)
|
||||
if tx.Error != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func columnName(gormTag string) string {
|
||||
colNames := colNameReg.FindSubmatch([]byte(gormTag))
|
||||
if len(colNames) != 2 {
|
||||
panic("Model tag regex error")
|
||||
}
|
||||
return string(colNames[1])
|
||||
}
|
||||
|
||||
func uniqueKey(gormTag string) bool {
|
||||
return uniqueKeyReg.Match([]byte(gormTag))
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package utils
|
||||
@@ -1,51 +1,26 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"nCovTrack-Backend/models"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
SUCCESS = "Success"
|
||||
BAD_REQUEST = "Bad Request"
|
||||
DATA_NOT_FOUND = "Data not Found"
|
||||
STATUS_DATA_NOT_FOUND = 210
|
||||
SUCCESS = "Success"
|
||||
)
|
||||
|
||||
func Success(c *gin.Context, status int, code int, msg interface{}, data interface{}) {
|
||||
c.JSON(http.StatusOK, models.GinResponse{Code: code, Msg: msg, Data: data})
|
||||
c.JSON(http.StatusOK, GinResponse{Code: code, Msg: msg, Data: data})
|
||||
}
|
||||
|
||||
func Error(c *gin.Context, status int, code int, msg interface{}, data interface{}) {
|
||||
c.JSON(status, models.GinResponse{Code: code, Msg: msg, Data: data})
|
||||
c.JSON(status, GinResponse{Code: code, Msg: msg, Data: data})
|
||||
}
|
||||
|
||||
func Succ(c *gin.Context, data interface{}) {
|
||||
Success(c, http.StatusOK, http.StatusOK, SUCCESS, data)
|
||||
}
|
||||
|
||||
func DataNotFound(c *gin.Context, data interface{}) {
|
||||
Success(c, http.StatusOK, STATUS_DATA_NOT_FOUND, DATA_NOT_FOUND, data)
|
||||
}
|
||||
|
||||
func Err(c *gin.Context, status int, code int, msg interface{}) {
|
||||
Error(c, status, code, msg, nil)
|
||||
}
|
||||
|
||||
func ServerError(c *gin.Context, code int, msg interface{}) {
|
||||
Err(c, http.StatusInternalServerError, code, msg)
|
||||
}
|
||||
|
||||
func ServerErr(c *gin.Context, msg interface{}) {
|
||||
ServerError(c, http.StatusInternalServerError, msg)
|
||||
}
|
||||
|
||||
func RequestError(c *gin.Context, code int, data interface{}) {
|
||||
Error(c, http.StatusBadRequest, code, BAD_REQUEST, data)
|
||||
}
|
||||
|
||||
func RequestErr(c *gin.Context, data interface{}) {
|
||||
RequestError(c, http.StatusBadRequest, data)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package utils
|
||||
|
||||
type void struct{}
|
||||
|
||||
// This is set with generic
|
||||
|
||||
type Set[T comparable] struct {
|
||||
setMap map[T]void
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
package utils
|
||||
@@ -4,6 +4,6 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func FormateDate(date time.Time) string {
|
||||
func FormatDate(date time.Time) string {
|
||||
return date.Format("06-01-02")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user