Browse Source

feat: Add local info storage. (#127)

* 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
Asklv GitHub 3 years ago
parent
commit
3643fef975
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 845 additions and 192 deletions
  1. +5
    -1
      server/cmd/genhooks/pkg/template.go
  2. +56
    -0
      server/internal/api/community.go
  3. +55
    -0
      server/internal/api/user.go
  4. +10
    -1
      server/internal/common/code.go
  5. +16
    -0
      server/internal/common/msg.go
  6. +0
    -39
      server/internal/middleware/cros.go
  7. +0
    -72
      server/internal/middleware/frequency_control.go
  8. +5
    -7
      server/internal/middleware/hooks/gen_cors_hook.go
  9. +3
    -3
      server/internal/middleware/hooks/url_trie/trie_test.go
  10. +6
    -5
      server/internal/models/base/user_base.go
  11. +5
    -4
      server/internal/models/system_roles/sys_community.go
  12. +5
    -0
      server/internal/params/request/community.go
  13. +5
    -0
      server/internal/params/request/user.go
  14. +24
    -0
      server/internal/params/response/community.go
  15. +11
    -0
      server/internal/params/response/user.go
  16. +10
    -3
      server/internal/router/router.go
  17. +47
    -4
      server/internal/service/community.go
  18. +12
    -1
      server/internal/service/mail.go
  19. +38
    -1
      server/internal/service/user.go
  20. +17
    -21
      server/internal/utils/claims.go
  21. +5
    -5
      server/internal/utils/jwt.go
  22. +7
    -0
      server/main.go
  23. +27
    -0
      server/pkg/utils/valid.go
  24. +2
    -1
      web/.eslintrc.js
  25. +63
    -1
      web/package-lock.json
  26. +3
    -0
      web/package.json
  27. +17
    -0
      web/src/api/index/community.ts
  28. +36
    -0
      web/src/api/index/user.ts
  29. +4
    -0
      web/src/api/request/communityModel.ts
  30. +4
    -0
      web/src/api/request/userModel.ts
  31. +7
    -0
      web/src/api/response/communityModel.ts
  32. +10
    -0
      web/src/api/response/userModel.ts
  33. +2
    -1
      web/src/main.ts
  34. +37
    -0
      web/src/router/index.ts
  35. +12
    -0
      web/src/store/index.ts
  36. +3
    -0
      web/src/store/menu/index.ts
  37. +79
    -0
      web/src/store/user/index.ts
  38. +20
    -0
      web/src/types/interface.d.ts
  39. +17
    -15
      web/src/utils/request/index.ts
  40. +0
    -0
      web/src/views/.gitkeep
  41. +7
    -0
      web/src/views/dashboard/index.vue
  42. +7
    -0
      web/src/views/health/index.vue
  43. +7
    -0
      web/src/views/layouts/components/LayoutContent.vue
  44. +80
    -0
      web/src/views/layouts/components/LayoutSideNav.vue
  45. +21
    -0
      web/src/views/layouts/index.vue
  46. +16
    -6
      web/src/views/login/components/LoginForm.vue
  47. +1
    -1
      web/src/views/login/components/RegisterForm.vue
  48. +7
    -0
      web/src/views/platform/index.vue
  49. +7
    -0
      web/src/views/session/index.vue
  50. +7
    -0
      web/src/views/setting/index.vue

+ 5
- 1
server/cmd/genhooks/pkg/template.go View File

@@ -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...")
}



+ 56
- 0
server/internal/api/community.go View File

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

+ 55
- 0
server/internal/api/user.go View File

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

+ 10
- 1
server/internal/common/code.go View File

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

+ 16
- 0
server/internal/common/msg.go View File

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

+ 0
- 39
server/internal/middleware/cros.go View File

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

+ 0
- 72
server/internal/middleware/frequency_control.go View File

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

+ 5
- 7
server/internal/middleware/hooks/gen_cors_hook.go View File

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


+ 3
- 3
server/internal/middleware/hooks/url_trie/trie_test.go View File

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


+ 6
- 5
server/internal/models/base/user_base.go View File

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

+ 5
- 4
server/internal/models/system_roles/sys_community.go View File

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


+ 5
- 0
server/internal/params/request/community.go View File

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

+ 5
- 0
server/internal/params/request/user.go View File

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

+ 24
- 0
server/internal/params/response/community.go View File

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

+ 11
- 0
server/internal/params/response/user.go View File

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

+ 10
- 3
server/internal/router/router.go View File

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



+ 47
- 4
server/internal/service/community.go View File

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

+ 12
- 1
server/internal/service/mail.go View File

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


+ 38
- 1
server/internal/service/user.go View File

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

server/internal/middleware/jwt.go → server/internal/utils/claims.go View File

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

+ 5
- 5
server/internal/utils/jwt.go View File

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


+ 7
- 0
server/main.go View File

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



+ 27
- 0
server/pkg/utils/valid.go View File

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

+ 2
- 1
web/.eslintrc.js View File

@@ -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',
},
};

+ 63
- 1
web/package-lock.json View File

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


+ 3
- 0
web/package.json View File

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


+ 17
- 0
web/src/api/index/community.ts View File

@@ -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,
});
}

+ 36
- 0
web/src/api/index/user.ts View File

@@ -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,
});
}

+ 4
- 0
web/src/api/request/communityModel.ts View File

@@ -5,3 +5,7 @@ export interface CommunityInfo {
}

export type CreateCommunityParam = CommunityInfo;

export interface GetCommunityInfoParam {
uuid: string;
}

+ 4
- 0
web/src/api/request/userModel.ts View File

@@ -22,3 +22,7 @@ export interface AccountLoginParam {
email: string;
password: string;
}

export interface GetUserInfoParam {
uuid: string;
}

+ 7
- 0
web/src/api/response/communityModel.ts View File

@@ -0,0 +1,7 @@
export interface GetCommunityInfoResponse {
uuid: string;
name: string;
email: string;
avatar: string;
description: string;
}

+ 10
- 0
web/src/api/response/userModel.ts View File

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

+ 2
- 1
web/src/main.ts View File

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

+ 37
- 0
web/src/router/index.ts View File

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


+ 12
- 0
web/src/store/index.ts View File

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

+ 3
- 0
web/src/store/menu/index.ts View File

@@ -0,0 +1,3 @@
import { defineStore } from 'pinia';

import { usePermissionStore } from '@/store';

+ 79
- 0
web/src/store/user/index.ts View File

@@ -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 };
},
},
});

+ 20
- 0
web/src/types/interface.d.ts View File

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

+ 17
- 15
web/src/utils/request/index.ts View File

@@ -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
web/src/views/.gitkeep View File


+ 7
- 0
web/src/views/dashboard/index.vue View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<div></div>
</template>

<style lang="scss" scoped></style>

+ 7
- 0
web/src/views/health/index.vue View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<div></div>
</template>

<style lang="scss" scoped></style>

+ 7
- 0
web/src/views/layouts/components/LayoutContent.vue View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<div>asdas</div>
</template>

<style lang="less" scoped></style>

+ 80
- 0
web/src/views/layouts/components/LayoutSideNav.vue View File

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

+ 21
- 0
web/src/views/layouts/index.vue View File

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

+ 16
- 6
web/src/views/login/components/LoginForm.vue View File

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


+ 1
- 1
web/src/views/login/components/RegisterForm.vue View File

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


+ 7
- 0
web/src/views/platform/index.vue View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<div></div>
</template>

<style lang="scss" scoped></style>

+ 7
- 0
web/src/views/session/index.vue View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<div></div>
</template>

<style lang="scss" scoped></style>

+ 7
- 0
web/src/views/setting/index.vue View File

@@ -0,0 +1,7 @@
<script setup lang="ts"></script>

<template>
<div></div>
</template>

<style lang="scss" scoped></style>

Loading…
Cancel
Save