From 0f8e26f815506205017fc6e9863c76952c38f14a Mon Sep 17 00:00:00 2001
From: RockChinQ <1010553892@qq.com>
Date: Sat, 27 Jul 2024 15:32:07 +0800
Subject: [PATCH 1/2] =?UTF-8?q?feat:=20oauth2=20app=20=E7=AE=A1=E7=90=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.github/workflows/build-latest-backend.yaml | 2 +-
backend/controller/admapi.go | 119 ++++++++++++++++
backend/controller/api.go | 2 +
backend/controller/dto.go | 8 ++
backend/core/app.go | 3 +-
backend/database/mongo.go | 60 ++++++++
backend/database/po.go | 8 ++
backend/service/admin.go | 52 +++++++
backend/service/errors.go | 3 +
backend/util/string.go | 14 ++
frontend/package.json | 1 +
frontend/src/components/BanRecordCard.vue | 2 +-
frontend/src/components/OAuthAppCard.vue | 111 +++++++++++++++
frontend/src/pages/admin.vue | 150 +++++++++++++++++++-
14 files changed, 529 insertions(+), 6 deletions(-)
create mode 100644 backend/controller/admapi.go
create mode 100644 backend/service/admin.go
create mode 100644 frontend/src/components/OAuthAppCard.vue
diff --git a/.github/workflows/build-latest-backend.yaml b/.github/workflows/build-latest-backend.yaml
index 61ef7b8..6efcde7 100644
--- a/.github/workflows/build-latest-backend.yaml
+++ b/.github/workflows/build-latest-backend.yaml
@@ -56,5 +56,5 @@ jobs:
--header 'Authorization: Bearer ${{ secrets.ONEBOT_V11_TOKEN }}' \
--data '{
"group_id": ${{ secrets.ONEBOT_V11_GROUP_ID }},
- "message": "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 构建完成。"
+ "message": "Campux 构建完成。"
}'
\ No newline at end of file
diff --git a/backend/controller/admapi.go b/backend/controller/admapi.go
new file mode 100644
index 0000000..e9e028f
--- /dev/null
+++ b/backend/controller/admapi.go
@@ -0,0 +1,119 @@
+package controller
+
+import (
+ "github.com/RockChinQ/Campux/backend/database"
+ "github.com/RockChinQ/Campux/backend/service"
+ "github.com/gin-gonic/gin"
+)
+
+type AdminRouter struct {
+ APIRouter
+ AdminService service.AdminService
+}
+
+func NewAdminRouter(rg *gin.RouterGroup, as service.AdminService) *AdminRouter {
+ ar := &AdminRouter{
+ AdminService: as,
+ }
+
+ group := rg.Group("/admin")
+
+ // bind routes
+ group.POST("/add-oauth2-app", ar.AddOAuth2App)
+ group.GET("/get-oauth2-apps", ar.GetOAuth2AppList)
+ group.DELETE("/del-oauth2-app/:id", ar.DeleteOAuth2App)
+
+ return ar
+}
+
+// 添加一个OAuth2应用
+func (ar *AdminRouter) AddOAuth2App(c *gin.Context) {
+
+ uin, err := ar.Auth(c, UserOnly)
+
+ if err != nil {
+ return
+ }
+
+ if !ar.AdminService.CheckUserGroup(uin, []database.UserGroup{
+ database.USER_GROUP_ADMIN,
+ }) {
+ ar.StatusCode(c, 401, "权限不足")
+ return
+ }
+
+ // 取body的json里的appname
+ var body OAuth2AppCreateBody
+
+ if err := c.ShouldBindJSON(&body); err != nil {
+ ar.Fail(c, 1, err.Error())
+ return
+ }
+
+ // 创建OAuth2应用
+ app, err := ar.AdminService.AddOAuth2App(body.Name, body.Emoji)
+
+ if err != nil {
+ ar.Fail(c, 2, err.Error())
+ return
+ }
+
+ ar.Success(c, app)
+}
+
+// 获取 OAuth2 应用列表
+func (ar *AdminRouter) GetOAuth2AppList(c *gin.Context) {
+ uin, err := ar.Auth(c, UserOnly)
+
+ if err != nil {
+ return
+ }
+
+ if !ar.AdminService.CheckUserGroup(uin, []database.UserGroup{
+ database.USER_GROUP_ADMIN,
+ }) {
+ ar.StatusCode(c, 401, "权限不足")
+ return
+ }
+
+ // 获取OAuth2应用列表
+ list, err := ar.AdminService.GetOAuth2Apps()
+
+ if err != nil {
+ ar.Fail(c, 1, err.Error())
+ return
+ }
+
+ ar.Success(c, gin.H{
+ "list": list,
+ })
+}
+
+// 删除一个OAuth2应用
+func (ar *AdminRouter) DeleteOAuth2App(c *gin.Context) {
+ uin, err := ar.Auth(c, UserOnly)
+
+ if err != nil {
+ return
+ }
+
+ if !ar.AdminService.CheckUserGroup(uin, []database.UserGroup{
+ database.USER_GROUP_ADMIN,
+ }) {
+ ar.StatusCode(c, 401, "权限不足")
+ return
+ }
+
+ // 取路由参数
+ appID := c.Param("id")
+
+ // 删除OAuth2应用
+ err = ar.AdminService.DeleteOAuth2App(appID)
+
+ if err != nil {
+ ar.Fail(c, 1, err.Error())
+ return
+ }
+
+ ar.Success(c, nil)
+}
diff --git a/backend/controller/api.go b/backend/controller/api.go
index 6b48eda..1139907 100644
--- a/backend/controller/api.go
+++ b/backend/controller/api.go
@@ -21,6 +21,7 @@ func NewApiController(
as service.AccountService,
ps service.PostService,
ms service.MiscService,
+ ads service.AdminService,
) *APIController {
r := gin.Default()
@@ -63,6 +64,7 @@ func NewApiController(
NewAccountRouter(rg, as)
NewPostRouter(rg, ps, as)
NewMiscRouter(rg, ms)
+ NewAdminRouter(rg, ads)
return &APIController{
R: r,
diff --git a/backend/controller/dto.go b/backend/controller/dto.go
index eb8d7f8..c461a2e 100644
--- a/backend/controller/dto.go
+++ b/backend/controller/dto.go
@@ -151,3 +151,11 @@ type GetBanListBody struct {
// 时间排序
TimeOrder *int `json:"time_order" binding:"required"`
}
+
+type OAuth2AppCreateBody struct {
+ // 名称
+ Name string `json:"name" binding:"required"`
+
+ // emoji
+ Emoji string `json:"emoji" binding:"required"`
+}
diff --git a/backend/core/app.go b/backend/core/app.go
index a79120e..96e5229 100644
--- a/backend/core/app.go
+++ b/backend/core/app.go
@@ -27,6 +27,7 @@ func NewApplication() *Application {
as := service.NewAccountService(*db)
ps := service.NewPostService(*db, *fs, *msq)
ms := service.NewMiscService(*db)
+ ads := service.NewAdminService(*db)
err := ScheduleRoutines(*db, *msq)
if err != nil {
@@ -34,7 +35,7 @@ func NewApplication() *Application {
}
return &Application{
- API: controller.NewApiController(*as, *ps, *ms),
+ API: controller.NewApiController(*as, *ps, *ms, *ads),
}
}
diff --git a/backend/database/mongo.go b/backend/database/mongo.go
index c611674..6d13db1 100644
--- a/backend/database/mongo.go
+++ b/backend/database/mongo.go
@@ -18,6 +18,7 @@ const (
POST_VERBOSE_COLLECTION = "post_verbose"
METADATA_COLLECTION = "metadata"
BAN_LIST_COLLECTION = "ban_list"
+ OAUTH_APP_COLLECTION = "oauth_app"
)
type Metadata struct {
@@ -563,3 +564,62 @@ func (m *MongoDBManager) GetMetadata(key string) (string, error) {
}
return meta.Value, nil
}
+
+func (m *MongoDBManager) AddOAuth2App(app *OAuthAppPO) error {
+ _, err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).InsertOne(context.TODO(), app)
+ return err
+}
+
+func (m *MongoDBManager) GetOAuth2App(clientID string) (*OAuthAppPO, error) {
+ var app OAuthAppPO
+ err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).FindOne(
+ context.TODO(),
+ bson.M{"client_id": clientID},
+ ).Decode(&app)
+ if err != nil {
+ if err == mongo.ErrNoDocuments {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return &app, nil
+}
+
+func (m *MongoDBManager) GetOAuth2AppByName(name string) (*OAuthAppPO, error) {
+ // 若不存在,返回 nil, nil
+ var app OAuthAppPO
+
+ err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).FindOne(
+ context.TODO(),
+ bson.M{"name": name},
+ ).Decode(&app)
+ if err != nil {
+ if err == mongo.ErrNoDocuments {
+ return nil, nil
+ }
+ return nil, err
+ }
+ return &app, nil
+}
+
+// list
+func (m *MongoDBManager) GetOAuth2Apps() ([]OAuthAppPO, error) {
+ var apps []OAuthAppPO
+ cursor, err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).Find(context.TODO(), bson.M{})
+ if err != nil {
+ return nil, err
+ }
+ defer cursor.Close(context.Background())
+
+ err = cursor.All(context.Background(), &apps)
+ if err != nil {
+ return nil, err
+ }
+
+ return apps, nil
+}
+
+func (m *MongoDBManager) DeleteOAuth2App(clientID string) error {
+ _, err := m.Client.Database(viper.GetString("database.mongo.db")).Collection(OAUTH_APP_COLLECTION).DeleteOne(context.TODO(), bson.M{"client_id": clientID})
+ return err
+}
diff --git a/backend/database/po.go b/backend/database/po.go
index 5b5d6b5..5502982 100644
--- a/backend/database/po.go
+++ b/backend/database/po.go
@@ -84,3 +84,11 @@ const (
REVIEW_OPTION_APPROVE ReviewOption = "approve"
REVIEW_OPTION_REJECT ReviewOption = "reject"
)
+
+type OAuthAppPO struct {
+ Name string `json:"name" bson:"name"` // 应用名称
+ Emoji string `json:"emoji" bson:"emoji"` // Emoji
+ ClientID string `json:"client_id" bson:"client_id"` // 客户端ID
+ ClientSecret string `json:"client_secret" bson:"client_secret"` // 客户端密钥
+ CreatedAt time.Time `json:"created_at" bson:"created_at"` // CST时间
+}
diff --git a/backend/service/admin.go b/backend/service/admin.go
new file mode 100644
index 0000000..88ac97f
--- /dev/null
+++ b/backend/service/admin.go
@@ -0,0 +1,52 @@
+package service
+
+import (
+ "github.com/RockChinQ/Campux/backend/database"
+ "github.com/RockChinQ/Campux/backend/util"
+ "github.com/google/uuid"
+)
+
+type AdminService struct {
+ CommonService
+}
+
+func NewAdminService(db database.MongoDBManager) *AdminService {
+ return &AdminService{
+ CommonService: CommonService{
+ DB: db,
+ },
+ }
+}
+
+func (as *AdminService) AddOAuth2App(name, emoji string) (*database.OAuthAppPO, error) {
+ check, err := as.DB.GetOAuth2AppByName(name)
+
+ if err != nil {
+ return nil, err
+ }
+
+ if check != nil {
+ return nil, ErrOAuth2AppAlreadyExist
+ }
+
+ app := &database.OAuthAppPO{
+ Name: name,
+ Emoji: emoji,
+ ClientID: util.RandomString(16),
+ ClientSecret: uuid.New().String(),
+ CreatedAt: util.GetCSTTime(),
+ }
+
+ err = as.DB.AddOAuth2App(app)
+
+ return app, err
+}
+
+func (as *AdminService) GetOAuth2Apps() ([]database.OAuthAppPO, error) {
+ return as.DB.GetOAuth2Apps()
+}
+
+// delete
+func (as *AdminService) DeleteOAuth2App(appID string) error {
+ return as.DB.DeleteOAuth2App(appID)
+}
diff --git a/backend/service/errors.go b/backend/service/errors.go
index 4bf8c6c..d680e98 100644
--- a/backend/service/errors.go
+++ b/backend/service/errors.go
@@ -13,3 +13,6 @@ var ErrPasswordIncorrect = errors.New("密码错误")
// 不允许的图片后缀
var ErrInvalidImageSuffix = errors.New("不允许的图片后缀")
+
+// OAuth2应用名称已存在
+var ErrOAuth2AppAlreadyExist = errors.New("OAuth2应用名称已存在")
diff --git a/backend/util/string.go b/backend/util/string.go
index b0320b9..eef5e9d 100644
--- a/backend/util/string.go
+++ b/backend/util/string.go
@@ -1,5 +1,7 @@
package util
+import "math/rand"
+
func StringInSlice(str string, list []string) bool {
for _, v := range list {
if v == str {
@@ -8,3 +10,15 @@ func StringInSlice(str string, list []string) bool {
}
return false
}
+
+func RandomString(length int) string {
+ list := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
+
+ var result []byte
+
+ for i := 0; i < length; i++ {
+ result = append(result, list[rand.Intn(len(list))])
+ }
+
+ return string(result)
+}
diff --git a/frontend/package.json b/frontend/package.json
index 1cc48d5..dd5dab8 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,6 +15,7 @@
"roboto-fontface": "*",
"vue": "^3.4.0",
"vue-cookies": "^1.8.4",
+ "vue3-emoji-picker": "^1.1.8",
"vuetify": "^3.5.0",
"vuex": "^4.1.0"
},
diff --git a/frontend/src/components/BanRecordCard.vue b/frontend/src/components/BanRecordCard.vue
index 1287ff9..60fac4c 100644
--- a/frontend/src/components/BanRecordCard.vue
+++ b/frontend/src/components/BanRecordCard.vue
@@ -30,7 +30,7 @@
+
+
\ No newline at end of file
diff --git a/frontend/src/pages/admin.vue b/frontend/src/pages/admin.vue
index 97acc0c..6d75453 100644
--- a/frontend/src/pages/admin.vue
+++ b/frontend/src/pages/admin.vue
@@ -7,6 +7,7 @@
🪪 账号
🚫 封禁记录
+ 🔑 OAuth 2 应用
@@ -23,7 +24,7 @@
- 查找
+ 查找
- 查找
+ 查找
@@ -61,6 +62,22 @@
+
+
+
+
+ 新建 OAuth2 应用
+ 刷新
+
+
+
+
+
+
+
+
@@ -77,13 +94,40 @@
+
+
+ 新建 OAuth2 应用
+
+
+
+
{{ newOAuthApp.emoji }}
+
+
+
+
+ 取消
+ 确定
+
+
+
+