* feat: Support multi urls matching. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> * fix: Update comments. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> * fix: Update misspelling. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> * feat: Add info api. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> * feat: Add local storage. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> * fix: Update comments. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> * fix: Fix error code. Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com> --------- Signed-off-by: IRONICBo <47499836+IRONICBo@users.noreply.github.com>main
| @@ -38,12 +38,16 @@ var hookTemplate = checkTemplate(` | |||||
| package hooks | package hooks | ||||
| import ( | import ( | ||||
| "fmt" | |||||
| urltrie "github.com/OpenIMSDK/OpenKF/server/internal/middleware/hooks/url_trie" | urltrie "github.com/OpenIMSDK/OpenKF/server/internal/middleware/hooks/url_trie" | ||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| ) | ) | ||||
| var _ urltrie.Hook = (*{{.HookName}})(nil) | |||||
| func init() { | func init() { | ||||
| urltrie.RegisterHook({{.HookName}}{}) | |||||
| urltrie.RegisterHook(&{{.HookName}}{}) | |||||
| fmt.Println("RegisterHook", "Register Hook[{{.HookName}}] success...") | fmt.Println("RegisterHook", "Register Hook[{{.HookName}}] success...") | ||||
| } | } | ||||
| @@ -21,6 +21,7 @@ import ( | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/common/response" | "github.com/OpenIMSDK/OpenKF/server/internal/common/response" | ||||
| requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/service" | "github.com/OpenIMSDK/OpenKF/server/internal/service" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | |||||
| "github.com/OpenIMSDK/OpenKF/server/pkg/log" | "github.com/OpenIMSDK/OpenKF/server/pkg/log" | ||||
| ) | ) | ||||
| @@ -52,3 +53,58 @@ func CreateCommunity(c *gin.Context) { | |||||
| response.Success(c) | response.Success(c) | ||||
| } | } | ||||
| // GetCommunityInfo | |||||
| // @Tags community | |||||
| // @Summary GetCommunityInfo | |||||
| // @Description get community info | |||||
| // @Produce application/json | |||||
| // @Param data body param.GetCommunityInfoParams true "GetCommunityInfoParams" | |||||
| // @Success 200 {object} response.Response{msg=string} "Success" | |||||
| // @Router /api/v1/community/info [post]. | |||||
| func GetCommunityInfo(c *gin.Context) { | |||||
| param := requestparams.GetCommunityInfoParams{} | |||||
| err := c.ShouldBindJSON(¶m) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.INVALID_PARAMS, c) | |||||
| return | |||||
| } | |||||
| svc := service.NewCommunityService(c) | |||||
| resp, err := svc.GetCommunityInfoByUUID(param.UUID) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.KF_RECORD_NOT_FOUND, c) | |||||
| return | |||||
| } | |||||
| response.SuccessWithData(resp, c) | |||||
| } | |||||
| // GetMyCommunityInfo | |||||
| // @Tags community | |||||
| // @Summary GetMyCommunityInfo | |||||
| // @Description get my community info | |||||
| // @Produce application/json | |||||
| // @Param data body param.AccountLogin true "AccountLogin" | |||||
| // @Success 200 {object} response.Response{msg=string} "Success" | |||||
| // @Router /api/v1/community/me [get]. | |||||
| func GetMyCommunityInfo(c *gin.Context) { | |||||
| uuid, err := utils.GetCommunityUUID(c) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.UNAUTHORIZED, c) | |||||
| return | |||||
| } | |||||
| svc := service.NewCommunityService(c) | |||||
| resp, err := svc.GetCommunityInfoByUUID(uuid) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.KF_RECORD_NOT_FOUND, c) | |||||
| return | |||||
| } | |||||
| response.SuccessWithData(resp, c) | |||||
| } | |||||
| @@ -21,6 +21,7 @@ import ( | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/common/response" | "github.com/OpenIMSDK/OpenKF/server/internal/common/response" | ||||
| requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/service" | "github.com/OpenIMSDK/OpenKF/server/internal/service" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | |||||
| "github.com/OpenIMSDK/OpenKF/server/pkg/log" | "github.com/OpenIMSDK/OpenKF/server/pkg/log" | ||||
| ) | ) | ||||
| @@ -110,3 +111,57 @@ func AccountLogin(c *gin.Context) { | |||||
| response.SuccessWithData(u, c) | response.SuccessWithData(u, c) | ||||
| } | } | ||||
| // GetUserInfo | |||||
| // @Tags user | |||||
| // @Summary GetUserInfo | |||||
| // @Description get user info | |||||
| // @Security ApiKeyAuth | |||||
| // Param data body param.GetUserInfoParams true "GetUserInfoParams" | |||||
| // @Success 200 {object} response.Response{msg=string} "Success" | |||||
| // @Router /api/v1/user/info [post]. | |||||
| func GetUserInfo(c *gin.Context) { | |||||
| param := requestparams.GetUserInfoParams{} | |||||
| err := c.ShouldBindJSON(¶m) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.INVALID_PARAMS, c) | |||||
| return | |||||
| } | |||||
| svc := service.NewUserService(c) | |||||
| resp, err := svc.GetUserInfoByUUID(param.UUID) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.KF_RECORD_NOT_FOUND, c) | |||||
| return | |||||
| } | |||||
| response.SuccessWithData(resp, c) | |||||
| } | |||||
| // GetMyInfo | |||||
| // @Tags user | |||||
| // @Summary GetMyInfo | |||||
| // @Description get my user info | |||||
| // @Security ApiKeyAuth | |||||
| // @Success 200 {object} response.Response{msg=string} "Success" | |||||
| // @Router /api/v1/user/me [get]. | |||||
| func GetMyInfo(c *gin.Context) { | |||||
| uuid, err := utils.GetUserUUID(c) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.UNAUTHORIZED, c) | |||||
| return | |||||
| } | |||||
| svc := service.NewUserService(c) | |||||
| resp, err := svc.GetUserInfoByUUID(uuid) | |||||
| if err != nil { | |||||
| response.FailWithCode(common.KF_RECORD_NOT_FOUND, c) | |||||
| return | |||||
| } | |||||
| response.SuccessWithData(resp, c) | |||||
| } | |||||
| @@ -14,13 +14,22 @@ | |||||
| package common | package common | ||||
| // Code. | |||||
| // HTTP response code. | |||||
| const ( | const ( | ||||
| SUCCESS = 200 | SUCCESS = 200 | ||||
| ERROR = 500 | ERROR = 500 | ||||
| INVALID_PARAMS = 400 | INVALID_PARAMS = 400 | ||||
| UNAUTHORIZED = 401 | |||||
| // OpenIM callback code. | // OpenIM callback code. | ||||
| OPENIM_SERVER_ALLOW_ACTION = 0 | OPENIM_SERVER_ALLOW_ACTION = 0 | ||||
| OPENIM_SERVER_DENY_ACTION = 1 | OPENIM_SERVER_DENY_ACTION = 1 | ||||
| // KF service status. | |||||
| KF_RECORD_NOT_FOUND = 10001 | |||||
| ) | |||||
| // KF internal error code. | |||||
| const ( | |||||
| I_INVALID_PARAM = 20000 + iota | |||||
| ) | ) | ||||
| @@ -14,15 +14,26 @@ | |||||
| package common | package common | ||||
| import "github.com/pkg/errors" | |||||
| // msg is a mapping of message. | // msg is a mapping of message. | ||||
| var msg = map[int]string{ | var msg = map[int]string{ | ||||
| SUCCESS: "success", | SUCCESS: "success", | ||||
| ERROR: "error", | ERROR: "error", | ||||
| INVALID_PARAMS: "request params error", | INVALID_PARAMS: "request params error", | ||||
| UNAUTHORIZED: "unauthorized", | |||||
| // OpenIM callback code | // OpenIM callback code | ||||
| OPENIM_SERVER_ALLOW_ACTION: "OpenIM allow action", | OPENIM_SERVER_ALLOW_ACTION: "OpenIM allow action", | ||||
| OPENIM_SERVER_DENY_ACTION: "OpenIM deny action", | OPENIM_SERVER_DENY_ACTION: "OpenIM deny action", | ||||
| // KF service status | |||||
| KF_RECORD_NOT_FOUND: "kf: record not found", | |||||
| } | |||||
| // internalMsg is a mapping of internal service message. | |||||
| var internalMsg = map[int]string{ | |||||
| I_INVALID_PARAM: "invalid param", | |||||
| } | } | ||||
| // GetMsg get the message by code. | // GetMsg get the message by code. | ||||
| @@ -34,3 +45,8 @@ func GetMsg(code int) string { | |||||
| return msg[ERROR] | return msg[ERROR] | ||||
| } | } | ||||
| // NewError return a new error. | |||||
| func NewError(code int) error { | |||||
| return errors.Errorf("%d: %s", code, internalMsg[code]) | |||||
| } | |||||
| @@ -1,39 +0,0 @@ | |||||
| // Copyright © 2023 OpenIM open source community. All rights reserved. | |||||
| // | |||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| // you may not use this file except in compliance with the License. | |||||
| // You may obtain a copy of the License at | |||||
| // | |||||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||||
| // | |||||
| // Unless required by applicable law or agreed to in writing, software | |||||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| // See the License for the specific language governing permissions and | |||||
| // limitations under the License. | |||||
| package middleware | |||||
| import ( | |||||
| "net/http" | |||||
| "github.com/gin-gonic/gin" | |||||
| ) | |||||
| // EnableCROS enable cros. | |||||
| func EnableCROS() gin.HandlerFunc { | |||||
| return func(c *gin.Context) { | |||||
| // c.Writer.Header().Set("Access-Control-Allow-Origin", "*") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Origin", c.GetHeader("origin")) | |||||
| c.Writer.Header().Set("Access-Control-Max-Age", "70000") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE, PATCH") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length,token") | |||||
| c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Headers,token") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") | |||||
| if c.Request.Method == "OPTIONS" { | |||||
| c.AbortWithStatus(http.StatusNoContent) | |||||
| } | |||||
| c.Next() | |||||
| } | |||||
| } | |||||
| @@ -1,72 +0,0 @@ | |||||
| // Copyright © 2023 OpenIM open source community. All rights reserved. | |||||
| // | |||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| // you may not use this file except in compliance with the License. | |||||
| // You may obtain a copy of the License at | |||||
| // | |||||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||||
| // | |||||
| // Unless required by applicable law or agreed to in writing, software | |||||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| // See the License for the specific language governing permissions and | |||||
| // limitations under the License. | |||||
| package middleware | |||||
| import ( | |||||
| "sync" | |||||
| "time" | |||||
| "github.com/gin-gonic/gin" | |||||
| ) | |||||
| // define rate limit struct. | |||||
| type frequencyControlByTokenBucket struct { | |||||
| refreshRate float64 // token refresh rate | |||||
| capacity int64 // bucket capacity | |||||
| tokens float64 // token count | |||||
| lastToken time.Time // latest time token stored | |||||
| mtx sync.Mutex // mutex | |||||
| } | |||||
| // allow frequency. | |||||
| func (tb *frequencyControlByTokenBucket) Allow() bool { | |||||
| tb.mtx.Lock() | |||||
| defer tb.mtx.Unlock() | |||||
| now := time.Now() | |||||
| // compute tokens which needs | |||||
| tb.tokens = tb.tokens + tb.refreshRate*now.Sub(tb.lastToken).Seconds() | |||||
| if tb.tokens > float64(tb.capacity) { | |||||
| tb.tokens = float64(tb.capacity) | |||||
| } | |||||
| // judge weather to pass through | |||||
| if tb.tokens >= 1 { | |||||
| tb.tokens-- | |||||
| tb.lastToken = now | |||||
| return true | |||||
| } | |||||
| return false | |||||
| } | |||||
| // LimitHandler registried a middle ware to use. | |||||
| func LimitHandler(maxConn int, refreshRate float64) gin.HandlerFunc { | |||||
| tb := &frequencyControlByTokenBucket{ | |||||
| capacity: int64(maxConn), | |||||
| refreshRate: refreshRate, | |||||
| tokens: 0, | |||||
| lastToken: time.Now(), | |||||
| } | |||||
| return func(c *gin.Context) { | |||||
| if !tb.Allow() { | |||||
| c.String(503, "Too many request") | |||||
| c.Abort() | |||||
| return | |||||
| } | |||||
| c.Next() | |||||
| } | |||||
| } | |||||
| @@ -49,13 +49,11 @@ func (h *CORS) Priority() int64 { | |||||
| // BeforeRun EDIT THIS TO YOUR OWN HOOK BEFORE RUN, DO NOT NEED USE Next() FUNCTION. | // BeforeRun EDIT THIS TO YOUR OWN HOOK BEFORE RUN, DO NOT NEED USE Next() FUNCTION. | ||||
| func (h *CORS) BeforeRun(c *gin.Context) { | func (h *CORS) BeforeRun(c *gin.Context) { | ||||
| // c.Writer.Header().Set("Access-Control-Allow-Origin", "*") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Origin", c.GetHeader("origin")) | |||||
| c.Writer.Header().Set("Access-Control-Max-Age", "70000") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE, PATCH") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length,token") | |||||
| c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Headers,token") | |||||
| c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") | |||||
| c.Header("Access-Control-Allow-Origin", c.GetHeader("Origin")) | |||||
| c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id") | |||||
| c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT") | |||||
| c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type, New-Token, New-Expires-At") | |||||
| c.Header("Access-Control-Allow-Credentials", "true") | |||||
| if c.Request.Method == "OPTIONS" { | if c.Request.Method == "OPTIONS" { | ||||
| c.AbortWithStatus(http.StatusNoContent) | c.AbortWithStatus(http.StatusNoContent) | ||||
| @@ -38,7 +38,7 @@ func TestUrlTrie(t *testing.T) { | |||||
| // test case | // test case | ||||
| testData := []struct { | testData := []struct { | ||||
| priority int64 | priority int64 | ||||
| url []string | |||||
| urls []string | |||||
| }{ | }{ | ||||
| {1, []string{"/gin"}}, | {1, []string{"/gin"}}, | ||||
| {1, []string{"/api/v1/123"}}, | {1, []string{"/api/v1/123"}}, | ||||
| @@ -51,8 +51,8 @@ func TestUrlTrie(t *testing.T) { | |||||
| // range test data | // range test data | ||||
| for _, data := range testData { | for _, data := range testData { | ||||
| trie.InsertBatch(data.url, &testHook{ | |||||
| urls: data.url, | |||||
| trie.InsertBatch(data.urls, &testHook{ | |||||
| urls: data.urls, | |||||
| priority: data.priority, | priority: data.priority, | ||||
| }) | }) | ||||
| } | } | ||||
| @@ -20,9 +20,10 @@ import "github.com/gofrs/uuid" | |||||
| type UserBase struct { | type UserBase struct { | ||||
| Model | Model | ||||
| UUID uuid.UUID `json:"uuid" gorm:"index;type:varchar(36);not null;comment:UUID"` | |||||
| Email string `json:"email" gorm:"type:varchar(255);not null;unique;comment:Email"` | |||||
| Nickname string `json:"nickname" gorm:"type:varchar(20);not null;comment:Nickname"` | |||||
| Avatar string `json:"avatar" gorm:"type:varchar(255);not null;comment:Avatar"` | |||||
| IsEnable bool `json:"enable" gorm:"type:tinyint(1);not null;default:1;comment:IsEnable,1:enable,0:disable"` | |||||
| UUID uuid.UUID `json:"uuid" gorm:"index;type:varchar(36);not null;comment:UUID"` | |||||
| Email string `json:"email" gorm:"type:varchar(255);not null;unique;comment:Email"` | |||||
| Nickname string `json:"nickname" gorm:"type:varchar(20);not null;comment:Nickname"` | |||||
| Avatar string `json:"avatar" gorm:"type:varchar(255);not null;comment:Avatar"` | |||||
| Description string `json:"description" gorm:"type:varchar(255);not null;comment:Description"` | |||||
| IsEnable bool `json:"is_enable" gorm:"type:tinyint(1);not null;default:1;comment:IsEnable,1:enable,0:disable"` | |||||
| } | } | ||||
| @@ -24,10 +24,11 @@ import ( | |||||
| type SysCommunity struct { | type SysCommunity struct { | ||||
| base.Model | base.Model | ||||
| UUID uuid.UUID `gorm:"index;column:uuid;column:uuid;not null;unique;comment:'community uuid'"` | |||||
| Name string `gorm:"column:name;type:varchar(64);not null;comment:'community name'"` | |||||
| Email string `gorm:"column:email;type:varchar(64);not null;comment:'community email'"` | |||||
| Avatar string `gorm:"column:avatar;type:varchar(255);not null;comment:'community avatar'"` | |||||
| UUID uuid.UUID `gorm:"index;column:uuid;column:uuid;not null;unique;comment:'community uuid'"` | |||||
| Name string `gorm:"column:name;type:varchar(64);not null;comment:'community name'"` | |||||
| Email string `gorm:"column:email;type:varchar(64);not null;comment:'community email'"` | |||||
| Avatar string `gorm:"column:avatar;type:varchar(255);not null;comment:'community avatar'"` | |||||
| Description string `gorm:"column:description;type:varchar(255);not null;comment:'community description'"` | |||||
| } | } | ||||
| // TableName table name. | // TableName table name. | ||||
| @@ -20,3 +20,8 @@ type CommunityParams struct { | |||||
| Email string `json:"email" binding:"required"` | Email string `json:"email" binding:"required"` | ||||
| Avatar *string `json:"avatar" binding:"required"` // Avatar is optional. | Avatar *string `json:"avatar" binding:"required"` // Avatar is optional. | ||||
| } | } | ||||
| // GetCommunityInfoParams community info params. | |||||
| type GetCommunityInfoParams struct { | |||||
| UUID string `json:"uuid" binding:"required"` | |||||
| } | |||||
| @@ -40,3 +40,8 @@ type LoginParamsWithAccount struct { | |||||
| Email string `json:"email" binding:"required"` | Email string `json:"email" binding:"required"` | ||||
| Password string `json:"password" binding:"required"` | Password string `json:"password" binding:"required"` | ||||
| } | } | ||||
| // GetUserInfoParams user info params. | |||||
| type GetUserInfoParams struct { | |||||
| UUID string `json:"uuid" binding:"required"` | |||||
| } | |||||
| @@ -0,0 +1,24 @@ | |||||
| // Copyright © 2023 OpenIM open source community. All rights reserved. | |||||
| // | |||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| // you may not use this file except in compliance with the License. | |||||
| // You may obtain a copy of the License at | |||||
| // | |||||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||||
| // | |||||
| // Unless required by applicable law or agreed to in writing, software | |||||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| // See the License for the specific language governing permissions and | |||||
| // limitations under the License. | |||||
| package responseparams | |||||
| // CommunityInfoResponse community info response. | |||||
| type CommunityInfoResponse struct { | |||||
| UUID string `json:"uuid"` | |||||
| Name string `json:"name"` | |||||
| Email string `json:"email"` | |||||
| Avatar string `json:"avatar"` | |||||
| Description string `json:"description"` | |||||
| } | |||||
| @@ -26,3 +26,14 @@ type UserTokenResponse struct { | |||||
| KFToken *TokenResponse `json:"kf_token"` | KFToken *TokenResponse `json:"kf_token"` | ||||
| IMToken *TokenResponse `json:"im_token"` | IMToken *TokenResponse `json:"im_token"` | ||||
| } | } | ||||
| // UserInfoResponse user info response. | |||||
| type UserInfoResponse struct { | |||||
| UUID string `json:"uuid"` | |||||
| Email string `json:"email"` | |||||
| Nickname string `json:"nickname"` | |||||
| Avatar string `json:"avatar"` | |||||
| Description string `json:"description"` | |||||
| IsEnable bool `json:"is_enable"` | |||||
| IsAdmin bool `json:"is_admin"` | |||||
| } | |||||
| @@ -25,7 +25,6 @@ import ( | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/api" | "github.com/OpenIMSDK/OpenKF/server/internal/api" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/config" | "github.com/OpenIMSDK/OpenKF/server/internal/config" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/middleware" | |||||
| urltrie "github.com/OpenIMSDK/OpenKF/server/internal/middleware/hooks/url_trie" | urltrie "github.com/OpenIMSDK/OpenKF/server/internal/middleware/hooks/url_trie" | ||||
| ) | ) | ||||
| @@ -38,7 +37,8 @@ func InitRouter() *gin.Engine { | |||||
| } | } | ||||
| r := gin.Default() | r := gin.Default() | ||||
| r.Use(urltrie.RunHook(), middleware.EnableCROS()) | |||||
| // Enable Hooks | |||||
| r.Use(urltrie.RunHook()) | |||||
| // swagger | // swagger | ||||
| r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) | r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) | ||||
| @@ -66,9 +66,16 @@ func InitRouter() *gin.Engine { | |||||
| } | } | ||||
| // admin := apiv1.Group("/admin") | // admin := apiv1.Group("/admin") | ||||
| // user := apiv1.Group("/user") | |||||
| user := apiv1.Group("/user") | |||||
| { | |||||
| user.GET("/me", api.GetMyInfo) | |||||
| user.POST("/info", api.GetUserInfo) | |||||
| } | |||||
| community := apiv1.Group("/community") | community := apiv1.Group("/community") | ||||
| { | { | ||||
| community.GET("/me", api.GetMyCommunityInfo) | |||||
| community.POST("/info", api.GetCommunityInfo) | |||||
| community.POST("/create", api.CreateCommunity) | community.POST("/create", api.CreateCommunity) | ||||
| } | } | ||||
| @@ -18,10 +18,12 @@ import ( | |||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| "github.com/gofrs/uuid" | "github.com/gofrs/uuid" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/common" | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/conn/db" | "github.com/OpenIMSDK/OpenKF/server/internal/conn/db" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/dal/dao" | "github.com/OpenIMSDK/OpenKF/server/internal/dal/dao" | ||||
| systemroles "github.com/OpenIMSDK/OpenKF/server/internal/models/system_roles" | systemroles "github.com/OpenIMSDK/OpenKF/server/internal/models/system_roles" | ||||
| requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | ||||
| responseparams "github.com/OpenIMSDK/OpenKF/server/internal/params/response" | |||||
| "github.com/OpenIMSDK/OpenKF/server/pkg/utils" | "github.com/OpenIMSDK/OpenKF/server/pkg/utils" | ||||
| ) | ) | ||||
| @@ -64,10 +66,51 @@ func (svc *CommunityService) Create(community *requestparams.CommunityParams) (s | |||||
| return c.UUID.String(), c.Id, nil | return c.UUID.String(), c.Id, nil | ||||
| } | } | ||||
| // GetCommunityInfoById get community info by id. | |||||
| func (svc *CommunityService) GetCommunityInfoById(id uint) (*responseparams.CommunityInfoResponse, error) { | |||||
| resp := &responseparams.CommunityInfoResponse{} | |||||
| if id <= 0 { | |||||
| return resp, common.NewError(common.I_INVALID_PARAM) | |||||
| } | |||||
| c, err := svc.SysCommunityDao.FindFirstById(id) | |||||
| if err != nil { | |||||
| return resp, err | |||||
| } | |||||
| resp.UUID = c.UUID.String() | |||||
| resp.Email = c.Email | |||||
| resp.Avatar = c.Avatar | |||||
| resp.Name = c.Name | |||||
| resp.Description = c.Description | |||||
| return resp, err | |||||
| } | |||||
| // GetCommunityInfoByUUID get community info by uuid. | // GetCommunityInfoByUUID get community info by uuid. | ||||
| func (svc *CommunityService) GetCommunityInfoByUUID(uid string, offset int, limit int) ([]*systemroles.SysCommunity, int64, error) { | |||||
| _uuid := uuid.Must(uuid.FromString(uid)) | |||||
| c, count, err := svc.SysCommunityDao.FindByUUIDPage(_uuid, offset, limit) | |||||
| func (svc *CommunityService) GetCommunityInfoByUUID(uid string) (*responseparams.CommunityInfoResponse, error) { | |||||
| resp := &responseparams.CommunityInfoResponse{} | |||||
| if uid == "" { | |||||
| return resp, common.NewError(common.I_INVALID_PARAM) | |||||
| } | |||||
| _uuid, err := uuid.FromString(uid) | |||||
| if err != nil { | |||||
| return resp, err | |||||
| } | |||||
| c, err := svc.SysCommunityDao.FindFirstByUUID(_uuid) | |||||
| if err != nil { | |||||
| return resp, err | |||||
| } | |||||
| resp.UUID = c.UUID.String() | |||||
| resp.Email = c.Email | |||||
| resp.Avatar = c.Avatar | |||||
| resp.Name = c.Name | |||||
| resp.Description = c.Description | |||||
| return c, count, err | |||||
| return resp, err | |||||
| } | } | ||||
| @@ -19,9 +19,12 @@ import ( | |||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/common" | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/conn/client" | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/conn/db" | "github.com/OpenIMSDK/OpenKF/server/internal/conn/db" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/dal/cache" | "github.com/OpenIMSDK/OpenKF/server/internal/dal/cache" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | "github.com/OpenIMSDK/OpenKF/server/internal/utils" | ||||
| pkgutils "github.com/OpenIMSDK/OpenKF/server/pkg/utils" | |||||
| ) | ) | ||||
| // MailService mail service. | // MailService mail service. | ||||
| @@ -43,6 +46,10 @@ func NewMailService(c *gin.Context) *MailService { | |||||
| // SendCode send code to email. | // SendCode send code to email. | ||||
| func (svc *MailService) SendCode(email string) (err error) { | func (svc *MailService) SendCode(email string) (err error) { | ||||
| if !pkgutils.IsValidEmail(email) { | |||||
| return common.NewError(common.I_INVALID_PARAM) | |||||
| } | |||||
| // Check the code is exist. | // Check the code is exist. | ||||
| code, err := svc.cache.Get(svc.ctx, "code:"+email) | code, err := svc.cache.Get(svc.ctx, "code:"+email) | ||||
| // Refresh code. | // Refresh code. | ||||
| @@ -55,13 +62,17 @@ func (svc *MailService) SendCode(email string) (err error) { | |||||
| } | } | ||||
| // Generate code. | // Generate code. | ||||
| // err = client.SendEmail(email, "OpenKF Admin Register", "Your verification code is "+code) | |||||
| err = client.SendEmail(email, "OpenKF Admin Register", "Your verification code is "+code) | |||||
| return err | return err | ||||
| } | } | ||||
| // CheckCode check code. | // CheckCode check code. | ||||
| func (svc *MailService) CheckCode(email, code string) bool { | func (svc *MailService) CheckCode(email, code string) bool { | ||||
| if !pkgutils.IsValidEmail(email) { | |||||
| return false | |||||
| } | |||||
| // Check the code is exist. | // Check the code is exist. | ||||
| c, err := svc.cache.Get(svc.ctx, "code:"+email) | c, err := svc.cache.Get(svc.ctx, "code:"+email) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -20,7 +20,9 @@ import ( | |||||
| "net" | "net" | ||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| "github.com/gofrs/uuid" | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/common" | |||||
| "github.com/OpenIMSDK/OpenKF/server/internal/config" | "github.com/OpenIMSDK/OpenKF/server/internal/config" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/dal/dao" | "github.com/OpenIMSDK/OpenKF/server/internal/dal/dao" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/models/base" | "github.com/OpenIMSDK/OpenKF/server/internal/models/base" | ||||
| @@ -189,8 +191,15 @@ func (svc *UserService) LoginWithAccount(param *requestparams.LoginParamsWithAcc | |||||
| return resp, errors.New("password is not correct") | return resp, errors.New("password is not correct") | ||||
| } | } | ||||
| // Get community id | |||||
| cService := NewCommunityService((svc.ctx).(*gin.Context)) | |||||
| c, err := cService.GetCommunityInfoById(u.CommunityId) | |||||
| if err != nil { | |||||
| return resp, err | |||||
| } | |||||
| // Generate KF token | // Generate KF token | ||||
| kfToken, kfExpireTimeSeconds, err := internal_utils.GenerateJwtToken(u.UUID.String(), u.CommunityId) | |||||
| kfToken, kfExpireTimeSeconds, err := internal_utils.GenerateJwtToken(u.UUID.String(), c.UUID) | |||||
| if err != nil { | if err != nil { | ||||
| return resp, err | return resp, err | ||||
| } | } | ||||
| @@ -237,3 +246,31 @@ func getUserIMToken(param *request.UserTokenParams) (*response.TokenData, error) | |||||
| return &resp.Data, nil | return &resp.Data, nil | ||||
| } | } | ||||
| // GetUserInfoByUUID get user info by uuid. | |||||
| func (svc *UserService) GetUserInfoByUUID(uid string) (*responseparams.UserInfoResponse, error) { | |||||
| resp := &responseparams.UserInfoResponse{} | |||||
| if uid == "" { | |||||
| return resp, common.NewError(common.I_INVALID_PARAM) | |||||
| } | |||||
| _uuid, err := uuid.FromString(uid) | |||||
| if err != nil { | |||||
| return resp, err | |||||
| } | |||||
| u, err := svc.SysUserDao.FindFirstByUUID(_uuid) | |||||
| if err != nil { | |||||
| return resp, err | |||||
| } | |||||
| resp.UUID = u.UUID.String() | |||||
| resp.Email = u.Email | |||||
| resp.Nickname = u.Nickname | |||||
| resp.Avatar = u.Avatar | |||||
| resp.IsAdmin = u.IsAdmin | |||||
| resp.IsEnable = u.IsEnable | |||||
| return resp, nil | |||||
| } | |||||
| @@ -12,35 +12,31 @@ | |||||
| // See the License for the specific language governing permissions and | // See the License for the specific language governing permissions and | ||||
| // limitations under the License. | // limitations under the License. | ||||
| package middleware | |||||
| package utils | |||||
| import ( | import ( | ||||
| "net/http" | |||||
| "strings" | |||||
| "github.com/gin-gonic/gin" | "github.com/gin-gonic/gin" | ||||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | |||||
| "github.com/pkg/errors" | |||||
| ) | ) | ||||
| // EnableAuth enable auth middleware. | |||||
| func EnableAuth() gin.HandlerFunc { | |||||
| return func(c *gin.Context) { | |||||
| token := c.GetHeader("Authorization") | |||||
| if token == "" || strings.Fields(token)[0] != "Bearer" { | |||||
| c.AbortWithStatus(http.StatusUnauthorized) | |||||
| return | |||||
| // GetUserID get user uuid from context. | |||||
| func GetUserUUID(c *gin.Context) (string, error) { | |||||
| if claims, ok := c.Get("claims"); ok { | |||||
| if c := claims.(*JwtClaims); c != nil { | |||||
| return c.UserUUID, nil | |||||
| } | } | ||||
| } | |||||
| _, err := utils.ParseJwtToken(token) | |||||
| if err != nil { | |||||
| c.AbortWithStatus(http.StatusUnauthorized) | |||||
| return "", errors.Errorf("get user uuid failed") | |||||
| } | |||||
| return | |||||
| // GetCommunityUUID get community uuid from context. | |||||
| func GetCommunityUUID(c *gin.Context) (string, error) { | |||||
| if claims, ok := c.Get("claims"); ok { | |||||
| if c := claims.(*JwtClaims); c != nil { | |||||
| return c.CommunityUUID, nil | |||||
| } | } | ||||
| // todo: add info to claims | |||||
| c.Next() | |||||
| } | } | ||||
| return "", errors.Errorf("get community uuid failed") | |||||
| } | } | ||||
| @@ -23,19 +23,19 @@ import ( | |||||
| ) | ) | ||||
| type JwtClaims struct { | type JwtClaims struct { | ||||
| UUID string `json:"uuid"` | |||||
| CommunityId uint `json:"community_id"` | |||||
| UserUUID string `json:"user_uuid"` | |||||
| CommunityUUID string `json:"community_uuid"` | |||||
| jwt.RegisteredClaims | jwt.RegisteredClaims | ||||
| } | } | ||||
| func GenerateJwtToken(uid string, community_id uint) (string, uint, error) { | |||||
| func GenerateJwtToken(user_uuid string, community_uuid string) (string, uint, error) { | |||||
| secret := []byte(config.Config.JWT.Secret) | secret := []byte(config.Config.JWT.Secret) | ||||
| issuer := config.Config.JWT.Issuer | issuer := config.Config.JWT.Issuer | ||||
| expireDays := config.Config.JWT.ExpireDays | expireDays := config.Config.JWT.ExpireDays | ||||
| claims := &JwtClaims{ | claims := &JwtClaims{ | ||||
| uid, | |||||
| community_id, | |||||
| user_uuid, | |||||
| community_uuid, | |||||
| jwt.RegisteredClaims{ | jwt.RegisteredClaims{ | ||||
| Issuer: issuer, | Issuer: issuer, | ||||
| NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), | NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), | ||||
| @@ -47,6 +47,13 @@ func init() { | |||||
| //go:generate go env -w GOPROXY=https://goproxy.cn,direct | //go:generate go env -w GOPROXY=https://goproxy.cn,direct | ||||
| //go:generate go mod tidy | //go:generate go mod tidy | ||||
| //go:generate go mod download | //go:generate go mod download | ||||
| // @title OpenKF Server | |||||
| // @version v0.2.0 | |||||
| // @description OpenKF Server API Docs. | |||||
| // @securityDefinitions.apikey ApiKeyAuth | |||||
| // @in header | |||||
| // @name Authorization. | |||||
| func main() { | func main() { | ||||
| serverAddress := fmt.Sprintf("%s:%d", config.Config.Server.Ip, config.Config.Server.Port) | serverAddress := fmt.Sprintf("%s:%d", config.Config.Server.Ip, config.Config.Server.Port) | ||||
| @@ -0,0 +1,27 @@ | |||||
| // Copyright © 2023 OpenIM open source community. All rights reserved. | |||||
| // | |||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| // you may not use this file except in compliance with the License. | |||||
| // You may obtain a copy of the License at | |||||
| // | |||||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||||
| // | |||||
| // Unless required by applicable law or agreed to in writing, software | |||||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| // See the License for the specific language governing permissions and | |||||
| // limitations under the License. | |||||
| package utils | |||||
| import "regexp" | |||||
| // IsValidEmail check if the email is valid. | |||||
| func IsValidEmail(email string) bool { | |||||
| // Regular expression for basic email validation | |||||
| // This regex pattern is a simplified version and may not cover all edge cases. | |||||
| // You can use more comprehensive patterns depending on your specific needs. | |||||
| emailRegex := `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` | |||||
| return regexp.MustCompile(emailRegex).MatchString(email) | |||||
| } | |||||
| @@ -45,6 +45,7 @@ module.exports = { | |||||
| '@typescript-eslint/no-explicit-any': 'off', | '@typescript-eslint/no-explicit-any': 'off', | ||||
| '@typescript-eslint/ban-ts-comment': 'off', | '@typescript-eslint/ban-ts-comment': 'off', | ||||
| '@typescript-eslint/ban-types': 'off', | '@typescript-eslint/ban-types': 'off', | ||||
| "vue/multi-word-component-names": "off" | |||||
| 'vue/multi-word-component-names': 'off', | |||||
| 'vue/comment-directive': 'off', | |||||
| }, | }, | ||||
| }; | }; | ||||
| @@ -11,8 +11,12 @@ | |||||
| "axios": "^1.4.0", | "axios": "^1.4.0", | ||||
| "echarts": "^5.4.2", | "echarts": "^5.4.2", | ||||
| "less-loader": "^11.1.3", | "less-loader": "^11.1.3", | ||||
| "lodash": "^4.17.21", | |||||
| "open-im-sdk-wasm": "^0.1.1", | "open-im-sdk-wasm": "^0.1.1", | ||||
| "pinia": "^2.1.4", | |||||
| "pinia-plugin-persistedstate": "^3.1.0", | |||||
| "qs": "^6.11.2", | "qs": "^6.11.2", | ||||
| "tdesign-icons-vue-next": "^0.1.11", | |||||
| "tdesign-vue-next": "^1.3.10", | "tdesign-vue-next": "^1.3.10", | ||||
| "vite-svg-loader": "^4.0.0", | "vite-svg-loader": "^4.0.0", | ||||
| "vue": "^3.3.4", | "vue": "^3.3.4", | ||||
| @@ -2950,6 +2954,64 @@ | |||||
| "node": ">=6" | "node": ">=6" | ||||
| } | } | ||||
| }, | }, | ||||
| "node_modules/pinia": { | |||||
| "version": "2.1.4", | |||||
| "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.4.tgz", | |||||
| "integrity": "sha512-vYlnDu+Y/FXxv1ABo1vhjC+IbqvzUdiUC3sfDRrRyY2CQSrqqaa+iiHmqtARFxJVqWQMCJfXx1PBvFs9aJVLXQ==", | |||||
| "dependencies": { | |||||
| "@vue/devtools-api": "^6.5.0", | |||||
| "vue-demi": ">=0.14.5" | |||||
| }, | |||||
| "funding": { | |||||
| "url": "https://github.com/sponsors/posva" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@vue/composition-api": "^1.4.0", | |||||
| "typescript": ">=4.4.4", | |||||
| "vue": "^2.6.14 || ^3.3.0" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@vue/composition-api": { | |||||
| "optional": true | |||||
| }, | |||||
| "typescript": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/pinia-plugin-persistedstate": { | |||||
| "version": "3.1.0", | |||||
| "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.1.0.tgz", | |||||
| "integrity": "sha512-8UN+vYMEPBdgNLwceY08mi5olI0wkYaEb8b6hD6xW7SnBRuPydWHlEhZvUWgNb/ibuf4PvufpvtS+dmhYjJQOw==", | |||||
| "peerDependencies": { | |||||
| "pinia": "^2.0.0" | |||||
| } | |||||
| }, | |||||
| "node_modules/pinia/node_modules/vue-demi": { | |||||
| "version": "0.14.5", | |||||
| "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", | |||||
| "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", | |||||
| "hasInstallScript": true, | |||||
| "bin": { | |||||
| "vue-demi-fix": "bin/vue-demi-fix.js", | |||||
| "vue-demi-switch": "bin/vue-demi-switch.js" | |||||
| }, | |||||
| "engines": { | |||||
| "node": ">=12" | |||||
| }, | |||||
| "funding": { | |||||
| "url": "https://github.com/sponsors/antfu" | |||||
| }, | |||||
| "peerDependencies": { | |||||
| "@vue/composition-api": "^1.0.0-rc.1", | |||||
| "vue": "^3.0.0-0 || ^2.6.0" | |||||
| }, | |||||
| "peerDependenciesMeta": { | |||||
| "@vue/composition-api": { | |||||
| "optional": true | |||||
| } | |||||
| } | |||||
| }, | |||||
| "node_modules/postcss": { | "node_modules/postcss": { | ||||
| "version": "8.4.24", | "version": "8.4.24", | ||||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | ||||
| @@ -3553,7 +3615,7 @@ | |||||
| "version": "5.1.3", | "version": "5.1.3", | ||||
| "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", | ||||
| "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", | "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", | ||||
| "dev": true, | |||||
| "devOptional": true, | |||||
| "bin": { | "bin": { | ||||
| "tsc": "bin/tsc", | "tsc": "bin/tsc", | ||||
| "tsserver": "bin/tsserver" | "tsserver": "bin/tsserver" | ||||
| @@ -13,7 +13,10 @@ | |||||
| "axios": "^1.4.0", | "axios": "^1.4.0", | ||||
| "echarts": "^5.4.2", | "echarts": "^5.4.2", | ||||
| "less-loader": "^11.1.3", | "less-loader": "^11.1.3", | ||||
| "lodash": "^4.17.21", | |||||
| "open-im-sdk-wasm": "^0.1.1", | "open-im-sdk-wasm": "^0.1.1", | ||||
| "pinia": "^2.1.4", | |||||
| "pinia-plugin-persistedstate": "^3.1.0", | |||||
| "qs": "^6.11.2", | "qs": "^6.11.2", | ||||
| "tdesign-icons-vue-next": "^0.1.11", | "tdesign-icons-vue-next": "^0.1.11", | ||||
| "tdesign-vue-next": "^1.3.10", | "tdesign-vue-next": "^1.3.10", | ||||
| @@ -13,10 +13,12 @@ | |||||
| // limitations under the License. | // limitations under the License. | ||||
| import { CreateCommunityParam } from '../request/communityModel'; | import { CreateCommunityParam } from '../request/communityModel'; | ||||
| import { GetCommunityInfoResponse } from '../response/communityModel'; | |||||
| import { request } from '@/utils/request'; | import { request } from '@/utils/request'; | ||||
| const API = { | const API = { | ||||
| CreateCommunity: '/community/create', | CreateCommunity: '/community/create', | ||||
| CommunityMe: '/community/me', | |||||
| }; | }; | ||||
| // Create community | // Create community | ||||
| @@ -26,3 +28,18 @@ export function createCommunity(data: CreateCommunityParam) { | |||||
| data, | data, | ||||
| }); | }); | ||||
| } | } | ||||
| // Get my community info | |||||
| export function getMyCommunityInfo() { | |||||
| return request.get<GetCommunityInfoResponse>({ | |||||
| url: API.CommunityMe, | |||||
| }); | |||||
| } | |||||
| // Get community info | |||||
| export function getCommunityInfo(data: GetCommunityInfoResponse) { | |||||
| return request.get<GetCommunityInfoResponse>({ | |||||
| url: API.CommunityMe, | |||||
| params: data, | |||||
| }); | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| // Copyright © 2023 OpenIM open source community. All rights reserved. | |||||
| // | |||||
| // Licensed under the Apache License, Version 2.0 (the "License"); | |||||
| // you may not use this file except in compliance with the License. | |||||
| // You may obtain a copy of the License at | |||||
| // | |||||
| // http://www.apache.org/licenses/LICENSE-2.0 | |||||
| // | |||||
| // Unless required by applicable law or agreed to in writing, software | |||||
| // distributed under the License is distributed on an "AS IS" BASIS, | |||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
| // See the License for the specific language governing permissions and | |||||
| // limitations under the License. | |||||
| import { GetUserInfoParam } from '../request/userModel'; | |||||
| import { GetUserInfoResponse } from '../response/userModel'; | |||||
| import { request } from '@/utils/request'; | |||||
| const API = { | |||||
| UserMe: '/user/me', | |||||
| }; | |||||
| // Get my info | |||||
| export function getMyInfo() { | |||||
| return request.get<GetUserInfoResponse>({ | |||||
| url: API.UserMe, | |||||
| }); | |||||
| } | |||||
| // Get User Info | |||||
| export function getUserInfo(data: GetUserInfoParam) { | |||||
| return request.get<GetUserInfoResponse>({ | |||||
| url: API.UserMe, | |||||
| params: data, | |||||
| }); | |||||
| } | |||||
| @@ -5,3 +5,7 @@ export interface CommunityInfo { | |||||
| } | } | ||||
| export type CreateCommunityParam = CommunityInfo; | export type CreateCommunityParam = CommunityInfo; | ||||
| export interface GetCommunityInfoParam { | |||||
| uuid: string; | |||||
| } | |||||
| @@ -22,3 +22,7 @@ export interface AccountLoginParam { | |||||
| email: string; | email: string; | ||||
| password: string; | password: string; | ||||
| } | } | ||||
| export interface GetUserInfoParam { | |||||
| uuid: string; | |||||
| } | |||||
| @@ -0,0 +1,7 @@ | |||||
| export interface GetCommunityInfoResponse { | |||||
| uuid: string; | |||||
| name: string; | |||||
| email: string; | |||||
| avatar: string; | |||||
| description: string; | |||||
| } | |||||
| @@ -8,3 +8,13 @@ export interface UserLoginResponse { | |||||
| kf_token: TokenResponse; | kf_token: TokenResponse; | ||||
| im_token: TokenResponse; | im_token: TokenResponse; | ||||
| } | } | ||||
| export interface GetUserInfoResponse { | |||||
| uuid: string; | |||||
| email: string; | |||||
| nickname: string; | |||||
| avatar: string; | |||||
| description: string; | |||||
| is_enabled: boolean; | |||||
| is_admin: boolean; | |||||
| } | |||||
| @@ -19,6 +19,7 @@ import App from './App.vue'; | |||||
| import axios from 'axios'; | import axios from 'axios'; | ||||
| import router from './router'; | import router from './router'; | ||||
| import TDesign from 'tdesign-vue-next'; | import TDesign from 'tdesign-vue-next'; | ||||
| import { store } from './store'; | |||||
| import 'tdesign-vue-next/es/style/index.css'; | import 'tdesign-vue-next/es/style/index.css'; | ||||
| import './style/index.less'; | import './style/index.less'; | ||||
| @@ -29,4 +30,4 @@ console.log(OpenIM); | |||||
| const app = createApp(App); | const app = createApp(App); | ||||
| app.config.globalProperties.$https = axios; // use axios | app.config.globalProperties.$https = axios; // use axios | ||||
| app.use(router).use(TDesign).mount('#app'); // mount the router on the app | |||||
| app.use(router).use(TDesign).use(store).mount('#app'); // mount the router on the app | |||||
| @@ -18,11 +18,48 @@ import { createRouter, createWebHistory } from 'vue-router'; | |||||
| // router options | // router options | ||||
| const routes = [ | const routes = [ | ||||
| { | |||||
| path: '/', | |||||
| redirect: '/home/dashboard', | |||||
| }, | |||||
| { | { | ||||
| path: '/login', | path: '/login', | ||||
| name: 'Login', | name: 'Login', | ||||
| component: () => import('@/views/login/index.vue'), | component: () => import('@/views/login/index.vue'), | ||||
| }, | }, | ||||
| { | |||||
| path: '/home', | |||||
| name: 'Home', | |||||
| redirect: '/home/dashboard', | |||||
| component: () => import('@/views/layouts/index.vue'), | |||||
| children: [ | |||||
| { | |||||
| path: 'setting', | |||||
| name: 'Setting', | |||||
| component: () => import('@/views/setting/index.vue'), | |||||
| }, | |||||
| { | |||||
| path: 'platform', | |||||
| name: 'Platform', | |||||
| component: () => import('@/views/platform/index.vue'), | |||||
| }, | |||||
| { | |||||
| path: 'session', | |||||
| name: 'Session', | |||||
| component: () => import('@/views/session/index.vue'), | |||||
| }, | |||||
| { | |||||
| path: 'dashboard', | |||||
| name: 'Dashboard', | |||||
| component: () => import('@/views/dashboard/index.vue'), | |||||
| }, | |||||
| { | |||||
| path: 'health', | |||||
| name: 'Health', | |||||
| component: () => import('@/views/health/index.vue'), | |||||
| }, | |||||
| ], | |||||
| }, | |||||
| ]; | ]; | ||||
| // create router | // create router | ||||
| @@ -0,0 +1,12 @@ | |||||
| import { createPinia } from 'pinia'; | |||||
| import { createPersistedState } from 'pinia-plugin-persistedstate'; | |||||
| const store = createPinia(); | |||||
| store.use(createPersistedState()); | |||||
| export { store }; | |||||
| export * from './menu'; | |||||
| export * from './user'; | |||||
| export default store; | |||||
| @@ -0,0 +1,3 @@ | |||||
| import { defineStore } from 'pinia'; | |||||
| import { usePermissionStore } from '@/store'; | |||||
| @@ -0,0 +1,79 @@ | |||||
| import type { | |||||
| TokenResponse, | |||||
| GetUserInfoResponse, | |||||
| UserLoginResponse, | |||||
| } from '@/api/response/userModel'; | |||||
| import type { GetCommunityInfoResponse } from '@/api/response/communityModel'; | |||||
| import { defineStore } from 'pinia'; | |||||
| import { getMyCommunityInfo } from '@/api/index/community'; | |||||
| import { getMyInfo } from '@/api/index/user'; | |||||
| const InitUserInfo: GetUserInfoResponse = { | |||||
| uuid: '', | |||||
| email: '', | |||||
| nickname: '', | |||||
| avatar: '', | |||||
| description: '', | |||||
| is_enabled: false, | |||||
| is_admin: false, | |||||
| }; | |||||
| const InitCommunityInfo: GetCommunityInfoResponse = { | |||||
| uuid: '', | |||||
| name: '', | |||||
| email: '', | |||||
| description: '', | |||||
| avatar: '', | |||||
| }; | |||||
| const InitToken: TokenResponse = { | |||||
| token: '', | |||||
| expire_time_seconds: 0, | |||||
| }; | |||||
| export const useUserStore = defineStore('user', { | |||||
| state: () => ({ | |||||
| kf_token: { ...InitToken }, | |||||
| im_token: { ...InitToken }, | |||||
| userInfo: { ...InitUserInfo }, | |||||
| communityInfo: { ...InitCommunityInfo }, | |||||
| }), | |||||
| getters: {}, | |||||
| actions: { | |||||
| async StoreToken(info: UserLoginResponse) { | |||||
| this.kf_token = info.kf_token; | |||||
| this.im_token = info.im_token; | |||||
| }, | |||||
| async StoreInfo() { | |||||
| // fetch user info and community info | |||||
| const [userInfo, communityInfo] = await Promise.all([ | |||||
| getMyInfo() | |||||
| .then(res => { | |||||
| console.log('getMyInfo success', res); | |||||
| return res; | |||||
| }) | |||||
| .catch(res => { | |||||
| console.log('getMyInfo error', res); | |||||
| return InitUserInfo; | |||||
| }), // if error, set to default value | |||||
| getMyCommunityInfo() | |||||
| .then(res => { | |||||
| console.log('getMyCommunityInfo success', res); | |||||
| return res; | |||||
| }) | |||||
| .catch(res => { | |||||
| console.log('getMyCommunityInfo error', res); | |||||
| return InitCommunityInfo; | |||||
| }), | |||||
| ]); | |||||
| this.userInfo = userInfo; | |||||
| this.communityInfo = communityInfo; | |||||
| }, | |||||
| async logout() { | |||||
| this.kf_token = { ...InitToken }; | |||||
| this.im_token = { ...InitToken }; | |||||
| this.userInfo = { ...InitUserInfo }; | |||||
| this.communityInfo = { ...InitCommunityInfo }; | |||||
| }, | |||||
| }, | |||||
| }); | |||||
| @@ -0,0 +1,20 @@ | |||||
| export interface BaseInfo { | |||||
| User: UserInfo; | |||||
| Community: CommunityInfo; | |||||
| } | |||||
| export interface UserInfo { | |||||
| avatar: string; | |||||
| name: string; | |||||
| email: string; | |||||
| description: string; | |||||
| } | |||||
| export interface CommunityInfo { | |||||
| avatar: string; | |||||
| name: string; | |||||
| email: string; | |||||
| description: string; | |||||
| } | |||||
| // export interface MenuRoute {} | |||||
| @@ -7,6 +7,7 @@ import { ContentTypeEnum } from '@/constants'; | |||||
| import { VAxios } from './axios'; | import { VAxios } from './axios'; | ||||
| import type { AxiosTransform, CreateAxiosOptions } from './transform'; | import type { AxiosTransform, CreateAxiosOptions } from './transform'; | ||||
| import { formatRequestDate, joinTimestamp, setObjToUrlParams } from './utils'; | import { formatRequestDate, joinTimestamp, setObjToUrlParams } from './utils'; | ||||
| import { useUserStore } from '@/store'; | |||||
| // Default API url | // Default API url | ||||
| const host = import.meta.env.VITE_API_URL; | const host = import.meta.env.VITE_API_URL; | ||||
| @@ -114,19 +115,20 @@ const transform: AxiosTransform = { | |||||
| }, | }, | ||||
| requestInterceptors: (config, options) => { | requestInterceptors: (config, options) => { | ||||
| // TODO: Append request token | |||||
| // const { token } = xxx; | |||||
| // if ( | |||||
| // token && | |||||
| // (config as Recordable)?.requestOptions?.withToken !== false | |||||
| // ) { | |||||
| // // jwt token | |||||
| // (config as Recordable).headers.Authorization = | |||||
| // options.authenticationScheme | |||||
| // ? `${options.authenticationScheme} ${token}` | |||||
| // : token; | |||||
| // } | |||||
| const userStore = useUserStore(); | |||||
| const kf_token = userStore.kf_token.token; | |||||
| if ( | |||||
| kf_token && | |||||
| (config as Recordable)?.requestOptions?.withToken !== false | |||||
| ) { | |||||
| // Append Bearer token | |||||
| (config as Recordable).headers.Authorization = | |||||
| options.authenticationScheme | |||||
| ? `${options.authenticationScheme} ${kf_token}` | |||||
| : kf_token; | |||||
| } | |||||
| return config; | return config; | ||||
| }, | }, | ||||
| @@ -163,8 +165,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) { | |||||
| return new VAxios( | return new VAxios( | ||||
| merge( | merge( | ||||
| <CreateAxiosOptions>{ | <CreateAxiosOptions>{ | ||||
| authenticationScheme: '', | |||||
| // authenticationScheme: 'Bearer', | |||||
| // authenticationScheme: '', | |||||
| authenticationScheme: 'Bearer', // Use Bearer type token | |||||
| timeout: 10 * 1000, | timeout: 10 * 1000, | ||||
| withCredentials: true, | withCredentials: true, | ||||
| headers: { 'Content-Type': ContentTypeEnum.Json }, | headers: { 'Content-Type': ContentTypeEnum.Json }, | ||||
| @@ -0,0 +1,7 @@ | |||||
| <script setup lang="ts"></script> | |||||
| <template> | |||||
| <div></div> | |||||
| </template> | |||||
| <style lang="scss" scoped></style> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <script setup lang="ts"></script> | |||||
| <template> | |||||
| <div></div> | |||||
| </template> | |||||
| <style lang="scss" scoped></style> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <script setup lang="ts"></script> | |||||
| <template> | |||||
| <div>asdas</div> | |||||
| </template> | |||||
| <style lang="less" scoped></style> | |||||
| @@ -0,0 +1,80 @@ | |||||
| <script setup lang="ts"> | |||||
| import { ref } from 'vue'; | |||||
| const collapsed = ref(true); | |||||
| const iconUrl = ref( | |||||
| 'https://github.com/OpenIMSDK/OpenKF/assets/47499836/1cccc6f6-6baf-4849-b3f9-5901d683207c', | |||||
| ); | |||||
| const changeCollapsed = () => { | |||||
| collapsed.value = !collapsed.value; | |||||
| iconUrl.value = collapsed.value | |||||
| ? 'https://github.com/OpenIMSDK/OpenKF/assets/47499836/1cccc6f6-6baf-4849-b3f9-5901d683207c' | |||||
| : 'https://github.com/OpenIMSDK/OpenKF/assets/47499836/e4475b49-ccc0-4d1b-a308-b5fd150a594f'; | |||||
| }; | |||||
| const changeHandler = active => { | |||||
| console.log('change', active); | |||||
| }; | |||||
| </script> | |||||
| <template> | |||||
| <t-menu | |||||
| theme="light" | |||||
| default-value="dashboard" | |||||
| :collapsed="collapsed" | |||||
| @change="changeHandler" | |||||
| > | |||||
| <template #logo> | |||||
| <img :width="collapsed ? 35 : 136" :src="iconUrl" alt="logo" /> | |||||
| </template> | |||||
| <t-menu-item value="dashboard" class="menu-item"> | |||||
| <template #icon> | |||||
| <t-icon name="dashboard" /> | |||||
| </template> | |||||
| Dashboard | |||||
| </t-menu-item> | |||||
| <t-menu-item value="client-session"> | |||||
| <template #icon> | |||||
| <t-icon name="chat" /> | |||||
| </template> | |||||
| Session | |||||
| </t-menu-item> | |||||
| <t-menu-item value="access-platform"> | |||||
| <template #icon> | |||||
| <t-icon name="control-platform" /> | |||||
| </template> | |||||
| Platform | |||||
| </t-menu-item> | |||||
| <t-menu-item value="system-monitor"> | |||||
| <template #icon> | |||||
| <t-icon name="chart" /> | |||||
| </template> | |||||
| Monitor | |||||
| </t-menu-item> | |||||
| <t-menu-item value="system-config"> | |||||
| <template #icon> | |||||
| <t-icon name="setting" /> | |||||
| </template> | |||||
| Config | |||||
| </t-menu-item> | |||||
| <t-menu-item value="exit"> | |||||
| <template #icon> | |||||
| <t-icon name="login" /> | |||||
| </template> | |||||
| Exit | |||||
| </t-menu-item> | |||||
| <template #operations> | |||||
| <t-button | |||||
| class="t-demo-collapse-btn" | |||||
| variant="text" | |||||
| shape="square" | |||||
| @click="changeCollapsed" | |||||
| > | |||||
| <template #icon><t-icon name="view-list" /></template> | |||||
| </t-button> | |||||
| </template> | |||||
| </t-menu> | |||||
| </template> | |||||
| <style lang="less" scoped></style> | |||||
| @@ -0,0 +1,21 @@ | |||||
| <script setup lang="ts"> | |||||
| import LayoutContent from './components/LayoutContent.vue'; | |||||
| import LayoutSideNav from './components/LayoutSideNav.vue'; | |||||
| </script> | |||||
| <template> | |||||
| <div class="base"> | |||||
| <t-layout> | |||||
| <t-aside><layout-side-nav /></t-aside> | |||||
| <t-layout> | |||||
| <t-content><layout-content /></t-content> | |||||
| </t-layout> | |||||
| </t-layout> | |||||
| </div> | |||||
| </template> | |||||
| <style lang="less" scoped> | |||||
| .base { | |||||
| height: 100vh; | |||||
| } | |||||
| </style> | |||||
| @@ -8,13 +8,13 @@ import { OpenIM } from '@/api/openim'; | |||||
| import { OpenIMLoginConfig } from '@/constants'; | import { OpenIMLoginConfig } from '@/constants'; | ||||
| import { IMLoginParam } from '@/api/request/openimModel'; | import { IMLoginParam } from '@/api/request/openimModel'; | ||||
| import { ref, reactive } from 'vue'; | import { ref, reactive } from 'vue'; | ||||
| import { localCache } from '@/utils/common/cache'; | |||||
| import { useUserStore } from '@/store'; | |||||
| const formData = reactive({ email: '', password: '' }); | const formData = reactive({ email: '', password: '' }); | ||||
| const showPsw = ref(false); | const showPsw = ref(false); | ||||
| const form = ref(null); | const form = ref(null); | ||||
| const isRem = ref(false); | const isRem = ref(false); | ||||
| const rules: FormRule = { | |||||
| const rules: Record<string, FormRule[]> = { | |||||
| email: [ | email: [ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -63,20 +63,30 @@ const onSubmit = async (ctx: SubmitContext) => { | |||||
| apiAddress: OpenIMLoginConfig.APIAddress, | apiAddress: OpenIMLoginConfig.APIAddress, | ||||
| wsAddress: OpenIMLoginConfig.WSAddress, | wsAddress: OpenIMLoginConfig.WSAddress, | ||||
| }; | }; | ||||
| let temp_data = res; | |||||
| // operationID is auto generated in login method. | // operationID is auto generated in login method. | ||||
| OpenIM.login(config) | OpenIM.login(config) | ||||
| .then(res => { | .then(res => { | ||||
| MessagePlugin.success('Login success...'); | MessagePlugin.success('Login success...'); | ||||
| // TODO: redirect home page if token exists | |||||
| // Store data | |||||
| const userStore = useUserStore(); | |||||
| userStore.StoreToken(temp_data); | |||||
| userStore.StoreInfo(); | |||||
| console.log(userStore); | |||||
| if (isRem.value) { | if (isRem.value) { | ||||
| localCache.setCache('token', config.token); | |||||
| // localCache.setCache('token', config.token); | |||||
| } else { | } else { | ||||
| localCache.removeCache('token'); | |||||
| // localCache.removeCache('token'); | |||||
| } | } | ||||
| const redirect = route.query.redirect as string; | const redirect = route.query.redirect as string; | ||||
| const redirectUrl = redirect | const redirectUrl = redirect | ||||
| ? decodeURIComponent(redirect) | ? decodeURIComponent(redirect) | ||||
| : '/dashboard'; | |||||
| : '/home/dashboard'; | |||||
| router.push(redirectUrl); | router.push(redirectUrl); | ||||
| }) | }) | ||||
| .catch(err => { | .catch(err => { | ||||
| @@ -24,7 +24,7 @@ const formData = ref({ | |||||
| community: COMMUNITY_INITIAL_DATA, | community: COMMUNITY_INITIAL_DATA, | ||||
| admin: ADMIN_INITIAL_DATA, | admin: ADMIN_INITIAL_DATA, | ||||
| }); | }); | ||||
| const rules: FormRule = { | |||||
| const rules: Record<string, FormRule[]> = { | |||||
| email: [ | email: [ | ||||
| { | { | ||||
| required: true, | required: true, | ||||
| @@ -0,0 +1,7 @@ | |||||
| <script setup lang="ts"></script> | |||||
| <template> | |||||
| <div></div> | |||||
| </template> | |||||
| <style lang="scss" scoped></style> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <script setup lang="ts"></script> | |||||
| <template> | |||||
| <div></div> | |||||
| </template> | |||||
| <style lang="scss" scoped></style> | |||||
| @@ -0,0 +1,7 @@ | |||||
| <script setup lang="ts"></script> | |||||
| <template> | |||||
| <div></div> | |||||
| </template> | |||||
| <style lang="scss" scoped></style> | |||||