diff --git a/server/cmd/genhooks/pkg/template.go b/server/cmd/genhooks/pkg/template.go index 04ca747..f8b2e02 100644 --- a/server/cmd/genhooks/pkg/template.go +++ b/server/cmd/genhooks/pkg/template.go @@ -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...") } diff --git a/server/internal/api/community.go b/server/internal/api/community.go index 815be4f..d18f6e6 100644 --- a/server/internal/api/community.go +++ b/server/internal/api/community.go @@ -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) +} diff --git a/server/internal/api/user.go b/server/internal/api/user.go index b76e1eb..a94694d 100644 --- a/server/internal/api/user.go +++ b/server/internal/api/user.go @@ -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) +} diff --git a/server/internal/common/code.go b/server/internal/common/code.go index adaf05f..5cb5138 100644 --- a/server/internal/common/code.go +++ b/server/internal/common/code.go @@ -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 ) diff --git a/server/internal/common/msg.go b/server/internal/common/msg.go index 663b756..0eadf7c 100644 --- a/server/internal/common/msg.go +++ b/server/internal/common/msg.go @@ -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]) +} diff --git a/server/internal/middleware/cros.go b/server/internal/middleware/cros.go deleted file mode 100644 index fa31fc8..0000000 --- a/server/internal/middleware/cros.go +++ /dev/null @@ -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() - } -} diff --git a/server/internal/middleware/frequency_control.go b/server/internal/middleware/frequency_control.go deleted file mode 100644 index 01d1074..0000000 --- a/server/internal/middleware/frequency_control.go +++ /dev/null @@ -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() - } -} diff --git a/server/internal/middleware/hooks/gen_cors_hook.go b/server/internal/middleware/hooks/gen_cors_hook.go index 64caae9..85a83a6 100644 --- a/server/internal/middleware/hooks/gen_cors_hook.go +++ b/server/internal/middleware/hooks/gen_cors_hook.go @@ -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) diff --git a/server/internal/middleware/hooks/url_trie/trie_test.go b/server/internal/middleware/hooks/url_trie/trie_test.go index 9be1444..360aa87 100644 --- a/server/internal/middleware/hooks/url_trie/trie_test.go +++ b/server/internal/middleware/hooks/url_trie/trie_test.go @@ -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, }) } diff --git a/server/internal/models/base/user_base.go b/server/internal/models/base/user_base.go index a1d5b55..b32ffce 100644 --- a/server/internal/models/base/user_base.go +++ b/server/internal/models/base/user_base.go @@ -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"` } diff --git a/server/internal/models/system_roles/sys_community.go b/server/internal/models/system_roles/sys_community.go index ac96c33..8978b28 100644 --- a/server/internal/models/system_roles/sys_community.go +++ b/server/internal/models/system_roles/sys_community.go @@ -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. diff --git a/server/internal/params/request/community.go b/server/internal/params/request/community.go index ed71056..e708414 100644 --- a/server/internal/params/request/community.go +++ b/server/internal/params/request/community.go @@ -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"` +} diff --git a/server/internal/params/request/user.go b/server/internal/params/request/user.go index d2aede2..eaaa5ee 100644 --- a/server/internal/params/request/user.go +++ b/server/internal/params/request/user.go @@ -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"` +} diff --git a/server/internal/params/response/community.go b/server/internal/params/response/community.go new file mode 100644 index 0000000..fe4d34c --- /dev/null +++ b/server/internal/params/response/community.go @@ -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"` +} diff --git a/server/internal/params/response/user.go b/server/internal/params/response/user.go index fe1e179..276111c 100644 --- a/server/internal/params/response/user.go +++ b/server/internal/params/response/user.go @@ -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"` +} diff --git a/server/internal/router/router.go b/server/internal/router/router.go index 274ac00..fec87ca 100644 --- a/server/internal/router/router.go +++ b/server/internal/router/router.go @@ -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) } diff --git a/server/internal/service/community.go b/server/internal/service/community.go index 4a07800..928d511 100644 --- a/server/internal/service/community.go +++ b/server/internal/service/community.go @@ -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 } diff --git a/server/internal/service/mail.go b/server/internal/service/mail.go index 53a4718..4266839 100644 --- a/server/internal/service/mail.go +++ b/server/internal/service/mail.go @@ -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 { diff --git a/server/internal/service/user.go b/server/internal/service/user.go index e59f126..e99ab72 100644 --- a/server/internal/service/user.go +++ b/server/internal/service/user.go @@ -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 +} diff --git a/server/internal/middleware/jwt.go b/server/internal/utils/claims.go similarity index 54% rename from server/internal/middleware/jwt.go rename to server/internal/utils/claims.go index e904f54..c4e536c 100644 --- a/server/internal/middleware/jwt.go +++ b/server/internal/utils/claims.go @@ -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") } diff --git a/server/internal/utils/jwt.go b/server/internal/utils/jwt.go index b06bd90..3300b9d 100644 --- a/server/internal/utils/jwt.go +++ b/server/internal/utils/jwt.go @@ -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)), diff --git a/server/main.go b/server/main.go index c036f89..e0991a3 100644 --- a/server/main.go +++ b/server/main.go @@ -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) diff --git a/server/pkg/utils/valid.go b/server/pkg/utils/valid.go new file mode 100644 index 0000000..0ec5b82 --- /dev/null +++ b/server/pkg/utils/valid.go @@ -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) +} diff --git a/web/.eslintrc.js b/web/.eslintrc.js index e8c7577..e2c18b9 100644 --- a/web/.eslintrc.js +++ b/web/.eslintrc.js @@ -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', }, }; diff --git a/web/package-lock.json b/web/package-lock.json index 7cc8746..720b56a 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -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" diff --git a/web/package.json b/web/package.json index 7a54b95..e75c5f9 100644 --- a/web/package.json +++ b/web/package.json @@ -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", diff --git a/web/src/api/index/community.ts b/web/src/api/index/community.ts index 226f0a9..99ee01f 100644 --- a/web/src/api/index/community.ts +++ b/web/src/api/index/community.ts @@ -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({ + url: API.CommunityMe, + }); +} + +// Get community info +export function getCommunityInfo(data: GetCommunityInfoResponse) { + return request.get({ + url: API.CommunityMe, + params: data, + }); +} diff --git a/web/src/api/index/user.ts b/web/src/api/index/user.ts new file mode 100644 index 0000000..929ee92 --- /dev/null +++ b/web/src/api/index/user.ts @@ -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({ + url: API.UserMe, + }); +} + +// Get User Info +export function getUserInfo(data: GetUserInfoParam) { + return request.get({ + url: API.UserMe, + params: data, + }); +} diff --git a/web/src/api/request/communityModel.ts b/web/src/api/request/communityModel.ts index 36239c5..c78c7b2 100644 --- a/web/src/api/request/communityModel.ts +++ b/web/src/api/request/communityModel.ts @@ -5,3 +5,7 @@ export interface CommunityInfo { } export type CreateCommunityParam = CommunityInfo; + +export interface GetCommunityInfoParam { + uuid: string; +} diff --git a/web/src/api/request/userModel.ts b/web/src/api/request/userModel.ts index 2c4fc14..831cfe4 100644 --- a/web/src/api/request/userModel.ts +++ b/web/src/api/request/userModel.ts @@ -22,3 +22,7 @@ export interface AccountLoginParam { email: string; password: string; } + +export interface GetUserInfoParam { + uuid: string; +} diff --git a/web/src/api/response/communityModel.ts b/web/src/api/response/communityModel.ts new file mode 100644 index 0000000..1861b8e --- /dev/null +++ b/web/src/api/response/communityModel.ts @@ -0,0 +1,7 @@ +export interface GetCommunityInfoResponse { + uuid: string; + name: string; + email: string; + avatar: string; + description: string; +} diff --git a/web/src/api/response/userModel.ts b/web/src/api/response/userModel.ts index 10a708c..831db8e 100644 --- a/web/src/api/response/userModel.ts +++ b/web/src/api/response/userModel.ts @@ -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; +} diff --git a/web/src/main.ts b/web/src/main.ts index c6688ab..d79eb22 100644 --- a/web/src/main.ts +++ b/web/src/main.ts @@ -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 diff --git a/web/src/router/index.ts b/web/src/router/index.ts index d394c18..eb1e924 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -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 diff --git a/web/src/store/index.ts b/web/src/store/index.ts new file mode 100644 index 0000000..7f62c9a --- /dev/null +++ b/web/src/store/index.ts @@ -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; diff --git a/web/src/store/menu/index.ts b/web/src/store/menu/index.ts new file mode 100644 index 0000000..f8760fb --- /dev/null +++ b/web/src/store/menu/index.ts @@ -0,0 +1,3 @@ +import { defineStore } from 'pinia'; + +import { usePermissionStore } from '@/store'; diff --git a/web/src/store/user/index.ts b/web/src/store/user/index.ts new file mode 100644 index 0000000..9891224 --- /dev/null +++ b/web/src/store/user/index.ts @@ -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 }; + }, + }, +}); diff --git a/web/src/types/interface.d.ts b/web/src/types/interface.d.ts new file mode 100644 index 0000000..c1e66ed --- /dev/null +++ b/web/src/types/interface.d.ts @@ -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 {} diff --git a/web/src/utils/request/index.ts b/web/src/utils/request/index.ts index 5ed4994..33c6aee 100644 --- a/web/src/utils/request/index.ts +++ b/web/src/utils/request/index.ts @@ -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) { return new VAxios( merge( { - authenticationScheme: '', - // authenticationScheme: 'Bearer', + // authenticationScheme: '', + authenticationScheme: 'Bearer', // Use Bearer type token timeout: 10 * 1000, withCredentials: true, headers: { 'Content-Type': ContentTypeEnum.Json }, diff --git a/web/src/views/.gitkeep b/web/src/views/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/web/src/views/dashboard/index.vue b/web/src/views/dashboard/index.vue new file mode 100644 index 0000000..c6fe2d5 --- /dev/null +++ b/web/src/views/dashboard/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/web/src/views/health/index.vue b/web/src/views/health/index.vue new file mode 100644 index 0000000..c6fe2d5 --- /dev/null +++ b/web/src/views/health/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/web/src/views/layouts/components/LayoutContent.vue b/web/src/views/layouts/components/LayoutContent.vue new file mode 100644 index 0000000..d40bf15 --- /dev/null +++ b/web/src/views/layouts/components/LayoutContent.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/web/src/views/layouts/components/LayoutSideNav.vue b/web/src/views/layouts/components/LayoutSideNav.vue new file mode 100644 index 0000000..e55746c --- /dev/null +++ b/web/src/views/layouts/components/LayoutSideNav.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/web/src/views/layouts/index.vue b/web/src/views/layouts/index.vue new file mode 100644 index 0000000..e63a1e9 --- /dev/null +++ b/web/src/views/layouts/index.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/web/src/views/login/components/LoginForm.vue b/web/src/views/login/components/LoginForm.vue index aab7720..561f5cd 100644 --- a/web/src/views/login/components/LoginForm.vue +++ b/web/src/views/login/components/LoginForm.vue @@ -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 = { 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 => { diff --git a/web/src/views/login/components/RegisterForm.vue b/web/src/views/login/components/RegisterForm.vue index 4443e10..6f1a97e 100644 --- a/web/src/views/login/components/RegisterForm.vue +++ b/web/src/views/login/components/RegisterForm.vue @@ -24,7 +24,7 @@ const formData = ref({ community: COMMUNITY_INITIAL_DATA, admin: ADMIN_INITIAL_DATA, }); -const rules: FormRule = { +const rules: Record = { email: [ { required: true, diff --git a/web/src/views/platform/index.vue b/web/src/views/platform/index.vue new file mode 100644 index 0000000..c6fe2d5 --- /dev/null +++ b/web/src/views/platform/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/web/src/views/session/index.vue b/web/src/views/session/index.vue new file mode 100644 index 0000000..c6fe2d5 --- /dev/null +++ b/web/src/views/session/index.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/web/src/views/setting/index.vue b/web/src/views/setting/index.vue new file mode 100644 index 0000000..c6fe2d5 --- /dev/null +++ b/web/src/views/setting/index.vue @@ -0,0 +1,7 @@ + + + + +