* 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 | |||
| import ( | |||
| "fmt" | |||
| urltrie "github.com/OpenIMSDK/OpenKF/server/internal/middleware/hooks/url_trie" | |||
| "github.com/gin-gonic/gin" | |||
| ) | |||
| var _ urltrie.Hook = (*{{.HookName}})(nil) | |||
| func init() { | |||
| urltrie.RegisterHook({{.HookName}}{}) | |||
| urltrie.RegisterHook(&{{.HookName}}{}) | |||
| fmt.Println("RegisterHook", "Register Hook[{{.HookName}}] success...") | |||
| } | |||
| @@ -21,6 +21,7 @@ import ( | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/common/response" | |||
| requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/service" | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | |||
| "github.com/OpenIMSDK/OpenKF/server/pkg/log" | |||
| ) | |||
| @@ -52,3 +53,58 @@ func CreateCommunity(c *gin.Context) { | |||
| 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" | |||
| requestparams "github.com/OpenIMSDK/OpenKF/server/internal/params/request" | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/service" | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | |||
| "github.com/OpenIMSDK/OpenKF/server/pkg/log" | |||
| ) | |||
| @@ -110,3 +111,57 @@ func AccountLogin(c *gin.Context) { | |||
| 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 | |||
| // Code. | |||
| // HTTP response code. | |||
| const ( | |||
| SUCCESS = 200 | |||
| ERROR = 500 | |||
| INVALID_PARAMS = 400 | |||
| UNAUTHORIZED = 401 | |||
| // OpenIM callback code. | |||
| OPENIM_SERVER_ALLOW_ACTION = 0 | |||
| 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 | |||
| import "github.com/pkg/errors" | |||
| // msg is a mapping of message. | |||
| var msg = map[int]string{ | |||
| SUCCESS: "success", | |||
| ERROR: "error", | |||
| INVALID_PARAMS: "request params error", | |||
| UNAUTHORIZED: "unauthorized", | |||
| // OpenIM callback code | |||
| OPENIM_SERVER_ALLOW_ACTION: "OpenIM allow 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. | |||
| @@ -34,3 +45,8 @@ func GetMsg(code int) string { | |||
| 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. | |||
| 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" { | |||
| c.AbortWithStatus(http.StatusNoContent) | |||
| @@ -38,7 +38,7 @@ func TestUrlTrie(t *testing.T) { | |||
| // test case | |||
| testData := []struct { | |||
| priority int64 | |||
| url []string | |||
| urls []string | |||
| }{ | |||
| {1, []string{"/gin"}}, | |||
| {1, []string{"/api/v1/123"}}, | |||
| @@ -51,8 +51,8 @@ func TestUrlTrie(t *testing.T) { | |||
| // range test data | |||
| for _, data := range testData { | |||
| trie.InsertBatch(data.url, &testHook{ | |||
| urls: data.url, | |||
| trie.InsertBatch(data.urls, &testHook{ | |||
| urls: data.urls, | |||
| priority: data.priority, | |||
| }) | |||
| } | |||
| @@ -20,9 +20,10 @@ import "github.com/gofrs/uuid" | |||
| type UserBase struct { | |||
| 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 { | |||
| 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. | |||
| @@ -20,3 +20,8 @@ type CommunityParams struct { | |||
| Email string `json:"email" binding:"required"` | |||
| 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"` | |||
| 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"` | |||
| 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/config" | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/middleware" | |||
| urltrie "github.com/OpenIMSDK/OpenKF/server/internal/middleware/hooks/url_trie" | |||
| ) | |||
| @@ -38,7 +37,8 @@ func InitRouter() *gin.Engine { | |||
| } | |||
| r := gin.Default() | |||
| r.Use(urltrie.RunHook(), middleware.EnableCROS()) | |||
| // Enable Hooks | |||
| r.Use(urltrie.RunHook()) | |||
| // swagger | |||
| r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) | |||
| @@ -66,9 +66,16 @@ func InitRouter() *gin.Engine { | |||
| } | |||
| // 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.GET("/me", api.GetMyCommunityInfo) | |||
| community.POST("/info", api.GetCommunityInfo) | |||
| community.POST("/create", api.CreateCommunity) | |||
| } | |||
| @@ -18,10 +18,12 @@ import ( | |||
| "github.com/gin-gonic/gin" | |||
| "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/dal/dao" | |||
| systemroles "github.com/OpenIMSDK/OpenKF/server/internal/models/system_roles" | |||
| 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" | |||
| ) | |||
| @@ -64,10 +66,51 @@ func (svc *CommunityService) Create(community *requestparams.CommunityParams) (s | |||
| 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. | |||
| 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/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/dal/cache" | |||
| "github.com/OpenIMSDK/OpenKF/server/internal/utils" | |||
| pkgutils "github.com/OpenIMSDK/OpenKF/server/pkg/utils" | |||
| ) | |||
| // MailService mail service. | |||
| @@ -43,6 +46,10 @@ func NewMailService(c *gin.Context) *MailService { | |||
| // SendCode send code to email. | |||
| func (svc *MailService) SendCode(email string) (err error) { | |||
| if !pkgutils.IsValidEmail(email) { | |||
| return common.NewError(common.I_INVALID_PARAM) | |||
| } | |||
| // Check the code is exist. | |||
| code, err := svc.cache.Get(svc.ctx, "code:"+email) | |||
| // Refresh code. | |||
| @@ -55,13 +62,17 @@ func (svc *MailService) SendCode(email string) (err error) { | |||
| } | |||
| // 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 | |||
| } | |||
| // CheckCode check code. | |||
| func (svc *MailService) CheckCode(email, code string) bool { | |||
| if !pkgutils.IsValidEmail(email) { | |||
| return false | |||
| } | |||
| // Check the code is exist. | |||
| c, err := svc.cache.Get(svc.ctx, "code:"+email) | |||
| if err != nil { | |||
| @@ -20,7 +20,9 @@ import ( | |||
| "net" | |||
| "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/dal/dao" | |||
| "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") | |||
| } | |||
| // Get community id | |||
| cService := NewCommunityService((svc.ctx).(*gin.Context)) | |||
| c, err := cService.GetCommunityInfoById(u.CommunityId) | |||
| if err != nil { | |||
| return resp, err | |||
| } | |||
| // 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 { | |||
| return resp, err | |||
| } | |||
| @@ -237,3 +246,31 @@ func getUserIMToken(param *request.UserTokenParams) (*response.TokenData, error) | |||
| 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 | |||
| // limitations under the License. | |||
| package middleware | |||
| package utils | |||
| import ( | |||
| "net/http" | |||
| "strings" | |||
| "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 { | |||
| UUID string `json:"uuid"` | |||
| CommunityId uint `json:"community_id"` | |||
| UserUUID string `json:"user_uuid"` | |||
| CommunityUUID string `json:"community_uuid"` | |||
| 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) | |||
| issuer := config.Config.JWT.Issuer | |||
| expireDays := config.Config.JWT.ExpireDays | |||
| claims := &JwtClaims{ | |||
| uid, | |||
| community_id, | |||
| user_uuid, | |||
| community_uuid, | |||
| jwt.RegisteredClaims{ | |||
| Issuer: issuer, | |||
| 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 mod tidy | |||
| //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() { | |||
| 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/ban-ts-comment': '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", | |||
| "echarts": "^5.4.2", | |||
| "less-loader": "^11.1.3", | |||
| "lodash": "^4.17.21", | |||
| "open-im-sdk-wasm": "^0.1.1", | |||
| "pinia": "^2.1.4", | |||
| "pinia-plugin-persistedstate": "^3.1.0", | |||
| "qs": "^6.11.2", | |||
| "tdesign-icons-vue-next": "^0.1.11", | |||
| "tdesign-vue-next": "^1.3.10", | |||
| "vite-svg-loader": "^4.0.0", | |||
| "vue": "^3.3.4", | |||
| @@ -2950,6 +2954,64 @@ | |||
| "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": { | |||
| "version": "8.4.24", | |||
| "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz", | |||
| @@ -3553,7 +3615,7 @@ | |||
| "version": "5.1.3", | |||
| "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", | |||
| "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", | |||
| "dev": true, | |||
| "devOptional": true, | |||
| "bin": { | |||
| "tsc": "bin/tsc", | |||
| "tsserver": "bin/tsserver" | |||
| @@ -13,7 +13,10 @@ | |||
| "axios": "^1.4.0", | |||
| "echarts": "^5.4.2", | |||
| "less-loader": "^11.1.3", | |||
| "lodash": "^4.17.21", | |||
| "open-im-sdk-wasm": "^0.1.1", | |||
| "pinia": "^2.1.4", | |||
| "pinia-plugin-persistedstate": "^3.1.0", | |||
| "qs": "^6.11.2", | |||
| "tdesign-icons-vue-next": "^0.1.11", | |||
| "tdesign-vue-next": "^1.3.10", | |||
| @@ -13,10 +13,12 @@ | |||
| // limitations under the License. | |||
| import { CreateCommunityParam } from '../request/communityModel'; | |||
| import { GetCommunityInfoResponse } from '../response/communityModel'; | |||
| import { request } from '@/utils/request'; | |||
| const API = { | |||
| CreateCommunity: '/community/create', | |||
| CommunityMe: '/community/me', | |||
| }; | |||
| // Create community | |||
| @@ -26,3 +28,18 @@ export function createCommunity(data: CreateCommunityParam) { | |||
| 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 interface GetCommunityInfoParam { | |||
| uuid: string; | |||
| } | |||
| @@ -22,3 +22,7 @@ export interface AccountLoginParam { | |||
| email: 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; | |||
| 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 router from './router'; | |||
| import TDesign from 'tdesign-vue-next'; | |||
| import { store } from './store'; | |||
| import 'tdesign-vue-next/es/style/index.css'; | |||
| import './style/index.less'; | |||
| @@ -29,4 +30,4 @@ console.log(OpenIM); | |||
| const app = createApp(App); | |||
| 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 | |||
| const routes = [ | |||
| { | |||
| path: '/', | |||
| redirect: '/home/dashboard', | |||
| }, | |||
| { | |||
| path: '/login', | |||
| name: 'Login', | |||
| 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 | |||
| @@ -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 type { AxiosTransform, CreateAxiosOptions } from './transform'; | |||
| import { formatRequestDate, joinTimestamp, setObjToUrlParams } from './utils'; | |||
| import { useUserStore } from '@/store'; | |||
| // Default API url | |||
| const host = import.meta.env.VITE_API_URL; | |||
| @@ -114,19 +115,20 @@ const transform: AxiosTransform = { | |||
| }, | |||
| 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; | |||
| }, | |||
| @@ -163,8 +165,8 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) { | |||
| return new VAxios( | |||
| merge( | |||
| <CreateAxiosOptions>{ | |||
| authenticationScheme: '', | |||
| // authenticationScheme: 'Bearer', | |||
| // authenticationScheme: '', | |||
| authenticationScheme: 'Bearer', // Use Bearer type token | |||
| timeout: 10 * 1000, | |||
| withCredentials: true, | |||
| 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 { IMLoginParam } from '@/api/request/openimModel'; | |||
| import { ref, reactive } from 'vue'; | |||
| import { localCache } from '@/utils/common/cache'; | |||
| import { useUserStore } from '@/store'; | |||
| const formData = reactive({ email: '', password: '' }); | |||
| const showPsw = ref(false); | |||
| const form = ref(null); | |||
| const isRem = ref(false); | |||
| const rules: FormRule = { | |||
| const rules: Record<string, FormRule[]> = { | |||
| email: [ | |||
| { | |||
| required: true, | |||
| @@ -63,20 +63,30 @@ const onSubmit = async (ctx: SubmitContext) => { | |||
| apiAddress: OpenIMLoginConfig.APIAddress, | |||
| wsAddress: OpenIMLoginConfig.WSAddress, | |||
| }; | |||
| let temp_data = res; | |||
| // operationID is auto generated in login method. | |||
| OpenIM.login(config) | |||
| .then(res => { | |||
| 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) { | |||
| localCache.setCache('token', config.token); | |||
| // localCache.setCache('token', config.token); | |||
| } else { | |||
| localCache.removeCache('token'); | |||
| // localCache.removeCache('token'); | |||
| } | |||
| const redirect = route.query.redirect as string; | |||
| const redirectUrl = redirect | |||
| ? decodeURIComponent(redirect) | |||
| : '/dashboard'; | |||
| : '/home/dashboard'; | |||
| router.push(redirectUrl); | |||
| }) | |||
| .catch(err => { | |||
| @@ -24,7 +24,7 @@ const formData = ref({ | |||
| community: COMMUNITY_INITIAL_DATA, | |||
| admin: ADMIN_INITIAL_DATA, | |||
| }); | |||
| const rules: FormRule = { | |||
| const rules: Record<string, FormRule[]> = { | |||
| email: [ | |||
| { | |||
| 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> | |||